Symbol stats: the win chance with no commission, spread or swap beside the real one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-26 18:47:50 -05:00
co-authored by Claude Opus 5.5
parent 957b9a50fe
commit 302e9b4911
6 changed files with 21 additions and 6 deletions
+1
View File
@@ -149,6 +149,7 @@ export async function GET() {
multiplier: best.multiplier, multiplier: best.multiplier,
volatilityPerDay: best.volatilityPerDay, volatilityPerDay: best.volatilityPerDay,
winProbability: best.winProbability, winProbability: best.winProbability,
winProbabilityNoFees: best.winProbabilityNoFees,
swapReserve: best.swapReserve, swapReserve: best.swapReserve,
leverage: best.leverage, leverage: best.leverage,
cost: best.cost, cost: best.cost,
+1
View File
@@ -400,6 +400,7 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
+ (account.holdEstimate.leverage !== null ? ` (${account.holdEstimate.leverage}× max)` : '') + (account.holdEstimate.leverage !== null ? ` (${account.holdEstimate.leverage}× max)` : '')
+ ` at a measured volatility of ${(account.holdEstimate.volatilityPerDay * 100).toFixed(2)}%/day over the past week` + ` 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.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` : '') + (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`} + `. Expected cost ~$${fmt(account.holdEstimate.cost)} (spread, commissions, swap) — the cheapest candidate that reaches the target hold`}
> >
+13 -5
View File
@@ -14,7 +14,11 @@ import {
planLegSize, rankForTarget, maxLossRoom, lastStand, DEFAULT_TARGET_HOLD_HOURS, planLegSize, rankForTarget, maxLossRoom, lastStand, DEFAULT_TARGET_HOLD_HOURS,
} from './trading-logic'; } 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 { interface FirmSizing {
commissionPct?: number; 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 funds = o.client.positions?.[o.accountId] ? o.cash.amount : (o.cash.availableFunds ?? o.cash.amount);
const sized = candidates.flatMap((c): CandidateLeg[] => { const sized = candidates.flatMap((c): CandidateLeg[] => {
if (!c.volatilityPerHour) return []; if (!c.volatilityPerHour) return [];
const p = planLegSize({ const plan = (costs: boolean) => planLegSize({
leg: o.leg, leg: o.leg,
contract: c, contract: costs ? c : { ...c, spreadFraction: 0 },
commissionPct: c.commissionPct ?? o.firm.commissionPct ?? 0, commissionPct: costs ? (c.commissionPct ?? o.firm.commissionPct ?? 0) : 0,
targetHoldHours: target, targetHoldHours: target,
financingPctPerDay: o.firm.financingPctPerDay ?? 0, financingPctPerDay: costs ? (o.firm.financingPctPerDay ?? 0) : 0,
dailyLossLimit: o.cfg.dailyLossLimit ?? 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, dllRoom: o.dllRoom ?? Infinity,
maxLossRoom: floorRoom, maxLossRoom: floorRoom,
obfuscationDollars: o.obfuscationDollars, obfuscationDollars: o.obfuscationDollars,
availableFunds: funds, availableFunds: funds,
}); });
const p = plan(true);
if (!p.sizing || p.cost === null) return []; if (!p.sizing || p.cost === null) return [];
const leverage = c.marginRate ? Math.round(10 / c.marginRate) / 10 : null; const leverage = c.marginRate ? Math.round(10 / c.marginRate) / 10 : null;
return [{ return [{
@@ -70,6 +77,7 @@ export function rankNextLeg(o: {
holdHours: Math.round(p.sizing.predictedHoldHours * 10) / 10, holdHours: Math.round(p.sizing.predictedHoldHours * 10) / 10,
cost: Math.round(p.cost), cost: Math.round(p.cost),
winProbability: p.sizing.winProbability, winProbability: p.sizing.winProbability,
winProbabilityNoFees: plan(false).sizing?.winProbability ?? null,
volatilityPerDay: c.volatilityPerHour * Math.sqrt(24), volatilityPerDay: c.volatilityPerHour * Math.sqrt(24),
spreadFraction: c.spreadFraction ?? 0, spreadFraction: c.spreadFraction ?? 0,
swapReserve: p.sizing.swapReserve, swapReserve: p.sizing.swapReserve,
+3
View File
@@ -139,6 +139,9 @@ export interface SymbolStats {
/** Expected spread, commissions and swap. */ /** Expected spread, commissions and swap. */
cost: number; cost: number;
winProbability: number | null; 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; volatilityPerDay: number;
spreadFraction: number; spreadFraction: number;
}[]; }[];
+1 -1
View File
@@ -427,7 +427,7 @@ function symbolStatsFor(
at: new Date().toISOString(), at: new Date().toISOString(),
symbols: next.candidates.map((c) => ({ symbols: next.candidates.map((c) => ({
symbol: c.symbol, notional: c.notional, multiplier: c.multiplier, maxMultiplier: c.maxMultiplier, 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, volatilityPerDay: c.volatilityPerDay, spreadFraction: c.spreadFraction,
})), })),
}, },
+2
View File
@@ -103,6 +103,8 @@ export interface AccountState {
multiplier: number; multiplier: number;
volatilityPerDay: number; volatilityPerDay: number;
winProbability: number | null; 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. */ /** Expected swap a winning leg pays, added to its take-profit. */
swapReserve: number; swapReserve: number;
/** The broker's leverage on the symbol (5 = 5:1); null when unstated. */ /** The broker's leverage on the symbol (5 = 5:1); null when unstated. */