// Checks for the pure trading math in lib/trading-logic.ts. // // npm run check // // .mjs on purpose: Next's tsconfig includes every .ts file, and a .ts test // importing another .ts by extension would trip the build's type check. Node // strips the types from lib/trading-logic.ts at import time (it has no // imports of its own). import { test } from 'node:test'; import assert from 'node:assert/strict'; import { percentFee, takeProfitPlan, onTick, obfuscationStopDollars, dllRoom, dllStopDollars, dllViable, stopTicks, botStopsActive, sizeNotional, isNoTradeTime, isPastSessionClose, nextTradingDate, effectiveCombine, DEFAULT_TARGET_HOLD_HOURS, projectPerfectPath, cycleCompletion, DLL_RESERVE_FRACTION, DLL_MIN_STOP, notionalCap, expectedSwapToWin, MARGIN_HEADROOM, dllReserve, DLL_SLIPPAGE_FRACTION, maxLossRoom, lastStand, breachDistance, LAST_STAND_FRACTION, planLegSize, rankForTarget, currentTradeDateCT, foldReason, withinFirmCaps, personSizeFactor, personHoldHours, PERSON_SIZE_SPREAD, machineSide, resolveEffectiveConfig, stageInputsFor, floorRoom, resetClearStop, shortestNextLeg, computeDayPlan, clearsClose, RESET_CLEAR_HOLDS, } from '../lib/trading-logic.ts'; // Deterministic PRNG so a failure reproduces. function rng(seed) { let s = seed >>> 0; return () => { s = (s + 0x6d2b79f5) >>> 0; let t = s; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } // ── prices on the tick grid ───────────────────────────────────────────────── test('onTick: 2026-10-02 GC short — 87 ticks below 4217.6 is 4208.9, not 4208.900000000001', () => { assert.notEqual(4217.6 - 87 * 0.1, 4208.9); // the fuzz TopstepX refused const tp = takeProfitPlan({ side: 'Sell', entry: 4217.6, leg: 861.4, pointValue: 100, contracts: 1, tickSize: 0.1, commissionPct: 0, entryCommission: 2.8 }); assert.equal(tp.ticks, 87); assert.equal(tp.exitPrice, 4208.9); assert.equal(JSON.stringify(tp.exitPrice), '4208.9'); }); test('onTick: measured-fee take-profits always land on the grid, as the shortest decimal', () => { const r = rng(11); for (let i = 0; i < 5000; i++) { const side = r() < 0.5 ? 'Buy' : 'Sell'; const [pv, tick] = [[100, 0.1], [1000, 0.01], [10, 0.1], [12.5, 0.0001], [1000, 0.03125], [50, 0.25]][Math.floor(r() * 6)]; const decimals = String(tick).split('.')[1].length; const entry = Number((Math.round((50 + r() * 20000) / tick) * tick).toFixed(decimals)); const tp = takeProfitPlan({ side, entry, leg: 5 + r() * 2500, pointValue: pv, contracts: 1 + Math.floor(r() * 4), tickSize: tick, commissionPct: 0, entryCommission: Math.round(r() * 1000) / 100 }); assert.equal(tp.exitPrice, Number(tp.exitPrice.toFixed(decimals)), `${tp.exitPrice} has more decimals than tick ${tick}`); assert.equal(Math.round(Math.abs(tp.exitPrice - entry) / tick), tp.ticks); } }); test('onTick: an averaged fill between ticks goes the given way; fuzz never moves a tick', () => { assert.equal(onTick(7749.125, 0.25, 'up'), 7749.25); // 2 ES filled at .00 and .25 assert.equal(onTick(7749.125, 0.25, 'down'), 7749); assert.equal(onTick(4208.900000000001, 0.1, 'up'), 4208.9); assert.equal(onTick(4208.899999999999, 0.1, 'down'), 4208.9); assert.equal(onTick(62.07, 0.01, 'up'), 62.07); assert.equal(onTick(123.5, 0, 'up'), 123.5); // no tick size known: untouched }); // ── parity with the pre-refactor inline code ──────────────────────────────── /** The take-profit exactly as auto-trade.ts computed it before takeProfitPlan. */ function oldTakeProfit(side, entry, leg, pv, contracts, tickSize, entryCommission) { const totalCommission = entryCommission * 2; const grossTarget = leg + totalCommission; const targetPoints = grossTarget / (pv * contracts); const ticks = Math.ceil(targetPoints / tickSize); const exitPrice = side === 'Buy' ? entry + ticks * tickSize : entry - ticks * tickSize; return { exitPrice, ticks, totalCommission, grossTarget }; } test('takeProfitPlan reproduces the old formula when fees are measured', () => { const r = rng(1); for (let i = 0; i < 2000; i++) { const side = r() < 0.5 ? 'Buy' : 'Sell'; const [pv, tick] = [[20, 0.25], [2, 0.25], [100, 0.1], [1000, 0.01], [50, 0.25]][Math.floor(r() * 5)]; const entry = Math.round((1000 + r() * 20000) / tick) * tick; const leg = Math.round(5 + r() * 2500); const contracts = 1 + Math.floor(r() * 4); const fee = Math.round(r() * 1000) / 100; const got = takeProfitPlan({ side, entry, leg, pointValue: pv, contracts, tickSize: tick, commissionPct: 0, entryCommission: fee }); const want = oldTakeProfit(side, entry, leg, pv, contracts, tick, fee); // The same price, minus the old formula's float fuzz. assert.equal(got.exitPrice, Math.round(want.exitPrice * 1e10) / 1e10); assert.equal(got.ticks, want.ticks); assert.equal(got.totalCommission, want.totalCommission); assert.equal(got.grossTarget, want.grossTarget); } }); /** obfuscationStopDollars exactly as it lived in auto-trade.ts. */ function oldObfuscation(cfg, dailyPnL, plan, tradeDate, legAmount) { if (!cfg.firstDayStop && !cfg.minDayStop) return Infinity; const prior = dailyPnL.filter((d) => d.date < tradeDate && d.pnl !== 0).sort((a, b) => a.date.localeCompare(b.date)); if (prior.length > 0 && prior[prior.length - 1].pnl < 0) return Infinity; let stop = Infinity; const realizedToday = Math.round((plan.dayTarget - plan.remainingToday) * 100) / 100; if (cfg.firstDayStop && prior.length === 0) stop = Math.max(0, 1000 + realizedToday); if (cfg.minDayStop && plan.dayTarget <= 1700) stop = Math.min(stop, 2 * legAmount); return stop; } test('obfuscationStopDollars is unchanged by the move', () => { const r = rng(2); for (let i = 0; i < 1000; i++) { const cfg = { firstDayStop: r() < 0.5, minDayStop: r() < 0.5 }; const days = Math.floor(r() * 4); const dailyPnL = Array.from({ length: days }, (_, d) => ({ date: `2026-09-0${d + 1}`, pnl: Math.round((r() - 0.4) * 2000) })); const dayTarget = Math.round(r() * 3000); const plan = { dayTarget, remainingToday: Math.round(dayTarget * r()), path: 'normal_day', splitAllowed: true }; const leg = Math.round(100 + r() * 1500); assert.equal(obfuscationStopDollars(cfg, dailyPnL, plan, '2026-09-08', leg), oldObfuscation(cfg, dailyPnL, plan, '2026-09-08', leg)); } }); /** isInNoTradeWindow as it was, with the clock and settings passed in. */ function oldNoTrade(now, mode, jitter) { const centralParts = new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: 'numeric', hour12: false, timeZone: 'America/Chicago' }).formatToParts(now); const hour = parseInt(centralParts.find((p) => p.type === 'hour').value, 10); const minute = parseInt(centralParts.find((p) => p.type === 'minute').value, 10); const day = new Intl.DateTimeFormat('en-US', { weekday: 'short', timeZone: 'America/Chicago' }).format(now); const timeMinutes = hour * 60 + minute; if (mode === 'equity_hours') { if (day === 'Sat' || day === 'Sun') return true; const open = 8 * 60 + 35 + jitter; const close = 14 * 60 + 55; return timeMinutes < open || timeMinutes >= close; } if (day === 'Sat') return true; const cmeOpen = 17 * 60 + 45 + jitter; if (day === 'Sun') return timeMinutes < cmeOpen; const cmeClose = 14 * 60 + 55; if (day === 'Fri' && timeMinutes >= cmeClose) return true; if (timeMinutes >= cmeClose && timeMinutes < cmeOpen) return true; return false; } function oldAfterClose(now) { const parts = new Intl.DateTimeFormat('en-US', { weekday: 'short', hour: 'numeric', minute: 'numeric', hour12: false, timeZone: 'America/Chicago' }).formatToParts(now); const get = (type) => parts.find((p) => p.type === type)?.value ?? ''; if (get('weekday') === 'Sat' || get('weekday') === 'Sun') return false; const minutes = (parseInt(get('hour'), 10) % 24) * 60 + parseInt(get('minute'), 10); return minutes >= 14 * 60 + 55 && minutes < 17 * 60; } test('CME trading hours: 5:45 PM plus the delay to 2:55 PM, across both DST regimes', () => { for (const start of [Date.UTC(2026, 8, 20), Date.UTC(2026, 0, 11)]) { // a September and a January week for (let m = 0; m < 7 * 24 * 60; m += 7) { const now = new Date(start + m * 60_000); for (const mode of ['full_cme', 'equity_hours']) { for (const jitter of [0, 1, 13, 20, 95, 180]) { assert.equal(isNoTradeTime(now, { everyDay: false, mode, jitterMin: jitter }), oldNoTrade(now, mode, jitter), `${now.toISOString()} ${mode} ${jitter}`); } } assert.equal(isPastSessionClose(now, false), oldAfterClose(now), now.toISOString()); } } }); // ── every-day hours ────────────────────────────────────────────────────────── const ct = (iso, offsetHours) => new Date(Date.parse(`${iso}Z`) + offsetHours * 3600_000); const cdt = (iso) => ct(iso, 5); // Central daylight: UTC−5 const cst = (iso) => ct(iso, 6); // Central standard: UTC−6 test('the evening session opens at 5:45 PM CT, and a 180-minute delay makes it 8:45', () => { const o = { everyDay: false, mode: 'full_cme', jitterMin: 180 }; assert.equal(isNoTradeTime(cdt('2026-09-28T17:45:00'), o), true); // Monday assert.equal(isNoTradeTime(cdt('2026-09-28T20:44:00'), o), true); assert.equal(isNoTradeTime(cdt('2026-09-28T20:45:00'), o), false); assert.equal(isNoTradeTime(cdt('2026-09-27T20:44:00'), o), true); // Sunday too assert.equal(isNoTradeTime(cdt('2026-09-27T20:45:00'), o), false); assert.equal(isNoTradeTime(cdt('2026-09-29T10:00:00'), o), false); // the day session is untouched // No delay: 5:45 PM — not 5:05, while Tradovate rolls the accounts' days. const now = { ...o, jitterMin: 0 }; assert.equal(isNoTradeTime(cdt('2026-09-28T17:05:00'), now), true); assert.equal(isNoTradeTime(cdt('2026-09-28T17:44:00'), now), true); assert.equal(isNoTradeTime(cdt('2026-09-28T17:45:00'), now), false); // Every-day firms keep their own 5:05 PM end of the roll guard. assert.equal(isNoTradeTime(cdt('2026-09-28T17:05:00'), { ...now, everyDay: true }), false); }); test('every-day firms close only around the roll (CDT)', () => { const o = { everyDay: true, mode: 'full_cme', jitterMin: 7 }; assert.equal(isNoTradeTime(cdt('2026-09-26T12:00:00'), o), false); // Saturday noon assert.equal(isNoTradeTime(cdt('2026-09-27T09:00:00'), o), false); // Sunday morning assert.equal(isNoTradeTime(cdt('2026-09-28T16:54:00'), o), false); assert.equal(isNoTradeTime(cdt('2026-09-28T16:55:00'), o), true); assert.equal(isNoTradeTime(cdt('2026-09-28T17:11:00'), o), true); assert.equal(isNoTradeTime(cdt('2026-09-28T17:12:00'), o), false); assert.equal(isPastSessionClose(cdt('2026-09-26T16:56:00'), true), true); assert.equal(isPastSessionClose(cdt('2026-09-26T17:01:00'), true), false); }); test('every-day firms close from 3:55 PM in CST, covering the 4 PM firm reset', () => { const o = { everyDay: true, mode: 'equity_hours', jitterMin: 1 }; assert.equal(isNoTradeTime(cst('2027-01-12T15:54:00'), o), false); assert.equal(isNoTradeTime(cst('2027-01-12T15:55:00'), o), true); assert.equal(isNoTradeTime(cst('2027-01-12T16:30:00'), o), true); assert.equal(isNoTradeTime(cst('2027-01-12T17:05:00'), o), true); assert.equal(isNoTradeTime(cst('2027-01-12T17:06:00'), o), false); assert.equal(isNoTradeTime(cst('2027-01-16T12:00:00'), o), false); // Saturday assert.equal(isPastSessionClose(cst('2027-01-12T16:10:00'), true), true); }); // ── fees ───────────────────────────────────────────────────────────────────── test('percentFee rounds each side up to the cent', () => { assert.equal(percentFee(0.04, 60000, 1, 1), 24); assert.equal(percentFee(0.04, 100000, 0.2, 1), 8); assert.equal(percentFee(0.04, 123.45, 0.001, 7), 0.01); // 0.00035 → a cent assert.equal(percentFee(0, 60000, 1, 1), 0); }); test('percent-fee take-profits net at least the leg, and one tick less does not', () => { const r = rng(3); for (let i = 0; i < 3000; i++) { const side = r() < 0.5 ? 'Buy' : 'Sell'; const tick = [0.01, 0.1, 0.5, 1][Math.floor(r() * 4)]; const entry = Math.round((50 + r() * 100000) / tick) * tick; const step = [0.001, 0.01, 1][Math.floor(r() * 3)]; const contracts = 1 + Math.floor(r() * 400); const pct = [0.04, 0.02, 0.1][Math.floor(r() * 3)]; const leg = Math.round(5 + r() * 2000); const entryFee = percentFee(pct, entry, step, contracts); const tp = takeProfitPlan({ side, entry, leg, pointValue: step, contracts, tickSize: tick, commissionPct: pct, entryCommission: entryFee }); const k = step * contracts; const dir = side === 'Buy' ? 1 : -1; const net = (ticks) => { const x = entry + dir * ticks * tick; return ticks * tick * k - entryFee - percentFee(pct, x, step, contracts); }; assert.ok(net(tp.ticks) >= leg - 1e-6, `short of the leg: ${JSON.stringify({ side, entry, tick, step, contracts, pct, leg, tp })}`); if (tp.ticks > 1) assert.ok(net(tp.ticks - 1) < leg + 0.02, `a tick further than needed: ${JSON.stringify(tp)}`); assert.equal(tp.totalCommission, Math.round((entryFee + tp.exitFee) * 100) / 100); } }); // ── daily loss limit ───────────────────────────────────────────────────────── test('dllRoom: snapshot, fallback, green-day cap, no limit', () => { assert.equal(dllRoom({ dailyLossLimit: 0, closedBalance: 50000, realizedToday: -900 }), Infinity); assert.equal(dllRoom({ dailyLossLimit: 1500, closedBalance: 49600, dayStartBalance: 50000, realizedToday: 0 }), 1100); assert.equal(dllRoom({ dailyLossLimit: 1500, closedBalance: 50600, dayStartBalance: 50000, realizedToday: 0 }), 1500); assert.equal(dllRoom({ dailyLossLimit: 1000, closedBalance: 50000, dayStartBalance: null, realizedToday: -200 }), 800); assert.equal(dllRoom({ dailyLossLimit: 1000, closedBalance: 50000, realizedToday: 350 }), 1000); }); test('the worked futures example: $1,000 limit, down $200, NQ at $2.14/side', () => { const room = dllRoom({ dailyLossLimit: 1000, closedBalance: 49800, realizedToday: -200 }); const d = dllStopDollars({ room, dailyLossLimit: 1000, entryFee: 2.14, commissionPct: 0 }); const ticks = stopTicks({ obfuscationDollars: Infinity, dllDollars: d, pointValue: 20, contracts: 1, tickSize: 0.25 }); assert.equal(ticks, 139); const loss = ticks * 0.25 * 20 + 2 * 2.14; assert.ok(-200 - loss > -1000 + 100 - 1e-9); }); test('stopTicks: obfuscation rounds out, the daily-loss stop rounds in, the tighter wins', () => { const base = { pointValue: 20, contracts: 1, tickSize: 0.25 }; assert.equal(stopTicks({ ...base, obfuscationDollars: Infinity, dllDollars: Infinity }), null); assert.equal(stopTicks({ ...base, obfuscationDollars: 0, dllDollars: Infinity }), null); assert.equal(stopTicks({ ...base, obfuscationDollars: 1001, dllDollars: Infinity }), 201); assert.equal(stopTicks({ ...base, obfuscationDollars: Infinity, dllDollars: 1000 }), 200); assert.equal(stopTicks({ ...base, obfuscationDollars: Infinity, dllDollars: 999.99 }), 199); assert.equal(stopTicks({ ...base, obfuscationDollars: 600, dllDollars: 1000 }), 120); assert.equal(stopTicks({ ...base, obfuscationDollars: 1600, dllDollars: 1000 }), 200); assert.equal(stopTicks({ ...base, obfuscationDollars: Infinity, dllDollars: 3 }), 0); assert.equal(stopTicks({ ...base, obfuscationDollars: Infinity, dllDollars: -40 }), 0); }); test('worst case at the stop, both fees paid, never spends past room − reserve', () => { const r = rng(4); for (let i = 0; i < 20000; i++) { const side = r() < 0.5 ? 'Buy' : 'Sell'; const pctFirm = r() < 0.6; const pct = pctFirm ? [0.04, 0.1][Math.floor(r() * 2)] : 0; const tick = pctFirm ? [0.01, 0.1, 1][Math.floor(r() * 3)] : 0.25; const pv = pctFirm ? [0.001, 0.01, 1][Math.floor(r() * 3)] : [2, 20, 50][Math.floor(r() * 3)]; const contracts = pctFirm ? 1 + Math.floor(r() * 500) : 1 + Math.floor(r() * 3); const entry = Math.round((100 + r() * 90000) / tick) * tick; const dll = [300, 750, 1500, 3000][Math.floor(r() * 4)]; const room = dllRoom({ dailyLossLimit: dll, closedBalance: 50000, dayStartBalance: 50000 + Math.round((r() - 0.3) * dll), realizedToday: 0 }); const entryFee = pctFirm ? percentFee(pct, entry, pv, contracts) : Math.round(r() * 500) / 100 * contracts; const d = dllStopDollars({ room, dailyLossLimit: dll, entryFee, commissionPct: pct }); const ticks = stopTicks({ obfuscationDollars: Infinity, dllDollars: d, pointValue: pv, contracts, tickSize: tick }); if (ticks === 0) continue; // flattened instead const stopPrice = side === 'Buy' ? entry - ticks * tick : entry + ticks * tick; const exitFee = pctFirm ? percentFee(pct, stopPrice, pv, contracts) : entryFee; const loss = ticks * tick * pv * contracts + entryFee + exitFee; assert.ok(loss <= room - DLL_RESERVE_FRACTION * dll + 1e-6, JSON.stringify({ side, pct, tick, pv, contracts, entry, dll, room, ticks, loss })); } }); test('dllViable and botStopsActive', () => { assert.equal(dllViable(Infinity, 0, 0), true); assert.equal(dllViable(1500, 1500, 20), true); assert.equal(dllViable(150 + DLL_MIN_STOP + 20, 1500, 20), true); assert.equal(dllViable(150 + DLL_MIN_STOP + 19.99, 1500, 20), false); assert.equal(botStopsActive({ firstDayStop: false, minDayStop: false }), false); assert.equal(botStopsActive({ firstDayStop: false, minDayStop: false, dailyLossLimit: 1500 }), true); assert.equal(botStopsActive({ firstDayStop: true, minDayStop: false, dailyLossLimit: 0 }), true); }); // ── sizing ─────────────────────────────────────────────────────────────────── const btcSigma = 0.03 / Math.sqrt(24); // 3%/day test('volatility sizing hits the target hold, and the example table', () => { const stop = () => 1350; const at = (hours) => sizeNotional({ leg: 600, stopDollarsAt: stop, volatilityPerHour: btcSigma, targetHoldHours: hours, commissionPct: 0, maxNotional: 0 }); for (const [hours, notional] of [[54, 20000], [24, 30000], [6, 60000]]) { const s = at(hours); assert.ok(Math.abs(s.notional - notional) / notional < 0.001, `${hours}h → ${s.notional}`); assert.ok(Math.abs(s.predictedHoldHours - hours) / hours < 0.001); assert.ok(Math.abs(s.winProbability - 1350 / 1950) < 1e-9); } }); test('sizing: fees feed back, the cap binds, financing only raises N, defaults', () => { const base = { leg: 600, volatilityPerHour: btcSigma, targetHoldHours: 6, commissionPct: 0.04, spreadFraction: 0.0002, maxNotional: 0 }; // Daily-loss stop shrinks as fees grow with N. const stopDollarsAt = (n) => dllStopDollars({ room: 1500, dailyLossLimit: 1500, entryFee: 0.0004 * n, commissionPct: 0.04 }); const s = sizeNotional({ ...base, stopDollarsAt }); const a = 600 + 2 * 0.0004 * s.notional; const b = stopDollarsAt(s.notional); assert.ok(Math.abs(s.predictedHoldHours - 6) < 0.05, `hold ${s.predictedHoldHours}`); assert.ok(Math.abs(s.winProbability - b / (a + b)) < 1e-9); const capped = sizeNotional({ ...base, stopDollarsAt, maxNotional: 25000 }); assert.equal(capped.notional, 25000); assert.ok(capped.predictedHoldHours > 6); const financed = sizeNotional({ ...base, stopDollarsAt, targetHoldHours: 200, financingPctPerDay: 0.033 }); const unfinanced = sizeNotional({ ...base, stopDollarsAt, targetHoldHours: 200 }); assert.ok(financed.notional > unfinanced.notional); // No target hold = 12 h. const dflt = sizeNotional({ ...base, targetHoldHours: 0, stopDollarsAt }); assert.ok(Math.abs(dflt.predictedHoldHours - DEFAULT_TARGET_HOLD_HOURS) < 0.1, `hold ${dflt.predictedHoldHours}`); // A stopless leg is sized as if the stop sat as far as the target. const stopless = sizeNotional({ ...base, stopDollarsAt: () => Infinity }); assert.ok(Math.abs(stopless.predictedHoldHours - 6) < 0.05); assert.equal(stopless.winProbability, null); // No volatility, no size. assert.equal(sizeNotional({ ...base, volatilityPerHour: undefined, stopDollarsAt }), null); }); test('random-walk simulation agrees with the predicted hold and win rate', () => { const r = rng(5); const notional = 60000; const A = 600; const B = 1350; const sigma = btcSigma; const dtHours = 1 / 60; // one-minute steps const stepDollars = notional * sigma * Math.sqrt(dtHours); const predicted = (A / notional) * (B / notional) / (sigma * sigma); let wins = 0; let totalHours = 0; let winHours = 0; const paths = 3000; for (let p = 0; p < paths; p++) { let x = 0; let steps = 0; while (x < A && x > -B) { x += r() < 0.5 ? stepDollars : -stepDollars; steps++; } if (x >= A) { wins++; winHours += steps * dtHours; } totalHours += steps * dtHours; } const meanHold = totalHours / paths; assert.ok(Math.abs(meanHold - predicted) / predicted < 0.1, `mean hold ${meanHold} vs predicted ${predicted}`); assert.ok(Math.abs(wins / paths - B / (A + B)) < 0.03, `win rate ${wins / paths}`); // Winners take longer than the average leg: (A² + 2AB) / 3σ$² — what // expectedSwapToWin charges swap over. const predictedWin = (A * A + 2 * A * B) / (3 * (notional * sigma) ** 2); assert.ok(Math.abs(winHours / wins - predictedWin) / predictedWin < 0.1, `winners ${winHours / wins}h vs ${predictedWin}h`); }); test('expected swap to win: a fixed point over the winners\' hold, zero without swap, capped', () => { const sigma = 0.022 / Math.sqrt(24); // ETH over a week, ~2.2%/day const o = { distance: 12400, stopDollars: 2300, notional: 200000, volatilityPerHour: sigma, financingPctPerDay: 0.033 }; const swap = expectedSwapToWin(o); const a = o.distance + swap; const b = o.stopDollars; const winHours = (a * a + 2 * a * b) / (3 * (o.notional * sigma) ** 2); assert.ok(Math.abs(swap - 0.00033 * o.notional * (winHours / 24)) < 0.02, `swap ${swap} over ${winHours}h`); assert.ok(swap > 200 && swap < 400, `swap ${swap}`); // Quieter means a longer hold and more swap; none without a rate or σ. assert.ok(expectedSwapToWin({ ...o, volatilityPerHour: sigma * 0.6 }) > swap); assert.equal(expectedSwapToWin({ ...o, financingPctPerDay: 0 }), 0); assert.equal(expectedSwapToWin({ ...o, volatilityPerHour: undefined }), 0); // Stopless reads as B = A: a longer walk than with the stop. assert.ok(expectedSwapToWin({ ...o, stopDollars: Infinity }) > swap); // A leg that can barely move is capped at a quarter of its distance. assert.equal(expectedSwapToWin({ ...o, volatilityPerHour: sigma / 50 }), 3100); }); test('the notional cap: what the available funds can margin at the symbol\'s rate', () => { assert.equal(notionalCap({ marginRate: 0.2, availableFunds: 100000 }), MARGIN_HEADROOM * 500000); assert.equal(notionalCap({ marginRate: 0.5, availableFunds: 99757.39 }), MARGIN_HEADROOM * 99757.39 * 2); assert.equal(notionalCap({ marginRate: 0.2, availableFunds: undefined }), 0); assert.equal(notionalCap({ availableFunds: 100000 }), 0); }); test('the daily-loss reserve grows with the position past 10% of the limit', () => { assert.equal(dllReserve(3000), 300); assert.equal(dllReserve(3000, 200000), 300); assert.equal(dllReserve(3000, 485000), 485000 * DLL_SLIPPAGE_FRACTION); const at = (notional) => dllStopDollars({ room: 3000, dailyLossLimit: 3000, entryFee: 0.0004 * notional, commissionPct: 0.04, notional }); // Below $300k the flat 10% binds; above, 0.1% of notional does. assert.ok(Math.abs(at(200000) - (3000 - 300 - 160 - 0.01) / 1.0004) < 1e-9); assert.ok(Math.abs(at(485000) - (3000 - 485 - 388 - 0.01) / 1.0004) < 1e-9); // No notional given: the old flat reserve. assert.equal(dllStopDollars({ room: 3000, dailyLossLimit: 3000, entryFee: 0, commissionPct: 0 }), 2699.99); }); test('a platform whose stops fill closer reserves its own share of the position', () => { // Me's Vest eval on 2026-10-27: $1,400 of room on $495,439 of NDX. At the // 0.1% default the reserve is $495.44; at 0.02% the flat $140 binds. const n = 495439; assert.ok(Math.abs(dllReserve(1400, n) - 495.439) < 1e-9); assert.equal(dllReserve(1400, n, 0.0002), 140); assert.ok(Math.abs(dllReserve(1400, 900000, 0.0002) - 180) < 1e-9, 'past $700k the share binds again'); // Left out, or not a share at all: the default. for (const bad of [undefined, NaN, -0.0002, Infinity]) assert.equal(dllReserve(1400, n, bad), dllReserve(1400, n), String(bad)); assert.equal(dllReserve(1400, n, 0), 140, 'no slippage at all still keeps the flat 10%'); const stop = (slippageFraction) => dllStopDollars({ room: 1400, dailyLossLimit: 1400, entryFee: 12.45, commissionPct: 0, notional: n, slippageFraction }); assert.ok(Math.abs(stop(undefined) - (1400 - 495.439 - 24.9 - 0.01)) < 1e-9); assert.ok(Math.abs(stop(0.0002) - (1400 - 140 - 24.9 - 0.01)) < 1e-9); }); test('max-drawdown room: the static floor, the broker floor, the higher', () => { assert.equal(maxLossRoom({ balance: 99757.39, accountSize: 100000, maxLoss: 6000 }), 5757.39); assert.equal(maxLossRoom({ balance: 99757.39, accountSize: 100000, maxLoss: -6000 }), 5757.39); assert.equal(maxLossRoom({ balance: 51000, accountSize: 50000, maxLoss: 0, autoLiqThreshold: 48500 }), 2500); assert.equal(maxLossRoom({ balance: 51000, accountSize: 50000, maxLoss: 2000, autoLiqThreshold: 49000 }), 2000); assert.equal(maxLossRoom({ balance: 99000, accountSize: 100000, maxLoss: 0 }), null); }); test('last stand: no daily-loss stop once hitting the daily limit would leave 15% of it or less', () => { // A full day's $3,000 of room. const at = (maxRoom) => lastStand({ dllRoom: 3000, dailyLossLimit: 3000, maxLossRoom: maxRoom }); assert.equal(LAST_STAND_FRACTION, 0.15); assert.equal(at(5757.39), false, 'a fresh 1-Step keeps its stop'); assert.equal(at(3000 * 1.15 + 1), false, 'just over 15% left: keep the stop'); assert.equal(at(3000 * 1.15), true, '15% left: last stand'); assert.equal(at(3294.70), true, 'one stopped day in: $294.70 left, 9.8% — last stand'); assert.equal(at(800), true, 'floor nearer than the limit: last stand'); assert.equal(at(null), false, 'no floor known'); // Measured against what's left of the day: $1,000 of room, $1,200 to the floor. assert.equal(lastStand({ dllRoom: 1000, dailyLossLimit: 3000, maxLossRoom: 1200 }), false); assert.equal(lastStand({ dllRoom: 1000, dailyLossLimit: 3000, maxLossRoom: 1150 }), true); assert.equal(lastStand({ dllRoom: Infinity, dailyLossLimit: 0, maxLossRoom: 500 }), false, 'no daily limit, no daily stop to drop'); // A green day's widened room is measured against a full day's limit: a // $1,400 eval starting $1,700 over its floor, up $1,000 — leg 2's $2,400 // stop-out leaves $300, over 15% of $1,400, so it keeps its stop. assert.equal(lastStand({ dllRoom: 2400, dailyLossLimit: 1400, maxLossRoom: 2700 }), false); assert.equal(lastStand({ dllRoom: 2400, dailyLossLimit: 1400, maxLossRoom: 2610 }), true); // The room past the floor is a last stand however it was widened. assert.equal(lastStand({ dllRoom: 2400, dailyLossLimit: 1400, maxLossRoom: 2300 }), true); // The far side of the walk is the nearer floor, net of the entry fee. assert.equal(breachDistance({ dllRoom: 3000, maxLossRoom: 2900, entryFee: 120 }), 2780); assert.equal(breachDistance({ dllRoom: 2757.39, maxLossRoom: null, entryFee: 0 }), 2757.39); }); test('planLegSize: the stop, a last stand, the margin cap and the cost, as fireLeg trades', () => { const eth = { stepNotional: 26.86, volatilityPerHour: 0.022 / Math.sqrt(24), spreadFraction: 0.00004, marginRate: 0.2 }; const base = { leg: 12239, contract: eth, commissionPct: 0.04, targetHoldHours: 12, financingPctPerDay: 0.033, dailyLossLimit: 3000, dllRoom: 2757.39, obfuscationDollars: Infinity, availableFunds: 99757.39, }; const fresh = planLegSize({ ...base, maxLossRoom: 5757.39 }); assert.equal(fresh.lastStand, false); assert.ok(Math.abs(fresh.sizing.predictedHoldHours - 12) < 0.1, `hold ${fresh.sizing.predictedHoldHours}`); const n = fresh.sizing.notional; assert.ok(Math.abs(fresh.walkAt(n) - dllStopDollars({ room: 2757.39, dailyLossLimit: 3000, entryFee: 0.0004 * n, commissionPct: 0.04, notional: n })) < 1e-9); const expectCost = n * (0.00004 + 0.0008) + 0.00033 * n * (fresh.sizing.predictedHoldHours / 24); assert.ok(Math.abs(fresh.cost - expectCost) < 1e-6); // Two days stopped out: a stop-out would leave ~$300 above the floor. const stand = planLegSize({ ...base, dllRoom: 3000, maxLossRoom: 3000 }); assert.equal(stand.lastStand, true); assert.equal(stand.walkAt(300000), 3000 - 120); assert.ok(stand.sizing.winProbability > fresh.sizing.winProbability, 'the whole room, no reserve: a better chance'); // An obfuscation stop still counts on a last stand. assert.equal(planLegSize({ ...base, dllRoom: 3000, maxLossRoom: 3000, obfuscationDollars: 1000 }).walkAt(300000), 1000); // A 2:1 stock can't reach 12 h in a quiet market: the cap binds. const stock = planLegSize({ ...base, maxLossRoom: 5757.39, contract: { ...eth, marginRate: 0.5, volatilityPerHour: 0.0076 / Math.sqrt(24) } }); assert.ok(Math.abs(stock.sizing.notional - stock.cap) < 1e-6); assert.ok(stock.sizing.predictedHoldHours > 12); // Futures (no stepNotional) aren't sized by notional. assert.equal(planLegSize({ ...base, maxLossRoom: null, contract: {} }).sizing, null); }); test('resetClearStop: the widest stop a leg may carry and still close before the reset', () => { const h = 3_600_000; const near = (a, b) => Math.abs(a - b) < 1e-6; // Exactly four holds off: a 2:1 leg, no more. assert.ok(near(resetClearStop({ leg: 2395.76, resetInMs: 6 * h, holdHours: 1.5 }), 2 * 2395.76)); // Under four holds (the 5:30 PM CT open batch, 1.5 h before Vest's // reset): no widening at all. assert.equal(resetClearStop({ leg: 2395.76, resetInMs: 1.5 * h, holdHours: 1.5 }), 0); assert.equal(resetClearStop({ leg: 2395.76, resetInMs: 6 * h - 1, holdHours: 1.5 }), 0); // Dingo's second leg at ~4 AM ET, 16 h off: far past the $4,004 the day allows. assert.ok(resetClearStop({ leg: 2395.76, resetInMs: 16 * h, holdHours: 1.5 }) > 20000); // A $500 final leg 10 h off may carry about $2,656, not the $5,900 a // $4,500 gain would give: g = (A+B)²/(AB) stays at 4.5 × 10/6. const lopsided = resetClearStop({ leg: 500, resetInMs: 10 * h, holdHours: 1.5 }); assert.ok(Math.abs(lopsided - 2656) < 1, `${lopsided}`); assert.ok(near((500 + lopsided) ** 2 / (500 * lopsided), 4.5 * 10 / 6)); assert.equal(resetClearStop({ leg: 0, resetInMs: 16 * h, holdHours: 1.5 }), 0); assert.equal(resetClearStop({ leg: 500, resetInMs: 16 * h, holdHours: 0 }), 0); assert.equal(resetClearStop({ leg: 500, resetInMs: NaN, holdHours: 1.5 }), 0); // The cap is held to the shortest leg the day may carve: a split day's // $2,395.76 can come out a $500 leg. assert.equal(shortestNextLeg({ remainingToday: 2395.76, splitAllowed: true }), 500); assert.equal(shortestNextLeg({ remainingToday: 320, splitAllowed: true }), 320); assert.equal(shortestNextLeg({ remainingToday: 2395.76, splitAllowed: false }), 2395.76); // Dingo at ~4 AM ET still gets the whole $2,604.24: a $500 leg 16 h off // may carry ~$4,950. assert.ok(resetClearStop({ leg: 500, resetInMs: 16 * h, holdHours: 1.5 }) - 1400 > 2604.24); }); test('clearsClose: no leg starts under four holds of its market\'s close', () => { const h = 3_600_000; assert.equal(RESET_CLEAR_HOLDS, 4); // The weekly close 7.5 h off, legs sized for 2 h: no entry. assert.equal(clearsClose({ closeInMs: 7.5 * h, holdHours: 2 }), false); // Exactly four holds off, or more: the clearance the daily reset gets. assert.equal(clearsClose({ closeInMs: 8 * h, holdHours: 2 }), true); assert.equal(clearsClose({ closeInMs: 8 * h - 1, holdHours: 2 }), false); assert.equal(clearsClose({ closeInMs: 20 * h, holdHours: 2 }), true); // A leg its leverage cap holds to 3.5 h needs 14 h, where a 2 h one is clear. assert.equal(clearsClose({ closeInMs: 10 * h, holdHours: 3.5 }), false); // It agrees with resetClearStop, which gives no stop at all under four holds. for (const off of [5.9, 6, 6.1, 12]) { assert.equal(clearsClose({ closeInMs: off * h, holdHours: 1.5 }), resetClearStop({ leg: 500, resetInMs: off * h, holdHours: 1.5 }) > 0); } // An unknown hold never clears. assert.equal(clearsClose({ closeInMs: 20 * h, holdHours: NaN }), false); }); test('planLegSize: a split day\'s next leg, with the day\'s gain on its stop, sizes up for the same hold', () => { // Dingo's 2026-10-27 evals: up $2,604.24 on a $1,400 day, $2,395.76 to // pass, NDX at ~$29.5k on Vest (1.31%/day), sized for 1.5 h. const ndx = { stepNotional: 2.95, volatilityPerHour: 0.0131 / Math.sqrt(24), spreadFraction: 0.0000085, marginRate: 0.02 }; const leg = { leg: 2395.76, contract: ndx, commissionPct: 0.0025, targetHoldHours: 1.5, financingPctPerDay: 0, dailyLossLimit: 1400, obfuscationDollars: Infinity, availableFunds: 52604.24, maxLossRoom: 5604.24, }; const capped = planLegSize({ ...leg, dllRoom: 1400 }); const room = floorRoom({ dailyLossLimit: 1400, closedBalance: 52604.24, floor: 48500, realizedToday: 2604.24, gain: 2604.24 }); const widened = planLegSize({ ...leg, dllRoom: room }); assert.equal(widened.lastStand, false); for (const p of [capped, widened]) assert.ok(Math.abs(p.sizing.predictedHoldHours - 1.5) < 0.05, `hold ${p.sizing.predictedHoldHours}`); assert.ok(widened.sizing.notional > 1.5 * capped.sizing.notional, `${widened.sizing.notional} vs ${capped.sizing.notional}`); // The stop gives back the day's gain plus the limit, less the reserve. const n = widened.sizing.notional; assert.ok(widened.walkAt(n) > 2604.24 && widened.walkAt(n) < 4004.24, `stop ${widened.walkAt(n)}`); }); test('planLegSize: a platform\'s own slippage share widens the stop and sizes the leg up for the same hold', () => { // Me's first-day Vest eval leg on 2026-10-27: $5,000 to pass, $1,400 of // room, NDX at ~$29.8k, sized for 2.6 h. const ndx = { stepNotional: 2.98, volatilityPerHour: 0.0131 / Math.sqrt(24), spreadFraction: 0.0000085, marginRate: 0.02 }; const leg = { leg: 5000, contract: ndx, commissionPct: 0.0025, targetHoldHours: 2.6, financingPctPerDay: 0, dailyLossLimit: 1400, dllRoom: 1400, obfuscationDollars: Infinity, availableFunds: 50000, maxLossRoom: 3000, }; const deflt = planLegSize(leg); const vest = planLegSize({ ...leg, slippageFraction: 0.0002 }); assert.equal(planLegSize({ ...leg, slippageFraction: undefined }).sizing.notional, deflt.sizing.notional); for (const p of [deflt, vest]) { assert.equal(p.lastStand, false); assert.ok(Math.abs(p.sizing.predictedHoldHours - 2.6) < 0.05, `hold ${p.sizing.predictedHoldHours}`); } const r = 0.0025 / 100; const nd = deflt.sizing.notional, nv = vest.sizing.notional; assert.ok(Math.abs(deflt.walkAt(nd) - (1400 - 0.001 * nd - 2 * r * nd - 0.01) / (1 + r)) < 1e-9); assert.ok(Math.abs(vest.walkAt(nv) - (1400 - 140 - 2 * r * nv - 0.01) / (1 + r)) < 1e-9, 'the flat $140 binds'); assert.ok(vest.walkAt(nv) > 1200 && deflt.walkAt(nd) < 950, `stops ${vest.walkAt(nv)} vs ${deflt.walkAt(nd)}`); assert.ok(nv > 1.1 * nd, `${nv} vs ${nd}`); assert.ok(vest.sizing.winProbability > deflt.sizing.winProbability); }); test('personSizeFactor: one draw per person and trade date, within the spread', () => { const people = Array.from({ length: 300 }, (_, i) => `0b6f${i.toString(16).padStart(4, '0')}-5c1e-4a7e-9d2b-${i}`); const draws = people.map((p) => personSizeFactor(p, '2026-10-20')); for (const f of draws) assert.ok(f >= 1 - PERSON_SIZE_SPREAD && f <= 1 + PERSON_SIZE_SPREAD, `factor ${f}`); // Spread over the whole range, not bunched at 1. assert.ok(Math.min(...draws) < 0.88 && Math.max(...draws) > 1.12, `${Math.min(...draws)}–${Math.max(...draws)}`); // Repeatable, so every sizing of the day agrees. assert.equal(personSizeFactor(people[0], '2026-10-20'), draws[0]); // Two people land within half a percent of each other about as often as // uniform draws do (2 × 0.005 / 0.3 ≈ 3%), not bunched together. const close = draws.filter((f, i) => i > 0 && Math.abs(f - draws[i - 1]) < 0.005).length; assert.ok(close < draws.length * 0.08, `${close} near-identical neighbours`); // A new trade date, a new draw: a month of one person's days covers the // range too, not a slow drift (the next date differs in its last digit). const days = Array.from({ length: 30 }, (_, i) => personSizeFactor(people[0], `2026-11-${String(i + 1).padStart(2, '0')}`)); assert.ok(Math.max(...days) - Math.min(...days) > PERSON_SIZE_SPREAD, `days span ${Math.min(...days)}–${Math.max(...days)}`); const steps = days.slice(1).map((f, i) => Math.abs(f - days[i])); assert.ok(steps.reduce((a, b) => a + b, 0) / steps.length > 0.05, 'day to day the draw should jump, not creep'); }); test('personHoldHours: the size comes out the person\'s factor times the plan, at the hold it was sized for', () => { const eth = { stepNotional: 26.86, volatilityPerHour: 0.022 / Math.sqrt(24), spreadFraction: 0.00004, marginRate: 0.2 }; const base = { leg: 12239, contract: eth, commissionPct: 0.04, targetHoldHours: 12, financingPctPerDay: 0.033, dailyLossLimit: 3000, dllRoom: 2757.39, maxLossRoom: 5757.39, obfuscationDollars: Infinity, availableFunds: 99757.39, }; const plan = planLegSize(base).sizing; for (const person of ['me-uuid', 'nik-uuid', 'ahmad-uuid', 'ziad-uuid']) { const factor = personSizeFactor(person, '2026-10-20'); const hold = personHoldHours(12, person, '2026-10-20'); const sized = planLegSize({ ...base, targetHoldHours: hold }).sizing; // Fees and the daily-loss stop move a little with the size, so the // ratio sits a touch nearer 1 than the factor. const ratio = sized.notional / plan.notional; assert.ok(Math.abs(ratio - factor) < 0.02, `${person}: ×${ratio.toFixed(4)} vs factor ${factor.toFixed(4)}`); assert.ok((ratio - 1) * (factor - 1) > 0, `${person}: moved the wrong way`); // The pick ranks against this hold, and the size reaches it. assert.ok(Math.abs(sized.predictedHoldHours - hold) < 0.1, `${person}: ${sized.predictedHoldHours}h vs ${hold}h`); } // No firm hold: the default, moved the same way. assert.equal(personHoldHours(0, 'me-uuid', '2026-10-20'), personHoldHours(DEFAULT_TARGET_HOLD_HOURS, 'me-uuid', '2026-10-20')); }); test('planLegSize: the broker\'s order size limits bind the sizing, so the pick sees the real hold', () => { const eth = { stepNotional: 26.86, volatilityPerHour: 0.022 / Math.sqrt(24), spreadFraction: 0.00004, marginRate: 0.2 }; const base = { leg: 12239, contract: eth, commissionPct: 0.04, targetHoldHours: 12, financingPctPerDay: 0.033, dailyLossLimit: 3000, dllRoom: 2757.39, maxLossRoom: 5757.39, obfuscationDollars: Infinity, availableFunds: 99757.39, }; const free = planLegSize(base); // A largest order well under the ideal size: the notional stops there and // the hold grows past the target (the margin cap stays what it was). const capped = planLegSize({ ...base, contract: { ...eth, maxContracts: 5000 } }); assert.ok(Math.abs(capped.sizing.notional - 5000 * 26.86) < 1e-6, `notional ${capped.sizing.notional}`); assert.ok(capped.sizing.notional < free.sizing.notional); assert.ok(capped.sizing.predictedHoldHours > 12, `hold ${capped.sizing.predictedHoldHours}`); assert.equal(capped.cap, free.cap); // One that doesn't bind changes nothing. assert.equal(planLegSize({ ...base, contract: { ...eth, maxContracts: 1e9 } }).sizing.notional, free.sizing.notional); // A smallest order above the ideal size: the order is that big. const floored = planLegSize({ ...base, contract: { ...eth, minContracts: 20000 } }); assert.ok(Math.abs(floored.sizing.notional - 20000 * 26.86) < 1e-6); assert.ok(floored.sizing.predictedHoldHours < 12); // Ranked on the capped hold, a capped instrument no longer wins on cost. const order = rankForTarget([ { holdHours: capped.sizing.predictedHoldHours, cost: capped.cost }, { holdHours: free.sizing.predictedHoldHours, cost: free.cost + 50 }, ], 12); assert.deepEqual(order, [1, 0]); }); test('rankForTarget: reach the target hold first, cheapest; else quickest', () => { const order = rankForTarget([ { holdHours: 12, cost: 330 }, // ETH { holdHours: 12.1, cost: 300 }, // BTC — within the 2% tolerance { holdHours: 40, cost: 250 }, // XYZ100, capped at 2:1 { holdHours: 20, cost: 400 }, // SP500, capped ], 12); assert.deepEqual(order, [1, 0, 3, 2]); assert.deepEqual(rankForTarget([{ holdHours: 30, cost: 1 }, { holdHours: 25, cost: 9 }], 12), [1, 0]); assert.deepEqual(rankForTarget([], 12), []); }); test('sizing carries the swap reserve into the take-profit distance', () => { const sigma = 0.0124 / Math.sqrt(24); const stopDollarsAt = (n) => dllStopDollars({ room: 2760.15, dailyLossLimit: 3000, entryFee: 0.0004 * n, commissionPct: 0.04 }); const base = { leg: 12239, stopDollarsAt, volatilityPerHour: sigma, targetHoldHours: 12, commissionPct: 0.04, spreadFraction: 0.0001 }; const withSwap = sizeNotional({ ...base, financingPctPerDay: 0.033, maxNotional: 200000 }); const noSwap = sizeNotional({ ...base, financingPctPerDay: 0, maxNotional: 200000 }); assert.equal(noSwap.swapReserve, 0); assert.ok(withSwap.swapReserve > 500, `swap ${withSwap.swapReserve}`); const a = 12239 + 2 * 0.0004 * 200000 + withSwap.swapReserve; const b = stopDollarsAt(200000); assert.ok(Math.abs(withSwap.predictedHoldHours - (a / 200000) * (b / 200000) / (sigma * sigma)) < 1e-6); assert.ok(withSwap.predictedHoldHours > noSwap.predictedHoldHours); assert.ok(withSwap.winProbability < noSwap.winProbability); }); // ── every-day calendar ─────────────────────────────────────────────────────── test('nextTradingDate and effectiveCombine', () => { assert.equal(nextTradingDate('2026-09-25'), '2026-09-28'); assert.equal(nextTradingDate('2026-09-25', true), '2026-09-26'); assert.equal(nextTradingDate('2026-12-24', true), '2026-12-25'); assert.equal(effectiveCombine({ combineCmeHolidays: true }), true); assert.equal(effectiveCombine({ combineCmeHolidays: true, tradesEveryDay: true }), false); }); const projCfg = { profitTarget: 3000, consistency: 0, minDayPnL: 0, minTradingDays: 5, accountSize: 50000, targetSameEquity: false, withdrawalStages: [], splitEnabled: true, }; test('every-day projections use consecutive dates; CME ones still skip weekends', () => { const now = cdt('2026-09-26T12:00:00'); // Saturday noon → trade date Saturday const common = { accountId: 7, cycleDailyPnL: [], fundTransactions: [], balance: 50000, realizedPnL: 0, now }; const every = projectPerfectPath({ ...common, cfg: { ...projCfg, tradesEveryDay: true } }); const cme = projectPerfectPath({ ...common, cfg: projCfg }); assert.ok(every.length > 1); for (let i = 1; i < every.length; i++) assert.equal(every[i].date, nextTradingDate(every[i - 1].date, true)); for (const d of cme) { const dow = new Date(`${d.date}T00:00:00Z`).getUTCDay(); assert.ok(dow !== 0 && dow !== 6, `CME projection landed on ${d.date}`); } // Saturday itself is booked (silently) for the every-day firm: its first // projected day is Sunday, the CME one's is Monday. assert.equal(every[0].date, '2026-09-27'); assert.equal(cme[0].date, '2026-09-28'); }); test('an every-day firm can finish its cycle on a Saturday', () => { const now = cdt('2026-09-26T12:00:00'); const cycleDailyPnL = [ { date: '2026-09-21', pnl: 700 }, { date: '2026-09-22', pnl: 700 }, { date: '2026-09-23', pnl: 700 }, { date: '2026-09-24', pnl: 700 }, ]; const opts = { accountId: 7, rawTradeDate: '2026-09-26', cycleDailyPnL, rawRealizedPnL: 400, combineCmeHolidays: false, effective: { profitTarget: 3000, consistency: 0, minTradingDays: 5, minDayPnL: 0, drain: false }, equityProfit: 3200, splitEnabled: true, now, }; assert.equal(cycleCompletion({ ...opts, tradesEveryDay: true }), 'today'); assert.equal(cycleCompletion(opts), 'none'); }); // ── DXTrade helpers (lib/dxtrade-helpers.ts) ───────────────────────────────── import { dxAccountId, accountDisplayName, accountCodeOf, encodeContractName, decodeContractName, roundToStep, lastUtcReset, ctTradeDayStart, firmDayOf, parseDurationMs, ciNum, signedFundValue, inTradingHours, realizedVolPerHour, scoreCandidate, candidateRejection, MIN_VOL_CANDLES, parseTransfers, marginRateOf, } from '../lib/dxtrade-helpers.ts'; test('transfers: a close\'s SETTLEMENT and COMMISSION share a code and both count', () => { // As Tradeify247 answered for a flatten. const body = { cashTransfers: [ { account: 'default:1S100K2166398', transferCode: '2354051:1417097', transactionTime: '2026-09-26T22:33:39.615Z', cashTransactions: [ { account: 'default:1S100K2166398', transactionCode: '2354051:1417097', type: 'COMMISSION', value: -79.95, transactionTime: '2026-09-26T22:33:39.615Z', orderCode: 'x:af-1-e' }, ] }, { account: 'default:1S100K2166398', transferCode: '2354051:1417102', transactionTime: '2026-09-26T22:33:42.197Z', cashTransactions: [ { account: 'default:1S100K2166398', transactionCode: '2354051:1417102', type: 'SETTLEMENT', value: -5.43, transactionTime: '2026-09-26T22:33:42.197Z', orderCode: 'x:af-2-x' }, { account: 'default:1S100K2166398', transactionCode: '2354051:1417102', type: 'COMMISSION', value: -79.95, transactionTime: '2026-09-26T22:33:42.197Z', orderCode: 'x:af-2-x' }, ] }, ] }; const txns = parseTransfers(body); assert.equal(txns.length, 3); const byKey = new Map(txns.map((t) => [t.key, t])); assert.equal(byKey.size, 3, 'every transaction keeps its own key'); assert.equal(Math.round([...byKey.values()].reduce((s, t) => s + t.value, 0) * 100) / 100, -165.33); // The same answer again (the next sync, another page) keys identically. assert.deepEqual(parseTransfers(body).map((t) => t.key), txns.map((t) => t.key)); // Two of one type in one transfer stay apart too. const twice = { cashTransfers: [{ transferCode: 't', cashTransactions: [ { account: 'default:A', transactionCode: 'c', type: 'COMMISSION', value: -1, transactionTime: '2026-09-26T00:00:00Z' }, { account: 'default:A', transactionCode: 'c', type: 'COMMISSION', value: -2, transactionTime: '2026-09-26T00:00:00Z' }, ] }] }; assert.equal(new Set(parseTransfers(twice).map((t) => t.key)).size, 2); }); test('margin rates: flat and tiered, the steepest tier, junk ignored', () => { assert.equal(marginRateOf({ marginRate: { rateType: 'FLAT', tiers: [{ endVolume: 'Infinity', value: 0.2 }] } }), 0.2); assert.equal(marginRateOf({ marginRate: { rateType: 'FLAT', tiers: [{ endVolume: 'Infinity', value: 0.5 }] } }), 0.5); assert.equal(marginRateOf({ marginRate: { tiers: [{ endVolume: 1e6, value: 0.1 }, { endVolume: 'Infinity', value: 0.25 }] } }), 0.25); assert.equal(marginRateOf({ marginRate: { value: 0.05 } }), 0.05); assert.equal(marginRateOf({}), undefined); assert.equal(marginRateOf(undefined), undefined); assert.equal(marginRateOf({ marginRate: { tiers: [{ value: 'x' }, { value: 0 }, { value: 3 }] } }), undefined); }); test('dxAccountId is stable, safe, far above broker ids, and host-scoped', () => { const a = dxAccountId('dx.tradeify247.co', 'default:TFY-1'); assert.equal(a, dxAccountId('DX.Tradeify247.co', 'default:TFY-1')); assert.ok(Number.isSafeInteger(a) && a >= 2 ** 50 && a < 2 ** 53); assert.notEqual(a, dxAccountId('dx.other.co', 'default:TFY-1')); const seen = new Set(); for (let i = 0; i < 20000; i++) seen.add(dxAccountId('dx.tradeify247.co', `default:${i}`)); assert.equal(seen.size, 20000); }); test('account codes and contract names', () => { assert.equal(accountDisplayName('default:TFY-50K-12'), 'TFY-50K-12'); assert.equal(accountDisplayName('plain'), 'plain'); assert.equal(accountCodeOf({ clearing: 'default', account: 'x1' }), 'default:x1'); assert.equal(accountCodeOf('default:x1'), 'default:x1'); assert.deepEqual(decodeContractName(encodeContractName('BTC/USD', 0.001)), { symbol: 'BTC/USD', step: 0.001 }); assert.equal(decodeContractName('NQZ5'), null); assert.equal(roundToStep(0.1 + 0.2, 0.1), 0.3); assert.equal(roundToStep(12 * 0.001, 0.001), 0.012); assert.equal(roundToStep(101.234, 0.5, 'up'), 101.5); assert.equal(roundToStep(101.234, 0.5, 'down'), 101); }); test('the firm reset and the CT trade-date start', () => { assert.equal(lastUtcReset(new Date('2026-09-26T21:59:00Z')).toISOString(), '2026-09-25T22:00:00.000Z'); assert.equal(lastUtcReset(new Date('2026-09-26T22:00:00Z')).toISOString(), '2026-09-26T22:00:00.000Z'); assert.equal(ctTradeDayStart(cdt('2026-09-26T18:30:00')).toISOString(), '2026-09-26T22:00:00.000Z'); assert.equal(ctTradeDayStart(cdt('2026-09-27T09:00:00')).toISOString(), '2026-09-26T22:00:00.000Z'); assert.equal(ctTradeDayStart(cst('2027-01-12T10:00:00')).toISOString(), '2027-01-11T23:00:00.000Z'); }); test('the firm\'s day: the same label as the CT trade date, rolling at its reset', () => { // Summer: the reset is 5 PM CDT, so the two agree on both sides of it. assert.equal(firmDayOf(new Date('2026-09-26T21:59:00Z')), '2026-09-26'); assert.equal(firmDayOf(new Date('2026-09-26T22:00:00Z')), '2026-09-27'); assert.equal(firmDayOf(new Date('2026-09-26T22:00:00Z')), currentTradeDateCT(new Date('2026-09-26T22:00:00Z'))); // Winter: a close at 4:30 PM CST is the firm's next day, still ours. const close = cst('2027-01-12T16:30:00'); assert.equal(firmDayOf(close), '2027-01-13'); assert.equal(currentTradeDateCT(close), '2027-01-12'); assert.equal(firmDayOf(cst('2027-01-12T15:59:00')), '2027-01-12'); assert.equal(firmDayOf(cst('2027-01-12T17:30:00')), currentTradeDateCT(cst('2027-01-12T17:30:00'))); }); test('loose parsing', () => { assert.equal(parseDurationMs('PT30M'), 30 * 60_000); assert.equal(parseDurationMs('01:05:08'), (3600 + 300 + 8) * 1000); assert.equal(parseDurationMs({ seconds: 900 }), 900_000); assert.equal(parseDurationMs(undefined), 30 * 60_000); assert.equal(ciNum({ openPL: 12.5 }, 'openPl'), 12.5); assert.equal(ciNum({ openPL: '12' }, 'openPl'), undefined); assert.equal(signedFundValue('WITHDRAWAL', 500), -500); assert.equal(signedFundValue('WITHDRAWAL', -500), -500); assert.equal(signedFundValue('DEPOSIT', 50000), 50000); }); test('trading hours from UTC week sessions', () => { const at = (dayOfWeek, hour, eventType) => ({ eventType, weekDay: { dayOfWeek, time: { hour, minute: 30 } } }); const weekdays = [at('MONDAY', 13, 'SESSION_OPEN'), at('MONDAY', 20, 'SESSION_CLOSE'), at('TUESDAY', 13, 'SESSION_OPEN'), at('TUESDAY', 20, 'SESSION_CLOSE')]; assert.equal(inTradingHours(weekdays, new Date('2026-09-28T15:00:00Z')), true); // Monday assert.equal(inTradingHours(weekdays, new Date('2026-09-28T21:00:00Z')), false); assert.equal(inTradingHours(weekdays, new Date('2026-09-27T15:00:00Z')), false); // Sunday wraps to Tuesday's close assert.equal(inTradingHours([], new Date()), true); assert.equal(inTradingHours([{ garbage: true }], new Date()), true); }); test('realized volatility of hourly closes', () => { assert.equal(realizedVolPerHour([100, 101, 102]), null); const r = rng(6); const sigma = 0.004; const closes = [30000]; for (let i = 0; i < 2000; i++) { // Box–Muller for a normal step const z = Math.sqrt(-2 * Math.log(1 - r())) * Math.cos(2 * Math.PI * r()); closes.push(closes[closes.length - 1] * Math.exp(sigma * z)); } const v = realizedVolPerHour(closes); assert.ok(Math.abs(v - sigma) / sigma < 0.08, `σ ${v}`); const flat = Array.from({ length: MIN_VOL_CANDLES + 1 }, () => 200); assert.equal(realizedVolPerHour(flat), 0); }); test('the selector ranks the plan example: ETH, BTC, XYZ100, AAPL', () => { const perHour = (daily) => daily / Math.sqrt(24); const score = (spread, daily) => scoreCandidate({ spreadFraction: spread, commissionPct: 0.04, financingPctPerDay: 0, targetHoldHours: 6, volatilityPerHour: perHour(daily), }); const board = [ ['BTC/USD', score(0.0002, 0.03)], ['XYZ100', score(0.0001, 0.012)], ['AAPL/USD (Sat)', score(0.0005, 0.004)], ['ETH/USD', score(0.0003, 0.04)], ['AAPL/USD (Tue)', score(0.0005, 0.015)], ].sort((a, b) => a[1] - b[1]).map((b) => b[0]); assert.deepEqual(board, ['ETH/USD', 'BTC/USD', 'XYZ100', 'AAPL/USD (Tue)', 'AAPL/USD (Sat)']); // Cost per leg = score × √(A·B/T): the example's $50 and $60. const k = Math.sqrt((600 * 1350) / 6); assert.ok(Math.abs(score(0.0003, 0.04) * k - 49.6) < 1); assert.ok(Math.abs(score(0.0002, 0.03) * k - 60) < 1); // No volatility, no score; financing over the hold adds to the cost. assert.equal(scoreCandidate({ spreadFraction: 0.0002, commissionPct: 0.04, financingPctPerDay: 0, targetHoldHours: 6, volatilityPerHour: null }), null); const withFinancing = scoreCandidate({ spreadFraction: 0.0002, commissionPct: 0.04, financingPctPerDay: 0.033, targetHoldHours: 12, volatilityPerHour: perHour(0.03) }); assert.ok(withFinancing > score(0.0002, 0.03)); }); test('the selector drops what cannot trade right now', () => { const ok = { tradingStatus: 'FULL', bid: 100, ask: 100.02, quoteAgeMs: 1000, inHours: true, candles: 49, minContractsOk: true, currency: 'USD', accountCurrency: 'USD', }; assert.equal(candidateRejection(ok), null); assert.match(candidateRejection({ ...ok, tradingStatus: 'CLOSE_ONLY' }), /CLOSE_ONLY/); assert.match(candidateRejection({ ...ok, inHours: false }), /hours/); assert.match(candidateRejection({ ...ok, bid: undefined }), /quote/); assert.match(candidateRejection({ ...ok, quoteAgeMs: 10 * 60_000 }), /old/); assert.match(candidateRejection({ ...ok, ask: 101 }), /spread/); assert.match(candidateRejection({ ...ok, candles: 10 }), /candles/); assert.match(candidateRejection({ ...ok, currency: 'EUR' }), /EUR/); }); // ── Hyperliquid price fallback (lib/hyperliquid-feed.ts) ───────────────────── import { hyperliquidCoinFor, parseL2Book } from '../lib/hyperliquid-feed.ts'; test('DXtrade symbols map to Hyperliquid coins', () => { const coins = new Set(['BTC', 'ETH', 'xyz:XYZ100', 'xyz:SP500', 'xyz:AAPL']); assert.equal(hyperliquidCoinFor('BTC/USD', coins), 'BTC'); assert.equal(hyperliquidCoinFor('ETH/USD', coins), 'ETH'); assert.equal(hyperliquidCoinFor('XYZ100', coins), 'xyz:XYZ100'); assert.equal(hyperliquidCoinFor('SP500', coins), 'xyz:SP500'); assert.equal(hyperliquidCoinFor('AAPL/USD', coins), 'xyz:AAPL'); assert.equal(hyperliquidCoinFor('DOGE/USD', coins), null); }); test('l2Book best bid/ask', () => { const q = parseL2Book({ levels: [[{ px: '84310.0' }, { px: '84309' }], [{ px: '84311.0' }]], time: 1790461870878 }); assert.deepEqual(q, { bid: 84310, ask: 84311, time: 1790461870878 }); const empty = parseL2Book({ levels: [[], []] }); assert.equal(empty.bid, undefined); assert.equal(empty.ask, undefined); }); test('tick reasons fold their amounts, so like reasons count together', () => { assert.equal(foldReason('day done ($12.5 left of $300)'), 'day done ($… left of $…)'); assert.equal(foldReason('day done ($40 left of $1,500)'), 'day done ($… left of $…)'); assert.equal(foldReason('already traded today (realizedPnL=-123.45)'), 'already traded today (realizedPnL=…)'); assert.equal(foldReason('daily loss limit: $294.7 of $3000 left — day booked'), 'daily loss limit: $… of $… left — day booked'); assert.equal(foldReason("waiting for the broker to roll the account's day"), "waiting for the broker to roll the account's day"); assert.equal(foldReason('leg cap reached (5 today)'), 'leg cap reached (5 today)'); }); test('withinFirmCaps: a firm\'s cap on accounts at once, in order, less what it holds', () => { const topstep = { id: 'ts', maxConcurrentAccounts: 3 }; const apex = { id: 'apex', maxConcurrentAccounts: 0 }; const lucid = { id: 'lucid' }; const items = [ ...[1, 2, 3, 4, 5, 6].map((n) => ({ firm: topstep, n: `ts${n}` })), ...[1, 2, 3, 4].map((n) => ({ firm: apex, n: `apex${n}` })), { firm: lucid, n: 'lucid1' }, ]; const names = (xs) => xs.map((x) => x.n); // 6 Topstep capped at 3 keep the first 3; uncapped firms keep everything. assert.deepEqual(names(withinFirmCaps(items)), ['ts1', 'ts2', 'ts3', 'apex1', 'apex2', 'apex3', 'apex4', 'lucid1']); // Two already open: one more slot. assert.deepEqual(names(withinFirmCaps(items, new Map([['ts', 2]]))), ['ts1', 'apex1', 'apex2', 'apex3', 'apex4', 'lucid1']); // At (or past) the cap: none. assert.deepEqual(names(withinFirmCaps(items, new Map([['ts', 4]]))), ['apex1', 'apex2', 'apex3', 'apex4', 'lucid1']); // Held counts don't touch an uncapped firm, and the input isn't mutated. const held = new Map([['apex', 9]]); assert.equal(withinFirmCaps(items, held).filter((x) => x.firm === apex).length, 4); assert.equal(held.get('apex'), 9); assert.equal(held.has('ts'), false); // Interleaved order is kept. const mixed = [{ firm: topstep, n: 'a' }, { firm: apex, n: 'b' }, { firm: topstep, n: 'c' }, { firm: topstep, n: 'd' }, { firm: topstep, n: 'e' }]; assert.deepEqual(names(withinFirmCaps(mixed)), ['a', 'b', 'c', 'd']); assert.deepEqual(withinFirmCaps([]), []); }); // ── fund events from the balance (TopstepX reconcileFunds) ────────────────── import { fundReconcileStep, FUND_CONFIRM_MS } from '../lib/trading-logic.ts'; /** reconcileFunds' bookkeeping for one trade date, pass by pass: `balance` * and `trades` (all trade P&L, today's included) as each pass read them. * `existing` is a row already on the day that reconcileFunds didn't book. */ function reconcileDay(passes, existing = null) { let row = existing; let undoable = false; // the day's first booking saves what it replaces let sighting = null; const steps = []; for (const { at, balance, trades } of passes) { const delta = Math.round((balance - trades - (row ?? 0)) * 100) / 100; const step = fundReconcileStep({ delta, sighting, now: at * 1000, existingToday: row, undoable }); steps.push(step); if (step.kind === 'unconfirmed') { sighting = step.sighting; continue; } sighting = null; if (step.kind === 'book') { if (row === null) undoable = true; row = step.amount; } if (step.kind === 'drop') { row = null; undoable = false; } } return { row, kinds: steps.map((s) => s.kind), steps }; } test('fund reconcile: a balance read before a fill the trades already show books nothing', () => { // 2026-10-05, EXPRESS-V2-CT-DLL-39029-23275136: the pass read the balance // (−2.16), then the trades with the −3,001.16 stop-out in them. One pass // later the balance had it (−3,004.32, $1.00 of commission off-book). // Booked as it came, that was +3,001.16, then −3,002.16, merged to −1.00. const express = reconcileDay([ { at: 0, balance: -2.16, trades: -3003.32 }, { at: 60, balance: -3004.32, trades: -3003.32 }, ]); assert.deepEqual(express.kinds, ['unconfirmed', 'settled']); assert.equal(express.steps[0].sighting.delta, 3001.16); assert.equal(express.row, null); // 2026-10-08, 150KTC-SKU-V2-DLL-39029-99745612 (balance less the $150K // start): +2,474.01 then −2,492.01, which left a −18.00 row. const combine = reconcileDay([ { at: 0, balance: 2413.43, trades: -60.58 }, { at: 60, balance: -78.58, trades: -60.58 }, ]); assert.deepEqual(combine.kinds, ['unconfirmed', 'settled']); assert.equal(combine.row, null); }); test('fund reconcile: a real withdrawal books once a later pass still sees it', () => { // 2026-10-14, EXPRESS-V2-CT-DLL-39029-43788281: a $4,620 payout. const payout = reconcileDay([ { at: 0, balance: 4601.42, trades: 9221.42 }, { at: 10, balance: 4601.42, trades: 9221.42 }, // too soon to confirm { at: 60, balance: 4601.42, trades: 9221.42 }, ]); assert.deepEqual(payout.kinds, ['unconfirmed', 'unconfirmed', 'book']); assert.equal(payout.steps[1].fresh, false); assert.equal(payout.steps[1].sighting.since, 0); // the clock runs from the first sighting assert.equal(payout.row, -4620); // Fee noise between the passes still confirms; the later amount books. assert.equal(reconcileDay([ { at: 0, balance: 4601.42, trades: 9221.42 }, { at: FUND_CONFIRM_MS / 1000, balance: 4598.42, trades: 9221.42 }, ]).row, -4623); // An amount that moves starts over: the second sighting is the new one. const moved = reconcileDay([ { at: 0, balance: 4601.42, trades: 9221.42 }, { at: 60, balance: 4901.42, trades: 9221.42 }, { at: 120, balance: 4901.42, trades: 9221.42 }, ]); assert.deepEqual(moved.kinds, ['unconfirmed', 'unconfirmed', 'book']); assert.equal(moved.steps[1].fresh, true); assert.equal(moved.row, -4320); }); test('fund reconcile: a transient that outlasts the confirmation is taken back whole', () => { // The balance lags for two passes, so +3,001.16 books; when it catches up, // the reversal nets the day to −1.00 and the day is dropped, not left as // a −1.00 "withdrawal" that splits the cycle. const lagging = reconcileDay([ { at: 0, balance: -2.16, trades: -3003.32 }, { at: 60, balance: -2.16, trades: -3003.32 }, { at: 120, balance: -3004.32, trades: -3003.32 }, { at: 180, balance: -3004.32, trades: -3003.32 }, ]); assert.deepEqual(lagging.kinds, ['unconfirmed', 'book', 'unconfirmed', 'drop']); assert.equal(lagging.steps[1].amount, 3001.16); assert.equal(lagging.steps[3].net, -1); assert.equal(lagging.row, null); // A payout landing with the reversal is real: the day keeps its row. const withPayout = reconcileDay([ { at: 0, balance: -2.16, trades: -3003.32 }, { at: 60, balance: -2.16, trades: -3003.32 }, { at: 120, balance: -7624.32, trades: -3003.32 }, { at: 180, balance: -7624.32, trades: -3003.32 }, ]); assert.deepEqual(withPayout.kinds, ['unconfirmed', 'book', 'unconfirmed', 'book']); assert.equal(withPayout.row, -4621); // Only a day reconcileFunds booked itself is taken back: a row it found // there (an import) merges as before. const imported = reconcileDay([ { at: 0, balance: -1, trades: 0 }, { at: 60, balance: -1, trades: 0 }, ], -4620); assert.deepEqual(imported.kinds, ['unconfirmed', 'book']); assert.equal(imported.row, -1); }); test('machineSide: one side per machine, across every lane', () => { // Nothing held, nothing being placed: free. assert.equal(machineSide([], null), null); // Vest shorts held (hand trades or the bot's): every lane must short. assert.equal(machineSide([-42.6545, -12.1], null), 'Sell'); // A long NQ in the futures lane: the Vest lane must go long. assert.equal(machineSide([3], null), 'Buy'); // Another lane's batch is placing longs right now, nothing filled yet. assert.equal(machineSide([], 'Buy'), 'Buy'); assert.equal(machineSide([2], 'Buy'), 'Buy'); // Both sides already: nothing may be added. assert.equal(machineSide([1, -1], null), 'mixed'); assert.equal(machineSide([-5], 'Buy'), 'mixed'); }); test('same-day stages: today\'s payouts pick the stage, and a stage may carry its own daily limit', () => { // The Vest 50k Funded type: $1,500 then claim, then $3,000 on the room the claim freed. const stages = [{ profit: 3000, consistency: 0, minTradingDays: 0, dailyLossLimit: 3000 }]; const fundRows = [{ date: '2026-10-20', amount: 50000 }, { date: '2026-10-25', amount: -1500 }]; const resolve = (inputs) => resolveEffectiveConfig(1500, 0, 0, false, stages, 0, fundRows, 0, inputs); // Base stage, base limit: yesterday's payout doesn't count for a same-day type. const fresh = resolve({ dailyLossLimit: 1229, stagesSameDay: true, sameDayWithdrawals: 0 }); assert.equal(fresh.profitTarget, 1500); assert.equal(fresh.dailyLossLimit, 1229); // After a claim today: stage 2, its own limit. const after = resolve({ dailyLossLimit: 1229, stagesSameDay: true, sameDayWithdrawals: 1 }); assert.equal(after.profitTarget, 3000); assert.equal(after.dailyLossLimit, 3000); // A second claim today: the last stage repeats. assert.equal(resolve({ dailyLossLimit: 1229, stagesSameDay: true, sameDayWithdrawals: 2 }).profitTarget, 3000); // Without same-day stages the lifetime count picks it, as it always has. assert.equal(resolve({ dailyLossLimit: 1229 }).profitTarget, 3000); // A stage without its own limit keeps the type's; no inputs = no limit. const plain = resolveEffectiveConfig(1500, 0, 0, false, [{ profit: 2000, consistency: 0, minTradingDays: 0 }], 0, fundRows, 0, { dailyLossLimit: 1229 }); assert.equal(plain.dailyLossLimit, 1229); assert.equal(resolveEffectiveConfig(1500, 0, 0, false, [], 0, [], 0).dailyLossLimit, 0); // stageInputsFor asks the client only for a same-day type. const client = { sameDayClaims: () => 1 }; assert.deepEqual(stageInputsFor(client, 7, { dailyLossLimit: 1229, stagesSameDay: true }), { dailyLossLimit: 1229, stagesSameDay: true, sameDayWithdrawals: 1 }); assert.deepEqual(stageInputsFor(client, 7, { dailyLossLimit: 1229 }), { dailyLossLimit: 1229, stagesSameDay: false, sameDayWithdrawals: 0 }); assert.deepEqual(stageInputsFor({}, 7, { stagesSameDay: true }), { dailyLossLimit: 0, stagesSameDay: true, sameDayWithdrawals: 0 }); }); test('floorRoom: what\'s left of the stage\'s limit, widened by the day\'s gain the client allows, never past the platform\'s floor', () => { // Fresh day: $50k, Vest floor $48,500, the type's $1,229. assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50000, floor: 48500, realizedToday: 0 }), 1229); // Lost $1,111.65 today: Vest still allows $388.35, but the bot's own // $1,229 day has only $117.35 left. assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 48888.35, floor: 48500, realizedToday: -1111.65 }), 117.35); // A green day alone doesn't widen the limit... assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50600, floor: 48500, realizedToday: 600 }), 1229); // ...the gain the client allows does: the next leg may give it back plus // the limit, as on Dingo's 2026-10-27 evals ($1,400 a day, up $2,604.24)... assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50600, floor: 48500, realizedToday: 600, gain: 600 }), 1829); assert.equal(floorRoom({ dailyLossLimit: 1400, closedBalance: 52604.24, floor: 48500, realizedToday: 2604.24, gain: 2604.24 }), 4004.24); // ...never past Vest's own floor... assert.equal(floorRoom({ dailyLossLimit: 1400, closedBalance: 52604.24, floor: 49000, realizedToday: 2604.24, gain: 2604.24 }), 3604.24); // ...and never by more than the day realized: $2,250 claimed by hand // leaves realized at $2,250 (added back) but none of it in the account. assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50000, floor: 46250, realizedToday: 2250, gain: 0 }), 1229); assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50600, floor: 48500, realizedToday: 600, gain: 5000 }), 1829); // A red day still counts in full, whatever gain is offered. assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 49500, floor: 48500, realizedToday: -500, gain: 300 }), 729); // Won $1,500 and claimed it: balance $50k, Vest's floor lowered to $47k, // the day's P&L counted from the claim. Stage 2's $3,000 gets all of it. assert.equal(floorRoom({ dailyLossLimit: 3000, closedBalance: 50000, floor: 47000, realizedToday: 0 }), 3000); // ...and after a $500 stage-2 loss, $2,500 either way. assert.equal(floorRoom({ dailyLossLimit: 3000, closedBalance: 49500, floor: 47000, realizedToday: -500 }), 2500); // If Vest hadn't lowered the floor, only what it really leaves. assert.equal(floorRoom({ dailyLossLimit: 3000, closedBalance: 50000, floor: 48500, realizedToday: 0 }), 1500); // Below the floor already: negative room (no trade). assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 48400, floor: 48500, realizedToday: -1600 }), -371); assert.equal(floorRoom({ dailyLossLimit: 0, closedBalance: 50000, floor: 48500, realizedToday: -900 }), Infinity); }); test('the day after a claim: a fresh cycle that aims at the next stage\'s target', () => { // Claimed everything above $50k; P&L since the claim is 0, the cycle has no days yet. const plan = computeDayPlan(7, '2026-10-27', 3000, 0, [], 0, 0, 0, 0, false, false); assert.ok(plan.dayTarget >= 3000 && plan.dayTarget <= 3025, `dayTarget ${plan.dayTarget}`); assert.equal(plan.remainingToday, plan.dayTarget); });