Arm harness on our scheduler + start closing-line capture (capture only)
PART A — HARNESS ARMED ON OUR OWN INFRA. snapshotScheduler now runs the
nightly backtest at HARNESS_HOUR_UTC (default 14), appends to
harness_results, and pages via opsWatch.harnessStaleAlarm — a validator
that stops running looks exactly like one that keeps passing. No external
dependency: the join is plain SQL through the service client and the
harness is a pure function. POST /api/internal/harness/run induces the
same code path on demand, because a scheduled mechanism is verified by
inducing it, never by waiting for a slot.
PART B PHASE 0 — GATE PASSED for what is capturable:
- C4 diagnosed: closing_line is ONE overwritable field with no timestamp
and no provenance. captureClosing writes the current line and, when a
prop fails to match, silently leaves the earlier value (= the lock) in
place — so "captured a real close" is indistinguishable from "never
updated". It is 92% equal, not 100%: 56 rows DID record movement, so
the defect is provenance, not the value.
- Feeds: normalized props already carry BOTH raw side prices per book,
with game_time, and the intraday refresh polls every ~20 min during
slate hours — so the last observable pre-lock line is available.
- SHARP close: pinnacle is in ALLOWED_BOOKS -> a no-vig reference is
capturable ("beat the market").
- ODAWA: NOT capturable. 'odawa' exists only as a UI preference option in
onboarding/settings; it is in no adapter, no ALLOWED_BOOKS, no feed. An
un-capturable source is a finding, not a gap to paper over.
- JOIN: must drop `line` from the natural key, because a close that MOVED
off the graded line is the entire point of CLV. Verified safe — all 164
current identity groups have exactly ONE line per
(sport, player_key, stat, side, game_date). Zero ambiguity.
PART B PHASE 1 — CAPTURE ONLY, built test-first. The refusal was proven
before the capture logic existed: unbound game_time, doubleheader
ambiguity, a missed pre-lock window, or a one-sided price all record
missed_reason with NO price. A stale or mid-day line substituted for a
close would manufacture a CLV proof from a number that was never the
close.
migration 029 closing_captures: append-only, never overwritten (that is
the provenance C4 lacked), BOTH raw side prices so the existing de-vig
engine can compute a fair closing probability later, sharp vs book line
types kept distinct. Wired into the intraday refresh with a capture-rate
alarm — a missed close is unrecoverable.
NO CLV metric built, as ordered. This starts the clock.
Suite 284/3417 green, build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
This commit is contained in:
@@ -265,6 +265,27 @@ function startSnapshotScheduler(opts = {}) {
|
||||
} catch (e) {
|
||||
console.warn('[snapshot] cron run failed:', e.message);
|
||||
}
|
||||
// Session 64 — NIGHTLY BACKTEST HARNESS, on our own scheduler. Runs once
|
||||
// per day at HARNESS_HOUR_UTC and appends to harness_results, so the
|
||||
// calibration trend is visible as retention compounds. An
|
||||
// INSUFFICIENT_HISTORY verdict is expected and correct.
|
||||
try {
|
||||
const harnessHour = Number(process.env.HARNESS_HOUR_UTC || 14);
|
||||
if (h === harnessHour) {
|
||||
const runner = opts.harnessRunner || require('./services/harnessRunner');
|
||||
const res = await runner.runAndRecord();
|
||||
console.log(`[harness] nightly run — verdict=${res.verdict || 'n/a'} scored=${res.scored ?? 'n/a'}${res.error ? ` ERROR: ${res.error}` : ''}`);
|
||||
// A validator that stops running looks exactly like one that keeps
|
||||
// passing — so staleness pages.
|
||||
const stale = opsWatch.harnessStaleAlarm(await runner.lastRunAt(), now());
|
||||
if (stale.alarm) {
|
||||
await notify(`Backtest harness stale — ${stale.reason}. The metric gate is not running.`, {
|
||||
title: 'VYNDR harness', priority: 'high', tags: ['rotating_light'],
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) { console.warn('[harness] nightly run failed:', e.message); }
|
||||
|
||||
// Session 8 — quota check after each snapshot run: odds-api >= 80% alerts
|
||||
// once per day (Redis-deduped). Never throws (guarded inside checkQuotaDaily).
|
||||
await opsWatch.checkQuotaDaily({ getStatus: getQuotaStatus, cacheGet, cacheSet, notify, now: () => now() });
|
||||
|
||||
Reference in New Issue
Block a user