Merge branch 'vest-fix-timing' into vest-fixes
# Conflicts: # lib/reporter.ts # lib/vest-class.ts # scripts/check-vest.mjs
This commit is contained in:
@@ -14,7 +14,8 @@ import {
|
||||
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,
|
||||
resolveEffectiveConfig, stageInputsFor, floorRoom, resetClearStop, shortestNextLeg, computeDayPlan, clearsClose, RESET_CLEAR_HOLDS,
|
||||
legCloseNote,
|
||||
} from '../lib/trading-logic.ts';
|
||||
|
||||
// Deterministic PRNG so a failure reproduces.
|
||||
@@ -527,6 +528,39 @@ test('resetClearStop: the widest stop a leg may carry and still close before the
|
||||
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('legCloseNote: each account\'s leg is held to its market\'s close at its own size', () => {
|
||||
const h = 3_600_000;
|
||||
const now = Date.parse('2026-10-30T12:30:00Z');
|
||||
const close = now + 9 * h;
|
||||
// The batch's lead leg, 2 h: clear. Another account's, sized to 3.5 h: not.
|
||||
assert.equal(legCloseNote(close, 2, now), null);
|
||||
assert.equal(legCloseNote(close, 3.5, now), 'closes in 9.0h, under 4 holds of 3.5h');
|
||||
// A contract that names no close (futures, crypto) never holds a leg back.
|
||||
assert.equal(legCloseNote(undefined, 3.5, now), null);
|
||||
assert.equal(legCloseNote(null, 3.5, now), null);
|
||||
// An unknown hold doesn't open into a known close.
|
||||
assert.notEqual(legCloseNote(close, NaN, now), null);
|
||||
});
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// Checks for the Vest client's own wiring (lib/vest-class.ts), past what its
|
||||
// pure helpers show: the market-close guard closing a leg whose open came back
|
||||
// unknown, and the candidate board keeping a symbol the close gate holds back.
|
||||
//
|
||||
// npm run check
|
||||
//
|
||||
// A scratch database, a fake clock, and every gateway call answered here —
|
||||
// nothing reaches Vest.
|
||||
import { after, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { registerHooks, stripTypeScriptTypes } from 'node:module';
|
||||
|
||||
// Resolve the extensionless local imports used by Next, without a build.
|
||||
function resolveTypes(specifier, context, nextResolve) {
|
||||
try {
|
||||
return nextResolve(specifier, context);
|
||||
} catch (err) {
|
||||
if (err.code !== 'ERR_MODULE_NOT_FOUND' || !specifier.startsWith('.')) throw err;
|
||||
return nextResolve(`${specifier}.ts`, context);
|
||||
}
|
||||
}
|
||||
// The client's classes use parameter properties, which plain type
|
||||
// stripping refuses: transform them instead.
|
||||
function loadTypes(url, context, nextLoad) {
|
||||
if (!url.startsWith('file:') || !url.endsWith('.ts')) return nextLoad(url, context);
|
||||
const source = stripTypeScriptTypes(readFileSync(fileURLToPath(url), 'utf8'), { mode: 'transform' });
|
||||
return { format: 'module', source, shortCircuit: true };
|
||||
}
|
||||
registerHooks({ resolve: resolveTypes, load: loadTypes });
|
||||
|
||||
const directory = mkdtempSync(join(tmpdir(), 'autofirmer-vest-client-'));
|
||||
process.env.AUTOFIRMER_DB = join(directory, 'test.sqlite');
|
||||
delete process.env.NEXT_PHASE;
|
||||
after(() => rmSync(directory, { recursive: true, force: true }));
|
||||
|
||||
// No request leaves this process: axios refuses every one, and each client's
|
||||
// gateway is a function in this file — from construction on, since the
|
||||
// constructor starts a sync.
|
||||
const { default: axios } = await import('../lib/http-client.ts');
|
||||
axios.defaults.adapter = (config) => Promise.reject(new Error(`no network in this test: ${config.method} ${config.url}`));
|
||||
const { VestClient } = await import('../lib/vest-class.ts');
|
||||
let gateway = null;
|
||||
VestClient.prototype.request = async function (bucket, method, path, opts = {}) {
|
||||
if (!gateway) throw new Error(`no gateway in this test: ${method} ${path}`);
|
||||
return gateway(method, path, opts);
|
||||
};
|
||||
const { putVestSession } = await import('../lib/vest-session.ts');
|
||||
const { botOpenedPositions } = await import('../lib/db.ts');
|
||||
const { vestDayOf, VEST_STATUS_ACTIVE } = await import('../lib/vest-helpers.ts');
|
||||
const { encodeContractName } = await import('../lib/dxtrade-helpers.ts');
|
||||
|
||||
const iso = (s) => Date.parse(s);
|
||||
const WALLET = '0x7ce10771819f80130e204ddb5ca1522e48a9b451';
|
||||
|
||||
// The clock the client reads (Date.now), set to an instant and running on.
|
||||
const realNow = Date.now;
|
||||
after(() => { Date.now = realNow; });
|
||||
let tokens = 0;
|
||||
function clockAt(when) {
|
||||
const start = realNow();
|
||||
const t0 = iso(when);
|
||||
Date.now = () => t0 + (realNow() - start);
|
||||
tokens += 1;
|
||||
assert.equal(putVestSession({ accessToken: `eyJ.test${tokens}.sig`, expiresAt: Date.now() + 3_600_000, primaryAddr: WALLET, userId: 'u' }), null);
|
||||
}
|
||||
|
||||
// The week of NDX hours from check-vest.mjs: maintenance at 21:00 UTC
|
||||
// (17:00 ET) Thursday, the weekly close at 21:00 UTC Friday.
|
||||
const open = (from, to) => ({ fromMs: iso(from), toMs: iso(to), state: { session: 'overnight', closure: 'unspecified', tradingStatus: 'OPEN', price: { allow: true } } });
|
||||
const shut = (from, to, closure) => ({ fromMs: iso(from), toMs: iso(to), state: { session: 'unspecified', closure, tradingStatus: closure === 'weekly' ? 'CLOSE' : 'OPEN', price: { allow: closure !== 'weekly' } } });
|
||||
const week = [
|
||||
open('2026-10-28T22:00:00Z', '2026-10-29T21:00:00Z'),
|
||||
shut('2026-10-29T21:00:00Z', '2026-10-29T22:00:00Z', 'maintenance'),
|
||||
open('2026-10-29T22:00:00Z', '2026-10-30T21:00:00Z'),
|
||||
shut('2026-10-30T21:00:00Z', '2026-11-01T23:00:00Z', 'weekly'),
|
||||
open('2026-11-01T23:00:00Z', '2026-11-02T22:00:00Z'),
|
||||
];
|
||||
const spec = (symbol, assetId) => ({
|
||||
symbol, assetId, sizeDecimals: 3, priceDecimals: 2, tick: 0.01, takerFee: 0.0001, marginRate: 0.05, tradingStatus: 'TRADING', capitalTradingStatus: 'TRADING',
|
||||
});
|
||||
const specs = new Map([['NDX-USD', spec('NDX-USD', 7)], ['BTC-PERP', spec('BTC-PERP', 9)]]);
|
||||
|
||||
/** A client with no sync running, its gateway answered by `answer`. */
|
||||
function testClient(answer) {
|
||||
gateway = null;
|
||||
const client = new VestClient({ firmId: 'vest-test', firmName: 'Vest test', wallet: WALLET }, async () => {});
|
||||
clearInterval(client.syncInterval);
|
||||
clearInterval(client.watchdogInterval);
|
||||
gateway = answer;
|
||||
client.specs = specs;
|
||||
client.symbolSpecs = async () => specs;
|
||||
client.marketHours = async (assetId) => (assetId === 7 ? week : []);
|
||||
client.accountList = [{ id: 1, name: 'V50K-F-D3-1', active: true }, { id: 2, name: 'V50K-F-D3-2', active: true }];
|
||||
client.uuidById.set(1, 'acc-1');
|
||||
client.uuidById.set(2, 'acc-2');
|
||||
client.capital.set(1, { status: VEST_STATUS_ACTIVE });
|
||||
client.capital.set(2, { status: VEST_STATUS_ACTIVE });
|
||||
return client;
|
||||
}
|
||||
|
||||
test('the market-close guard closes a bot leg whose open came back unknown, not a hand trade', async () => {
|
||||
let positions = [];
|
||||
const closes = [];
|
||||
const client = testClient((method, path, opts) => {
|
||||
if (method === 'GET' && path === '/v3/positions/opened') return { data: { positions } };
|
||||
if (method === 'POST' && path === '/v3/positions/open') throw new Error('socket hang up');
|
||||
if (method === 'POST' && path === '/v3/positions/close') { closes.push({ account: opts.account, ...opts.body }); return { data: {} }; }
|
||||
throw new Error(`no gateway in this test: ${method} ${path}`);
|
||||
});
|
||||
try {
|
||||
// Friday 8:30 AM ET: the entry's send fails in transit, but it filled.
|
||||
clockAt('2026-10-30T12:30:00Z');
|
||||
await assert.rejects(client.sendOrder(1, encodeContractName('NDX-USD', 0.001), 500, 'Buy', 'Market'), /outcome unknown/);
|
||||
const raw = (positionId, accountId, openDate) => ({
|
||||
positionId, accountId, symbol: 'NDX-USD', side: 'LONG', quantity: '0.500', openPrice: '20000', collateral: '500', leverage: '20', openDate,
|
||||
stopLosses: [], takeProfits: [],
|
||||
});
|
||||
positions = [raw('9001', 'acc-1', iso('2026-10-30T12:30:01Z')), raw('9002', 'acc-2', iso('2026-10-30T11:00:00Z'))];
|
||||
client.positionsDetail.set(1, []);
|
||||
client.positionsDetail.set(2, [{ positionId: '9002', accountId: 'acc-2', symbol: 'NDX-USD', side: 'long', quantity: 0.5 }]);
|
||||
client.syncComplete = true;
|
||||
|
||||
// 4:56 PM ET: four minutes to the weekly close.
|
||||
clockAt('2026-10-30T20:56:00Z');
|
||||
await client.guardClose();
|
||||
assert.deepEqual(closes, [{ account: 'acc-1', positionId: '9001', symbol: 'NDX-USD', orderType: 'market' }]);
|
||||
assert.deepEqual([...botOpenedPositions(['9001', '9002'])], ['9001']);
|
||||
// A close just sent isn't sent again while a read may not show it.
|
||||
await client.guardClose();
|
||||
assert.equal(closes.length, 1);
|
||||
} finally {
|
||||
client.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
test('a symbol the close gate holds back stays on the board, and isn\'t picked; the maintenance hour is ridden', async () => {
|
||||
const client = testClient((method, path) => {
|
||||
throw new Error(`no gateway in this test: ${method} ${path}`);
|
||||
});
|
||||
client.refreshMarks = async () => {};
|
||||
client.volatility = async (symbols) => new Map(symbols.map((s) => [s, { vol: 0.004, candles: 168 }]));
|
||||
const marked = () => { for (const s of ['NDX-USD', 'BTC-PERP']) client.marks.set(s, { mark: s === 'NDX-USD' ? 20000 : 70000, at: Date.now() }); };
|
||||
try {
|
||||
// Thursday 2 PM ET: NDX's maintenance hour is 3 h off, and reopens
|
||||
// before the reset — no gate.
|
||||
clockAt('2026-10-29T18:00:00Z');
|
||||
marked();
|
||||
await client.pickCandidate('Auto', ['NDX-USD', 'BTC-PERP'], false);
|
||||
const thursday = client.lastCandidates.find((c) => c.id === 'NDX-USD');
|
||||
assert.equal(thursday.closesAt, undefined);
|
||||
|
||||
// Friday 2 PM ET: the weekly close is 3 h off; crypto never closes.
|
||||
clockAt('2026-10-30T18:00:00Z');
|
||||
marked();
|
||||
const pick = await client.pickCandidate('Auto', ['NDX-USD', 'BTC-PERP'], false);
|
||||
assert.equal(pick.id, 'BTC-PERP');
|
||||
assert.equal(pick.closesAt, undefined);
|
||||
assert.deepEqual(client.lastCandidates.map((c) => c.id).sort(), ['BTC-PERP', 'NDX-USD']);
|
||||
const ndx = client.lastCandidates.find((c) => c.id === 'NDX-USD');
|
||||
assert.equal(ndx.closesAt, iso('2026-10-30T21:00:00Z'));
|
||||
// The survey keeps it up for the dashboard, though nothing is picked.
|
||||
assert.equal(await client.pickCandidate('survey', ['NDX-USD'], false), null);
|
||||
assert.deepEqual(client.lastCandidates.map((c) => c.id), ['NDX-USD']);
|
||||
// And the client's day is Vest's.
|
||||
assert.equal(client.tradeDate(iso('2026-10-27T22:30:00Z')), vestDayOf(iso('2026-10-27T22:30:00Z')));
|
||||
} finally {
|
||||
client.disconnect();
|
||||
}
|
||||
});
|
||||
+98
-4
@@ -1,9 +1,10 @@
|
||||
// Checks for the Vest client's pure helpers (lib/vest-helpers.ts) and the
|
||||
// relayed-session store (lib/vest-session.ts).
|
||||
// Checks for the Vest client's pure helpers (lib/vest-helpers.ts), the
|
||||
// relayed-session store (lib/vest-session.ts), and the trade date the engine
|
||||
// plans Vest accounts on (lib/trading-logic.ts).
|
||||
//
|
||||
// npm run check
|
||||
//
|
||||
// Both files import only Node built-ins, so Node strips their types and loads
|
||||
// These files import only Node built-ins, so Node strips their types and loads
|
||||
// them directly; .mjs keeps this file out of Next's type check. The samples are
|
||||
// shaped like the gateway's real answers (captured 2026-10-26).
|
||||
import { test } from 'node:test';
|
||||
@@ -13,9 +14,10 @@ import {
|
||||
vestAccountName, parsePosition, awaitingLegs, parseOpenOrder, unrealizedPnl, parseTxn, bookTxns, txnKind, VEST_TXN,
|
||||
hourlyVolatility, marketOpen, MIN_HOURLY_RETURNS, nextVestResetMs, resetSafeStop, parseHistoryPosition, jwtClaimStrings,
|
||||
accountScopedToken, VestApiError, VestNotSentError, nothingWentIn, tokenForRequest, claimWithdrawal, settleClaims,
|
||||
claimFundRows, readClaims, payoutDaysOf, payoutsSince,
|
||||
claimFundRows, readClaims, payoutDaysOf, payoutsSince, longClosure, unknownOpenFill,
|
||||
} from '../lib/vest-helpers.ts';
|
||||
import { putVestSession, vestSessionFor, dropVestSession, listVestSessions, refuseVestSession } from '../lib/vest-session.ts';
|
||||
import { computeDayPlan, currentTradeDateCT, tradeDateFor, cycleCompletion } from '../lib/trading-logic.ts';
|
||||
|
||||
const iso = (s) => Date.parse(s);
|
||||
|
||||
@@ -127,6 +129,23 @@ const openPosition = {
|
||||
takeProfits: [],
|
||||
};
|
||||
|
||||
test('an open of unknown outcome is the position on its account, symbol and side opened around it', () => {
|
||||
const pos = (positionId, accountId, side, openedAt, symbol = 'NDX-USD') => ({
|
||||
positionId, accountId, symbol, side, quantity: 0.5, openPrice: 20000, collateral: 500, leverage: 20, openedAt, stopLosses: [], takeProfits: [],
|
||||
});
|
||||
const sent = { accountId: 'acc-1', symbol: 'NDX-USD', side: 'long', from: iso('2026-10-30T12:28:00Z'), until: iso('2026-10-30T12:32:15Z') };
|
||||
const filled = pos('9001', 'acc-1', 'long', iso('2026-10-30T12:30:01Z'));
|
||||
assert.equal(unknownOpenFill(sent, [pos('9002', 'acc-2', 'long', iso('2026-10-30T12:30:01Z')), filled]), filled);
|
||||
// Another side or symbol, a position from before it, or one opened after
|
||||
// the window (a hand trade later on): not it.
|
||||
assert.equal(unknownOpenFill(sent, [pos('9003', 'acc-1', 'short', iso('2026-10-30T12:30:01Z'))]), null);
|
||||
assert.equal(unknownOpenFill(sent, [pos('9004', 'acc-1', 'long', iso('2026-10-30T12:30:01Z'), 'SPX-USD')]), null);
|
||||
assert.equal(unknownOpenFill(sent, [pos('9005', 'acc-1', 'long', iso('2026-10-30T11:00:00Z'))]), null);
|
||||
assert.equal(unknownOpenFill(sent, [pos('9006', 'acc-1', 'long', iso('2026-10-30T14:00:00Z'))]), null);
|
||||
// No open time: can't be told from a hand trade.
|
||||
assert.equal(unknownOpenFill(sent, [pos('9007', 'acc-1', 'long', NaN)]), null);
|
||||
});
|
||||
|
||||
test('positions, their legs and unrealized P&L', () => {
|
||||
const p = parsePosition(openPosition);
|
||||
assert.equal(p.side, 'short');
|
||||
@@ -343,6 +362,81 @@ test('market hours: open stretches, closures and crypto', () => {
|
||||
assert.deepEqual(marketOpen([], 600), { open: true, closesAt: null });
|
||||
});
|
||||
|
||||
test('market hours: a closure past an 8 PM ET reset is a long one, the maintenance hour is not', () => {
|
||||
const open = (from, to) => ({ fromMs: iso(from), toMs: iso(to), state: { session: 'overnight', closure: 'unspecified', tradingStatus: 'OPEN', price: { allow: true } } });
|
||||
const shut = (from, to, closure) => ({ fromMs: iso(from), toMs: iso(to), state: { session: 'unspecified', closure, tradingStatus: closure === 'weekly' ? 'CLOSE' : 'OPEN', price: { allow: closure !== 'weekly' } } });
|
||||
const week = [
|
||||
open('2026-10-28T22:00:00Z', '2026-10-29T21:00:00Z'),
|
||||
shut('2026-10-29T21:00:00Z', '2026-10-29T22:00:00Z', 'maintenance'),
|
||||
open('2026-10-29T22:00:00Z', '2026-10-30T21:00:00Z'),
|
||||
shut('2026-10-30T21:00:00Z', '2026-11-01T23:00:00Z', 'weekly'),
|
||||
open('2026-11-01T23:00:00Z', '2026-11-02T22:00:00Z'),
|
||||
shut('2026-11-02T22:00:00Z', '2026-11-02T23:00:00Z', 'maintenance'),
|
||||
open('2026-11-02T23:00:00Z', '2026-11-03T22:00:00Z'),
|
||||
];
|
||||
// The weekend: shut through Friday's and Saturday's 8 PM ET resets.
|
||||
const friday = marketOpen(week, iso('2026-10-30T20:57:00Z'));
|
||||
assert.deepEqual(friday, { open: true, closesAt: iso('2026-10-30T21:00:00Z') });
|
||||
assert.equal(longClosure(week, friday.closesAt), true);
|
||||
// The maintenance hour reopens before the evening's reset, in summer and winter.
|
||||
const thursday = marketOpen(week, iso('2026-10-29T20:57:00Z'));
|
||||
assert.equal(thursday.closesAt, iso('2026-10-29T21:00:00Z'));
|
||||
assert.equal(longClosure(week, thursday.closesAt), false);
|
||||
assert.equal(longClosure(week, iso('2026-11-02T22:00:00Z')), false);
|
||||
// A gap in the periods is shut too (a holiday), and this one outlasts the reset.
|
||||
const early = [open('2026-11-25T14:00:00Z', '2026-11-25T18:00:00Z'), open('2026-11-26T23:00:00Z', '2026-11-27T18:00:00Z')];
|
||||
assert.equal(longClosure(early, iso('2026-11-25T18:00:00Z')), true);
|
||||
// Shut as far as the periods go: long. Periods that just end: no close known.
|
||||
assert.equal(longClosure(week.slice(0, 4), iso('2026-10-30T21:00:00Z')), true);
|
||||
assert.equal(longClosure(week.slice(0, 3), iso('2026-10-30T21:00:00Z')), false);
|
||||
assert.equal(longClosure(undefined, iso('2026-10-30T21:00:00Z')), false);
|
||||
});
|
||||
|
||||
test("a Vest account plans on Vest's day: the 5 PM CT roll doesn't count today's P&L twice", () => {
|
||||
const vest = { tradeDate: vestDayOf };
|
||||
// 3:30 and 5:30 PM CT (4:30 / 6:30 PM ET): the Central date rolls at
|
||||
// 5 PM CT, Vest's day only at 8 PM ET.
|
||||
const before = new Date('2026-10-27T20:30:00Z');
|
||||
const after = new Date('2026-10-27T22:30:00Z');
|
||||
assert.equal(currentTradeDateCT(after), '2026-10-28');
|
||||
assert.equal(tradeDateFor(vest, after), '2026-10-27');
|
||||
assert.equal(tradeDateFor(vest, new Date('2026-10-28T00:00:00Z')), '2026-10-28');
|
||||
// Every other platform keeps the Central date.
|
||||
assert.equal(tradeDateFor(undefined, after), '2026-10-28');
|
||||
assert.equal(tradeDateFor({}, after), '2026-10-28');
|
||||
// $3,000 to go, 3 min days of $150, $800 made today: Vest's row for its
|
||||
// day, and the realized P&L since its reset.
|
||||
const rows = [{ date: '2026-10-27', pnl: 800 }];
|
||||
const plan = (date) => computeDayPlan(7, date, 3000, 0, rows, 150, 3, 800, 800);
|
||||
assert.equal(plan(currentTradeDateCT(before)).remainingToday, 1900);
|
||||
assert.deepEqual(plan(tradeDateFor(vest, after)), plan(currentTradeDateCT(before)));
|
||||
// On the Central date the $800 is a prior min day and today's P&L both.
|
||||
assert.equal(plan(currentTradeDateCT(after)).remainingToday, 2050);
|
||||
});
|
||||
|
||||
test("a Vest account's completion is judged on Vest's day, and waits out Vest's own roll", () => {
|
||||
const vest = { tradeDate: vestDayOf };
|
||||
// $3,000 in 2 min days: day one made $3,100 and is flat. At 5:30 PM CT
|
||||
// its row is booked and its P&L is still today's realized P&L.
|
||||
const eff = { profitTarget: 3000, consistency: 0, minTradingDays: 2, minDayPnL: 0, drain: false, dailyLossLimit: 0 };
|
||||
const opts = {
|
||||
accountId: 7, cycleDailyPnL: [{ date: '2026-10-27', pnl: 3100 }], rawRealizedPnL: 3100, combineCmeHolidays: false,
|
||||
tradesEveryDay: true, effective: eff, equityProfit: 3100, splitEnabled: true, client: vest,
|
||||
};
|
||||
const evening = new Date('2026-10-27T22:30:00Z');
|
||||
// The Central date counts the one day twice and reports a pass.
|
||||
assert.equal(cycleCompletion({ ...opts, client: undefined, rawTradeDate: currentTradeDateCT(evening), now: evening }), 'today');
|
||||
assert.equal(cycleCompletion({ ...opts, rawTradeDate: tradeDateFor(vest, evening), now: evening }), 'none');
|
||||
// Two minutes past Vest's 8 PM ET roll the last sync's realized P&L can
|
||||
// still be the ended day's: no 'today' until the settle window passes.
|
||||
const justRolled = new Date('2026-10-28T00:02:00Z');
|
||||
assert.equal(tradeDateFor(vest, justRolled), '2026-10-28');
|
||||
assert.equal(cycleCompletion({ ...opts, rawTradeDate: '2026-10-28', now: justRolled }), 'none');
|
||||
// A real second day, once settled, finishes it.
|
||||
const settled = new Date('2026-10-28T00:10:00Z');
|
||||
assert.equal(cycleCompletion({ ...opts, rawTradeDate: '2026-10-28', rawRealizedPnL: 50, equityProfit: 3150, now: settled }), 'today');
|
||||
});
|
||||
|
||||
test('relayed sessions: taken, kept fresher, dropped, never listed with tokens', () => {
|
||||
const now = iso('2026-10-26T18:40:00Z');
|
||||
const jwt = (n) => `eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIke${n}In0.sig${n}`;
|
||||
|
||||
Reference in New Issue
Block a user