Sizing: the broker's largest and smallest order bind the leg's size

fireLeg clamped the order to the instrument's max and min size, but the
size the pick ranked by (and the Firms page shows) didn't: an instrument
whose largest order falls short of the ideal notional ranked on a hold it
can't reach. planLegSize now holds the notional to the margin cap and the
broker's largest order, then its smallest, so the ranking, the stats and
the trade agree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-27 13:59:27 -05:00
co-authored by Claude Opus 5.5
parent 7f55e49755
commit 1782c16145
2 changed files with 47 additions and 4 deletions
+19 -4
View File
@@ -1398,6 +1398,9 @@ export function sizeNotional(o: {
financingPctPerDay?: number;
/** Hard cap on the position (see notionalCap); 0 = none. */
maxNotional: number;
/** The smallest order the broker takes; 0 = none. It wins over the cap,
* as the order does. */
minNotional?: number;
}): { notional: number; predictedHoldHours: number; winProbability: number | null; swapReserve: number } | null {
const sigma = o.volatilityPerHour ?? 0;
if (!(sigma > 0)) return null;
@@ -1428,7 +1431,7 @@ export function sizeNotional(o: {
n = Math.max(nTime, nCost);
}
if (!Number.isFinite(n) || n <= 0) return null;
const notional = o.maxNotional > 0 ? Math.min(n, o.maxNotional) : n;
const notional = Math.max(o.maxNotional > 0 ? Math.min(n, o.maxNotional) : n, o.minNotional ?? 0);
const { a, b, stopped, swap } = distances(notional);
return {
notional,
@@ -1448,10 +1451,19 @@ export function sizeNotional(o: {
* the far side is the firm's own breach. `cost` is what the leg is expected
* to spend: spread and both commissions on its notional, plus swap over its
* expected hold.
*
* The size is held to what the order can be — the margin cap and the
* broker's largest order, then its smallest — so the pick ranks each
* instrument by the hold it can actually reach. `cap` is the margin cap
* alone.
*/
export function planLegSize(o: {
leg: number;
contract: { stepNotional?: number; volatilityPerHour?: number; spreadFraction?: number; marginRate?: number };
contract: {
stepNotional?: number; volatilityPerHour?: number; spreadFraction?: number; marginRate?: number;
/** The broker's largest and smallest order, in steps. */
maxContracts?: number; minContracts?: number;
};
/** Per side, percent. */
commissionPct: number;
targetHoldHours: number;
@@ -1481,7 +1493,9 @@ export function planLegSize(o: {
: dllStopDollars({ room: o.dllRoom, dailyLossLimit: o.dailyLossLimit, entryFee: r * notional, commissionPct: o.commissionPct, notional }),
);
const cap = notionalCap({ marginRate: o.contract.marginRate, availableFunds: o.availableFunds });
const sizing = (o.contract.stepNotional ?? 0) > 0
const step = o.contract.stepNotional ?? 0;
const orderMax = (o.contract.maxContracts ?? 0) > 0 ? o.contract.maxContracts! * step : 0;
const sizing = step > 0
? sizeNotional({
leg: o.leg,
stopDollarsAt: walkAt,
@@ -1490,7 +1504,8 @@ export function planLegSize(o: {
commissionPct: o.commissionPct,
spreadFraction: o.contract.spreadFraction,
financingPctPerDay: o.financingPctPerDay,
maxNotional: cap,
maxNotional: cap > 0 && orderMax > 0 ? Math.min(cap, orderMax) : cap || orderMax,
minNotional: Math.max(1, o.contract.minContracts ?? 1) * step,
})
: null;
const cost = sizing