CME evening session: first entry at 5:45 PM CT, not 5:05

Tradovate moves each account's cash record to the new trade date on its own
schedule, an idle one perhaps not until it trades, and the bot holds any
account still on the old day until 5:45. Opening at 5:05 fired the ready
accounts alone (TopstepX, the early rollers) while the rest, released at
5:45, waited an hour or more for them to go flat. A 5:45 open puts every
account in the first batch and skips the open's widest spreads. The day's
0–180 minute delay still comes on top: first entries land 5:45–8:45 PM.

Every-day firms keep their own 5:05 PM end of the roll guard; the equity
hours open is unchanged. The draw's session progress now starts at 5:45.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-27 17:45:08 -05:00
co-authored by Claude Opus 5.5
parent ff97a59b49
commit db0ad99a76
4 changed files with 43 additions and 22 deletions
+4 -3
View File
@@ -63,11 +63,12 @@ test('daily move from hourly closes; contracts; expected hold', () => {
assert.equal(expectedHoldHours({ leg: 1000, stopDollars: Infinity, contracts: 1, dailyMove: null }), null);
});
test('session progress: full CME 17:05 → 14:55 CT, equity hours 8:35 → 14:55', () => {
test('session progress: full CME 17:45 → 14:55 CT, equity hours 8:35 → 14:55', () => {
// 2026-09-28 is a Monday; CDT is UTC−5.
const ct = (iso) => new Date(`${iso}-05:00`);
assert.equal(sessionProgress(ct('2026-09-27T17:05'), 'full_cme'), 0);
assert.ok(Math.abs(sessionProgress(ct('2026-09-28T04:00'), 'full_cme') - 655 / 1310) < 1e-9);
assert.equal(sessionProgress(ct('2026-09-27T17:45'), 'full_cme'), 0);
assert.equal(sessionProgress(ct('2026-09-27T17:30'), 'full_cme'), null); // Sunday before the open
assert.ok(Math.abs(sessionProgress(ct('2026-09-28T04:00'), 'full_cme') - 615 / 1270) < 1e-9);
assert.equal(sessionProgress(ct('2026-09-28T14:55'), 'full_cme'), 1);
assert.equal(sessionProgress(ct('2026-09-28T15:30'), 'full_cme'), null); // maintenance
assert.equal(sessionProgress(ct('2026-09-26T12:00'), 'full_cme'), null); // Saturday
+15 -10
View File
@@ -97,7 +97,7 @@ function oldNoTrade(now, mode, jitter) {
return timeMinutes < open || timeMinutes >= close;
}
if (day === 'Sat') return true;
const cmeOpen = 17 * 60 + 5 + jitter;
const cmeOpen = 17 * 60 + 45 + jitter;
if (day === 'Sun') return timeMinutes < cmeOpen;
const cmeClose = 14 * 60 + 55;
if (day === 'Fri' && timeMinutes >= cmeClose) return true;
@@ -113,7 +113,7 @@ function oldAfterClose(now) {
return minutes >= 14 * 60 + 55 && minutes < 17 * 60;
}
test('CME trading hours are unchanged, across both DST regimes', () => {
test('CME trading hours: 5:45 PM plus the delay to 2:55 PM, across both DST regimes', () => {
for (const start of [Date.UTC(2026, 8, 20), Date.UTC(2026, 0, 11)]) { // a September and a January week
for (let m = 0; m < 7 * 24 * 60; m += 7) {
const now = new Date(start + m * 60_000);
@@ -133,16 +133,21 @@ const ct = (iso, offsetHours) => new Date(Date.parse(`${iso}Z`) + offsetHours *
const cdt = (iso) => ct(iso, 5); // Central daylight: UTC−5
const cst = (iso) => ct(iso, 6); // Central standard: UTC−6
test('a 180-minute open delay: the evening session starts at 8:05 PM CT', () => {
test('the evening session opens at 5:45 PM CT, and a 180-minute delay makes it 8:45', () => {
const o = { everyDay: false, mode: 'full_cme', jitterMin: 180 };
assert.equal(isNoTradeTime(cdt('2026-09-28T17:05:00'), o), true); // Monday, the usual open
assert.equal(isNoTradeTime(cdt('2026-09-28T20:04:00'), o), true);
assert.equal(isNoTradeTime(cdt('2026-09-28T20:05:00'), o), false);
assert.equal(isNoTradeTime(cdt('2026-09-27T20:04:00'), o), true); // Sunday too
assert.equal(isNoTradeTime(cdt('2026-09-27T20:05:00'), o), false);
assert.equal(isNoTradeTime(cdt('2026-09-28T17:45:00'), o), true); // Monday
assert.equal(isNoTradeTime(cdt('2026-09-28T20:44:00'), o), true);
assert.equal(isNoTradeTime(cdt('2026-09-28T20:45:00'), o), false);
assert.equal(isNoTradeTime(cdt('2026-09-27T20:44:00'), o), true); // Sunday too
assert.equal(isNoTradeTime(cdt('2026-09-27T20:45:00'), o), false);
assert.equal(isNoTradeTime(cdt('2026-09-29T10:00:00'), o), false); // the day session is untouched
// No delay: 5:05 PM as always.
assert.equal(isNoTradeTime(cdt('2026-09-28T17:05:00'), { ...o, jitterMin: 0 }), false);
// No delay: 5:45 PM — not 5:05, while Tradovate rolls the accounts' days.
const now = { ...o, jitterMin: 0 };
assert.equal(isNoTradeTime(cdt('2026-09-28T17:05:00'), now), true);
assert.equal(isNoTradeTime(cdt('2026-09-28T17:44:00'), now), true);
assert.equal(isNoTradeTime(cdt('2026-09-28T17:45:00'), now), false);
// Every-day firms keep their own 5:05 PM end of the roll guard.
assert.equal(isNoTradeTime(cdt('2026-09-28T17:05:00'), { ...now, everyDay: true }), false);
});
test('every-day firms close only around the roll (CDT)', () => {