Exit prices on the tick grid, and a refused take-profit flattens
4217.6 − 87 × 0.1 is 4208.900000000001 in floating point. TopstepX refused
that take-profit ("Invalid price. Price is outside allowed range") on two
GC shorts on 2026-10-02, and with no stop on the leg nothing else was
working: both rode far past their target with no exit. ES and NQ never hit
it because 0.25 is exact in binary; GC and CL ticks aren't.
- onTick puts the measured-fee take-profit and every stop on the grid —
the take-profit away from the entry, the stop toward it — so an averaged
multi-contract fill between ticks is handled the same way.
- A leg's lone take-profit (no stop, or the fallback after a refused
bracket) that the broker refuses now flattens the position and benches
the account for the day, like a refused bracket. An unknown outcome still
places nothing more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d6bb5a7221
commit
8747dd6b08
+19
-2
@@ -1085,11 +1085,28 @@ export function percentFee(pct: number, price: number, pointValue: number, contr
|
||||
return Math.ceil(raw * 100 - 1e-9) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* A price on the tick grid, free of float fuzz. 4217.6 − 87 × 0.1 is
|
||||
* 4208.900000000001 in floating point, and TopstepX refuses a price off the
|
||||
* grid ("Invalid price. Price is outside allowed range") — a GC short rode
|
||||
* with no take-profit on 2026-10-02. An averaged multi-contract fill can sit
|
||||
* between ticks too (by 1/contracts of a tick or more), so an off-grid price
|
||||
* goes `mode`'s way; fuzz within a millionth of a tick is never pushed a
|
||||
* whole tick.
|
||||
*/
|
||||
export function onTick(price: number, tickSize: number, mode: 'up' | 'down'): number {
|
||||
if (!(tickSize > 0)) return price;
|
||||
const n = price / tickSize;
|
||||
const k = mode === 'up' ? Math.ceil(n - 1e-6) : Math.floor(n + 1e-6);
|
||||
return Math.round(k * tickSize * 1e10) / 1e10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where to put the take-profit so the leg nets at least `leg` after fees.
|
||||
*
|
||||
* Measured-fee firms (commissionPct 0): the exit fee is taken to equal the
|
||||
* entry fee — the same formula the fire path always used, reproduced exactly.
|
||||
* entry fee — the same formula the fire path always used, reproduced exactly,
|
||||
* then put on the tick grid away from the entry (onTick).
|
||||
*
|
||||
* Percent-fee firms: the exit fee depends on the exit price itself, so solve
|
||||
* for it. With r = pct/100 and k = pointValue × contracts, a long nets
|
||||
@@ -1116,7 +1133,7 @@ export function takeProfitPlan(o: {
|
||||
const grossTarget = o.leg + totalCommission;
|
||||
const ticks = Math.ceil(grossTarget / k / o.tickSize);
|
||||
return {
|
||||
exitPrice: o.entry + dir * ticks * o.tickSize,
|
||||
exitPrice: onTick(o.entry + dir * ticks * o.tickSize, o.tickSize, o.side === 'Buy' ? 'up' : 'down'),
|
||||
ticks,
|
||||
entryFee: o.entryCommission,
|
||||
exitFee: o.entryCommission,
|
||||
|
||||
Reference in New Issue
Block a user