// Checks for building trades out of fills (lib/fill-trades.ts — TopstepX and // Tradovate) and out of DXtrade's order history (lib/dxtrade-helpers.ts). // // npm run check // // The 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. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { fillsToTrades, startingNet, contractRoot, byFillTime } from '../lib/fill-trades.ts'; import { topstepxSymbol } from '../lib/topstep-fees.ts'; import { parseHistoryOrder, dxTrades } from '../lib/dxtrade-helpers.ts'; let n = 0; const fill = (side, qty, price, extra = {}) => { n += 1; return { id: String(1000 + n), orderId: String(5000 + n), at: 1_000 + n * 1_000, side, qty, price, fee: 1, pnl: null, ...extra }; }; test('a round trip, with the platform’s P&L', () => { const a = fill('buy', 2, 100); const b = fill('sell', 2, 101, { pnl: 40 }); const [t, ...rest] = fillsToTrades([b, a], { startNet: 0, pointValue: null }); assert.equal(rest.length, 0); assert.deepEqual(t, { id: a.id, side: 'long', quantity: 2, openPrice: 100, closePrice: 101, pnl: 40, fee: 2, openedAt: a.at, closedAt: b.at, openOrderId: a.orderId, closeOrderId: b.orderId, carried: false, }); }); test('P&L from the prices when the platform gives none; adds and partial closes stay one trade', () => { const fills = [fill('buy', 1, 100), fill('buy', 1, 102), fill('sell', 1, 103), fill('sell', 1, 104)]; const [t, ...rest] = fillsToTrades(fills, { startNet: 0, pointValue: 20 }); assert.equal(rest.length, 0); assert.equal(t.quantity, 2); assert.equal(t.openPrice, 101); assert.equal(t.closePrice, 103.5); assert.equal(t.pnl, 100); assert.equal(t.fee, 4); assert.equal(t.closeOrderId, fills[3].orderId); // No point value and no platform P&L: unknown, not zero. assert.equal(fillsToTrades(fills, { startNet: 0, pointValue: null })[0].pnl, null); }); test('a short', () => { const fills = [fill('sell', 3, 50.5), fill('buy', 3, 50)]; const [t] = fillsToTrades(fills, { startNet: 0, pointValue: 1000 }); assert.equal(t.side, 'short'); assert.equal(t.pnl, 1500); }); test('a fill through zero closes one trade and opens the next', () => { const a = fill('buy', 1, 100); const b = fill('sell', 3, 105, { fee: 3, pnl: 100 }); const [first, second, ...rest] = fillsToTrades([a, b], { startNet: 0, pointValue: 20 }); assert.equal(rest.length, 0); assert.equal(first.side, 'long'); assert.equal(first.pnl, 100); assert.equal(first.fee, 2); // its own buy, and a third of the reversing fill assert.equal(first.closeOrderId, b.orderId); assert.equal(second.id, b.id); assert.equal(second.side, 'short'); assert.equal(second.quantity, 2); assert.equal(second.openPrice, 105); assert.equal(second.fee, 2); assert.equal(second.closedAt, null); assert.equal(second.pnl, null); assert.equal(second.closePrice, null); }); test('fills that start inside a position: passed over, or carried on', () => { const close = fill('sell', 1, 110, { pnl: 200 }); const a = fill('buy', 1, 100); const b = fill('sell', 1, 101, { pnl: 20 }); // Nothing stored: the leading close belongs to nothing on record. const plain = fillsToTrades([close, a, b], { startNet: 1, pointValue: 20 }); assert.equal(plain.length, 1); assert.equal(plain[0].id, a.id); // The stored open trade carries on and closes. const carry = { id: 'tsx:42', side: 'long', quantity: 1, openPrice: 100, fee: 0.5, openedAt: 10 }; const [carried, next] = fillsToTrades([close, a, b], { startNet: 1, pointValue: 20, carry }); assert.equal(carried.id, 'tsx:42'); assert.equal(carried.carried, true); assert.equal(carried.openedAt, 10); assert.equal(carried.openOrderId, null); assert.equal(carried.pnl, 200); assert.equal(carried.fee, 1.5); assert.equal(carried.closedAt, close.at); assert.equal(next.id, a.id); // A carry on the other side isn't this position. assert.equal(fillsToTrades([close], { startNet: 1, pointValue: 20, carry: { ...carry, side: 'short' } }).length, 0); }); test('the position before the fills', () => { const buy = fill('buy', 1, 1); const sell = fill('sell', 1, 1); assert.equal(startingNet([buy, sell], 0), 0); assert.equal(startingNet([buy], 1), 0); assert.equal(startingNet([sell], 0), 1); assert.equal(startingNet([], -2), -2); }); test('fills sort by time, then numerically by id', () => { const a = { id: '9', at: 5 }; const b = { id: '10', at: 5 }; const c = { id: '1', at: 6 }; assert.deepEqual([c, b, a].sort(byFillTime).map((f) => f.id), ['9', '10', '1']); }); test('contract roots and TopstepX names', () => { assert.equal(contractRoot('MNQZ6'), 'MNQ'); assert.equal(contractRoot('ZNZ6'), 'ZN'); assert.equal(contractRoot('NGZ6'), 'NG'); assert.equal(contractRoot('6EZ6'), '6E'); assert.equal(contractRoot('M2KH27'), 'M2K'); assert.equal(contractRoot('CL'), 'CL'); assert.equal(topstepxSymbol('CON.F.US.ENQ.Z26'), 'NQZ6'); assert.equal(topstepxSymbol('CON.F.US.MNQ.H27'), 'MNQH7'); assert.equal(topstepxSymbol('CON.F.US.XYZ.M26'), 'XYZM6'); assert.equal(topstepxSymbol('something'), 'something'); }); // DXtrade: a bot round trip on BTC/USD, as /orders/history and /transfers // answer (position-based account: the opening order's id is the position). const openOrder = { account: 'default:T247-1', orderId: 555, orderCode: 'af-mh2-abc-e', type: 'MARKET', instrument: 'BTC/USD', status: 'COMPLETED', finalStatus: true, legs: [{ instrument: 'BTC/USD', positionEffect: 'OPEN', side: 'BUY', quantity: 0.5, filledQuantity: 0.5, averagePrice: 60000 }], issueTime: '2026-10-26T01:00:00.000Z', transactionTime: '2026-10-26T01:00:01.000Z', executions: [{ transactionTime: '2026-10-26T01:00:00.500Z', lastQuantity: 0.5, averagePrice: 60000 }], }; const stopOrder = { account: 'default:T247-1', orderId: 556, orderCode: 'af-mh3-def-sl', type: 'STOP', instrument: 'BTC/USD', status: 'COMPLETED', finalStatus: true, legs: [{ instrument: 'BTC/USD', positionEffect: 'CLOSE', positionCode: '555', side: 'SELL', filledQuantity: 0.5, averagePrice: 59000 }], issueTime: '2026-10-26T01:00:02.000Z', transactionTime: '2026-10-26T02:00:00.000Z', executions: [], }; const txn = (type, value, orderCode) => ({ key: `${orderCode}|${type}`, account: 'default:T247-1', type, value, time: 0, orderCode }); test('DXtrade order history rows', () => { const o = parseHistoryOrder(openOrder); assert.equal(o.orderCode, 'af-mh2-abc-e'); assert.equal(o.effect, 'OPEN'); assert.equal(o.side, 'BUY'); assert.equal(o.positionCode, undefined); assert.equal(o.qty, 0.5); assert.equal(o.price, 60000); assert.equal(o.at, Date.parse('2026-10-26T01:00:00.500Z')); const s = parseHistoryOrder(stopOrder); assert.equal(s.positionCode, '555'); assert.equal(s.at, Date.parse('2026-10-26T02:00:00.000Z')); // Never filled: not a trade's. assert.equal(parseHistoryOrder({ ...openOrder, legs: [{ ...openOrder.legs[0], filledQuantity: 0 }] }), null); assert.equal(parseHistoryOrder({}), null); }); test('DXtrade positions from their orders and cash', () => { const orders = [parseHistoryOrder(stopOrder), parseHistoryOrder(openOrder)]; const txns = [txn('SETTLEMENT', -500, 'af-mh3-def-sl'), txn('COMMISSION', -12, 'af-mh2-abc-e'), txn('COMMISSION', -11.8, 'af-mh3-def-sl')]; const [t, ...rest] = dxTrades(orders, txns, new Map(), new Map()); assert.equal(rest.length, 0); assert.equal(t.positionCode, '555'); assert.equal(t.side, 'long'); assert.equal(t.quantity, 0.5); assert.equal(t.openPrice, 60000); assert.equal(t.closePrice, 59000); assert.equal(t.pnl, -500); assert.equal(t.fee, 23.8); assert.equal(t.openCode, 'af-mh2-abc-e'); assert.equal(t.closeCode, 'af-mh3-def-sl'); assert.equal(t.closeType, 'STOP'); // Still held: open, whatever the history says. const held = new Map([['555', { symbol: 'BTC/USD', side: 'long', quantity: 0.5, openPrice: 60000, openedAt: 1 }]]); const [open] = dxTrades([parseHistoryOrder(openOrder)], [], held, new Map()); assert.equal(open.closedAt, null); assert.equal(open.pnl, null); // Opened before the history: completed from what's stored, or left out. const [late] = dxTrades([parseHistoryOrder(stopOrder)], txns, new Map(), held); assert.equal(late.openedAt, 1); assert.equal(late.openCode, null); assert.equal(late.pnl, -500); assert.equal(dxTrades([parseHistoryOrder(stopOrder)], txns, new Map(), new Map()).length, 0); // A position open with no orders in the window still shows. const [quiet] = dxTrades([], [], held, new Map()); assert.equal(quiet.positionCode, '555'); assert.equal(quiet.openedAt, 1); });