Tradovate: yesterday's realized P&L no longer reads as today's after the roll
Tradovate rolls each account's cash record to the new trade date on its own schedule (18 minutes after the 17:00 CT open on 2026-09-24, or not until the account trades), and until then the latest record still carries the ended day's realizedPnL. Plans sized legs off it, one-and-done accounts read as already traded, the reporter scored every account partial/loss, and an entry across the record roll took a whole day's P&L as its "commission", parking the take-profit out of reach. - Cash records keep their trade date; realized P&L from an earlier trade date reads as 0 (judged when read, so a record stored before the roll counts as ended after it). Archived or older records never overwrite a newer one, whichever order the roll pair arrives in. - The entry-commission delta falls back to $2.50/contract when it exceeds any real fee ($10/contract). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
77d8c2ecdb
commit
e49640498a
+25
-2
@@ -353,6 +353,29 @@ async function legInstrument(
|
||||
return { contract: mnq, pointValue: POINT_VALUES['MNQ'] };
|
||||
}
|
||||
|
||||
/** An entry's fee when the balance update hasn't shown it yet — at least one
|
||||
* extra tick on the exit. */
|
||||
const FALLBACK_COMMISSION_PER_CONTRACT = 2.5;
|
||||
/** No real entry fee comes near this per contract. */
|
||||
const MAX_COMMISSION_PER_CONTRACT = 10;
|
||||
|
||||
/**
|
||||
* The entry fee, read off the realized-P&L delta around the fill. Anything
|
||||
* past a real per-contract fee is something else moving realized P&L — a
|
||||
* broker record rolling to the new trade date under the entry swapped a whole
|
||||
* day's P&L into the "fee", and doubled into the exit target that parked the
|
||||
* take-profit out of reach — so it falls back like a missing update does.
|
||||
*/
|
||||
function entryCommissionFrom(before: number, after: number, contracts: number): number {
|
||||
const delta = Math.abs(after - before);
|
||||
if (delta > 0 && delta <= MAX_COMMISSION_PER_CONTRACT * contracts) return delta;
|
||||
if (delta > 0) {
|
||||
console.warn(`[auto-trade] realized P&L moved $${Math.round(delta * 100) / 100} around a ${contracts}-contract entry`
|
||||
+ ` — not a fee; using $${FALLBACK_COMMISSION_PER_CONTRACT}/contract`);
|
||||
}
|
||||
return FALLBACK_COMMISSION_PER_CONTRACT * contracts;
|
||||
}
|
||||
|
||||
// ── per-account trade evaluation (shared by runTrade / copyTrade / hasRemaining…) ──
|
||||
|
||||
/** The ineligible reason for an account whose history isn't loaded yet — still
|
||||
@@ -776,7 +799,7 @@ async function runTradeLocked(
|
||||
// After entry, realizedPnL drops by the entry commission; the delta from the
|
||||
// pre-entry value is the entry fee paid (pre-entry may be nonzero mid-day).
|
||||
// Fall back to $2.50/contract if the WS hasn't updated yet (guarantees at least 1 extra tick).
|
||||
const entryCommission = Math.abs(updatedCash.realizedPnL - item.cash.realizedPnL) || (2.5 * contracts);
|
||||
const entryCommission = entryCommissionFrom(item.cash.realizedPnL, updatedCash.realizedPnL, contracts);
|
||||
const totalCommission = entryCommission * 2; // entry + exit round-trip
|
||||
const grossTarget = legAmount + totalCommission;
|
||||
|
||||
@@ -1064,7 +1087,7 @@ async function copyTradeLocked(lease: number): Promise<unknown[]> {
|
||||
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
const updatedCash = client.accountCashBalances[acc.id] ?? { amount: 0, realizedPnL: 0 };
|
||||
const entryCommission = Math.abs(updatedCash.realizedPnL - item.cash.realizedPnL) || (2.5 * contracts);
|
||||
const entryCommission = entryCommissionFrom(item.cash.realizedPnL, updatedCash.realizedPnL, contracts);
|
||||
const totalCommission = entryCommission * 2;
|
||||
const grossTarget = legAmount + totalCommission;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user