From 302e9b49112d481cccf7fdf2439c0fbc71d27d43 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Sat, 26 Sep 2026 18:47:50 -0500 Subject: [PATCH] Symbol stats: the win chance with no commission, spread or swap beside the real one Co-Authored-By: Claude Opus 5.5 --- app/api/state/route.ts | 1 + app/page.tsx | 1 + lib/leg-estimate.ts | 18 +++++++++++++----- lib/master-protocol.ts | 3 +++ lib/reporter.ts | 2 +- types.ts | 2 ++ 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/api/state/route.ts b/app/api/state/route.ts index 880facb..aa5d28c 100644 --- a/app/api/state/route.ts +++ b/app/api/state/route.ts @@ -149,6 +149,7 @@ export async function GET() { multiplier: best.multiplier, volatilityPerDay: best.volatilityPerDay, winProbability: best.winProbability, + winProbabilityNoFees: best.winProbabilityNoFees, swapReserve: best.swapReserve, leverage: best.leverage, cost: best.cost, diff --git a/app/page.tsx b/app/page.tsx index a0b018c..4b969d2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -400,6 +400,7 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: { + (account.holdEstimate.leverage !== null ? ` (${account.holdEstimate.leverage}× max)` : '') + ` at a measured volatility of ${(account.holdEstimate.volatilityPerDay * 100).toFixed(2)}%/day over the past week` + (account.holdEstimate.winProbability !== null ? `, wins ~${Math.round(account.holdEstimate.winProbability * 100)}% of the time` : '') + + (account.holdEstimate.winProbabilityNoFees !== null ? ` (~${Math.round(account.holdEstimate.winProbabilityNoFees * 100)}% with no commission, spread or swap)` : '') + (account.holdEstimate.swapReserve > 0 ? `; its take-profit carries ~$${fmt(account.holdEstimate.swapReserve)} of expected swap` : '') + `. Expected cost ~$${fmt(account.holdEstimate.cost)} (spread, commissions, swap) — the cheapest candidate that reaches the target hold`} > diff --git a/lib/leg-estimate.ts b/lib/leg-estimate.ts index a848515..3ce2291 100644 --- a/lib/leg-estimate.ts +++ b/lib/leg-estimate.ts @@ -14,7 +14,11 @@ import { planLegSize, rankForTarget, maxLossRoom, lastStand, DEFAULT_TARGET_HOLD_HOURS, } from './trading-logic'; -export type CandidateLeg = SymbolStats['symbols'][number] & { swapReserve: number; leverage: number | null }; +export type CandidateLeg = SymbolStats['symbols'][number] & { + swapReserve: number; + leverage: number | null; + winProbabilityNoFees: number | null; +}; interface FirmSizing { commissionPct?: number; @@ -47,18 +51,21 @@ export function rankNextLeg(o: { const funds = o.client.positions?.[o.accountId] ? o.cash.amount : (o.cash.availableFunds ?? o.cash.amount); const sized = candidates.flatMap((c): CandidateLeg[] => { if (!c.volatilityPerHour) return []; - const p = planLegSize({ + const plan = (costs: boolean) => planLegSize({ leg: o.leg, - contract: c, - commissionPct: c.commissionPct ?? o.firm.commissionPct ?? 0, + contract: costs ? c : { ...c, spreadFraction: 0 }, + commissionPct: costs ? (c.commissionPct ?? o.firm.commissionPct ?? 0) : 0, targetHoldHours: target, - financingPctPerDay: o.firm.financingPctPerDay ?? 0, + financingPctPerDay: costs ? (o.firm.financingPctPerDay ?? 0) : 0, dailyLossLimit: o.cfg.dailyLossLimit ?? 0, + // Without fees the day's room would also have its fees back — but + // only this leg's are known, so the room stays as it is. dllRoom: o.dllRoom ?? Infinity, maxLossRoom: floorRoom, obfuscationDollars: o.obfuscationDollars, availableFunds: funds, }); + const p = plan(true); if (!p.sizing || p.cost === null) return []; const leverage = c.marginRate ? Math.round(10 / c.marginRate) / 10 : null; return [{ @@ -70,6 +77,7 @@ export function rankNextLeg(o: { holdHours: Math.round(p.sizing.predictedHoldHours * 10) / 10, cost: Math.round(p.cost), winProbability: p.sizing.winProbability, + winProbabilityNoFees: plan(false).sizing?.winProbability ?? null, volatilityPerDay: c.volatilityPerHour * Math.sqrt(24), spreadFraction: c.spreadFraction ?? 0, swapReserve: p.sizing.swapReserve, diff --git a/lib/master-protocol.ts b/lib/master-protocol.ts index 05cc5cc..8d8fffe 100644 --- a/lib/master-protocol.ts +++ b/lib/master-protocol.ts @@ -139,6 +139,9 @@ export interface SymbolStats { /** Expected spread, commissions and swap. */ cost: number; winProbability: number | null; + /** The same leg and daily room with no commission, spread or swap — + * what the costs take off the win chance. Optional. */ + winProbabilityNoFees?: number | null; volatilityPerDay: number; spreadFraction: number; }[]; diff --git a/lib/reporter.ts b/lib/reporter.ts index 834bfd9..bed926b 100644 --- a/lib/reporter.ts +++ b/lib/reporter.ts @@ -427,7 +427,7 @@ function symbolStatsFor( at: new Date().toISOString(), symbols: next.candidates.map((c) => ({ symbol: c.symbol, notional: c.notional, multiplier: c.multiplier, maxMultiplier: c.maxMultiplier, - holdHours: c.holdHours, cost: c.cost, winProbability: c.winProbability, + holdHours: c.holdHours, cost: c.cost, winProbability: c.winProbability, winProbabilityNoFees: c.winProbabilityNoFees, volatilityPerDay: c.volatilityPerDay, spreadFraction: c.spreadFraction, })), }, diff --git a/types.ts b/types.ts index d520b14..8c8cde1 100644 --- a/types.ts +++ b/types.ts @@ -103,6 +103,8 @@ export interface AccountState { multiplier: number; volatilityPerDay: number; winProbability: number | null; + /** The same leg with no commission, spread or swap. */ + winProbabilityNoFees: number | null; /** Expected swap a winning leg pays, added to its take-profit. */ swapReserve: number; /** The broker's leverage on the symbol (5 = 5:1); null when unstated. */