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:
co-authored by
Claude Opus 5.5
parent
957b9a50fe
commit
302e9b4911
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
Reference in New Issue
Block a user