TopstepX: book a fund event only once a later pass still sees it
A sync reads the balance first and each account's positions and trades after it, so a fill landing in between shows in the trades but not yet in the balance, and for that one pass the books are off by the whole fill. reconcileFunds booked that as a fund event, and the next pass booked the reversal. Same-day rows merge, so the two netted to the commission drift of the day and left a tiny row: - 2026-10-05, Express 43788281 and 23275136: +3,001.16 "deposit" (balance -2.16 against the -3,001.16 stop-out already in the trades), then -3,002.16, merged to -1.00. - 2026-10-08, combine 99745612: +2,474.01, then -2,492.01, left -18.00. - 2026-09-24, five 150K combines on another instance: +3,049.45, then -3,059.45, left -10.00 each. A negative row is a withdrawal. It started a new cycle, dropped its day out of the cycle (the -3,003.32 on 10-05) and counted toward the stage: both Express accounts traded drain days from 10-06 on a $1 "payout". - fundReconcileStep (trading-logic): an unexplained balance is booked only when a check at least 30s after the first sighting still sees it, to within $5. A transient is gone by the next pass and books nothing. - A transient that outlasts that, and so books, is taken back whole once its reversal nets the day under FUND_EVENT_MIN. The day's first booking saves what it replaced (fund_day_before), and dropFundTransaction deletes the row and puts last_fund_date and ended_cycle_start back in one transaction. A row reconcileFunds didn't book (an import) merges as before. - funds_reconciled is set only once a check settles, so a first-check lump that waits on a second pass is still booked as the first check. Existing tiny rows are left as they are; cleaning them up is a separate, one-time step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
aab225a19c
commit
d7d1e2cdae
@@ -328,6 +328,22 @@ export function loadFundTransactions(accountId: number): { date: string; amount:
|
||||
return db.prepare('SELECT date, amount FROM fund_transactions WHERE account_id = ? ORDER BY date').all(accountId) as { date: string; amount: number }[];
|
||||
}
|
||||
|
||||
/** Deletes one day's fund transaction and sets account meta keys back in the
|
||||
* same transaction (null deletes the key), so no reader sees the row gone
|
||||
* with the cycle it started still in place. TopstepX's reconcile uses it to
|
||||
* take back a day whose bookings netted to fee noise. */
|
||||
export function dropFundTransaction(accountId: number, date: string, meta: { [key: string]: string | null }): void {
|
||||
const del = db.prepare('DELETE FROM account_meta WHERE account_id = ? AND key = ?');
|
||||
const txn = db.transaction(() => {
|
||||
db.prepare('DELETE FROM fund_transactions WHERE account_id = ? AND date = ?').run(accountId, date);
|
||||
for (const [key, value] of Object.entries(meta)) {
|
||||
if (value === null) del.run(accountId, key);
|
||||
else saveAccountMeta(accountId, key, value);
|
||||
}
|
||||
});
|
||||
txn();
|
||||
}
|
||||
|
||||
// ── Account Meta ─────────────────────────────────────────────────────────────
|
||||
|
||||
db.exec(`
|
||||
|
||||
Reference in New Issue
Block a user