Futures legs respect a firm's per-instrument contract limit

A firm can cap how many contracts of an instrument its accounts may hold
(the catalog's instrumentLimits, set on the master's Firms page). A leg
trades one contract per $1,000, capped by the account type's max contracts
and that limit. The Auto draw sizes each instrument's hold with the capped
count, so a limit that slows a leg past 12 hours takes it out of the draw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-28 13:45:44 -05:00
co-authored by Claude Opus 5.5
parent b125257d4e
commit 58e7414849
6 changed files with 71 additions and 13 deletions
+23 -1
View File
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict';
import {
CME_SPECS, CME_ROOTS, POINT_VALUES, MICRO_OF, tickValue, spreadDollars, dailyMoveDollars, futuresContracts,
expectedHoldHours, sessionProgress, drawChances, pickByChance, housekeepingRoot, sanitizeInstruments,
resolveAllowlist, MAX_HOLD_HOURS, DRAW_FLOOR, DEFAULT_MAX_ENTRY_SPREAD, spreadTooWide,
resolveAllowlist, MAX_HOLD_HOURS, DRAW_FLOOR, DEFAULT_MAX_ENTRY_SPREAD, spreadTooWide, sanitizeInstrumentLimits,
} from '../lib/instrument-specs.ts';
import { mergeTopstepxQuote, parseTradovateFrame, bookSpread } from '../lib/quote-helpers.ts';
@@ -234,3 +234,25 @@ test('the draw leaves out what is over the spread limit', () => {
// All of them wide: nothing to draw.
assert.deepEqual(drawChances(cands.map((c) => ({ ...c, wide: true })), 0.5).chances, []);
});
test('a firm\'s per-instrument limit caps the contracts, with the account type\'s max', () => {
// One per $1,000 of leg ...
assert.equal(futuresContracts(4200, 0), 5);
// ... capped by the account type's max, the firm's limit, whichever is lower.
assert.equal(futuresContracts(4200, 0, 3), 3);
assert.equal(futuresContracts(4200, 4, 2), 2);
assert.equal(futuresContracts(4200, 2, 4), 2);
assert.equal(futuresContracts(4200, 0, 0), 5);
// Never under one contract.
assert.equal(futuresContracts(300, 0, 5), 1);
// A capped leg holds longer: 5 contracts on NQ vs a cap of 2.
const free = expectedHoldHours({ leg: 5000, stopDollars: Infinity, contracts: futuresContracts(5000, 0), dailyMove: 5227 });
const capped = expectedHoldHours({ leg: 5000, stopDollars: Infinity, contracts: futuresContracts(5000, 0, 2), dailyMove: 5227 });
assert.ok(Math.abs(capped / free - 6.25) < 1e-9, `${capped} vs ${free}`);
});
test('instrument limits off the catalog: CME roots, whole numbers 1-1000', () => {
assert.deepEqual(sanitizeInstrumentLimits({ NQ: 15, CL: 5, GC: 0, ES: 2.5, ZZ: 3, SI: 2000, YM: '4' }), { NQ: 15, CL: 5 });
assert.deepEqual(sanitizeInstrumentLimits(null), {});
assert.deepEqual(sanitizeInstrumentLimits(['NQ']), {});
});