Version: CI now tags images YYYY.MM.DD-<shortsha> alongside :latest/:sha and passes GIT_SHA/BUILD_TAG build-args; the running app reports them via dashboard /api/health and admin /api/status (TrueNAS custom apps only show 'custom/1.0.0', so the app self-reports what it actually runs). Config parity: every scheduler-run script (nightly_tune, eod_replay, premarket_eval, regime, market_context, experiments cli) now loads config_overrides at startup like the live services do — a bare env would replay under DEFAULT exits (the documented 4.5x-baseline landmine). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
"""CLI: ``python -m research.experiments {register|run|adjudicate} [args]``.
|
|
|
|
Thin wrapper. Builds ONE EventStore (its Postgres connection is shared for the
|
|
experiment tables) and dispatches. Deferred imports so each subcommand only
|
|
pulls what it needs. NEVER run against the production DB from a test — the
|
|
harness's real verdict lands when the owner first schedules it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
|
|
# config parity with the live loop: DB-backed knobs (config_overrides) beat
|
|
# compose env — a bare env would replay under DEFAULT exits (the 4.5x-baseline
|
|
# landmine, orderflow-shelf-verdicts 2026-07-17)
|
|
from runtime.env_overrides import load as _load_cfg_overrides # noqa: E402
|
|
_load_cfg_overrides()
|
|
|
|
|
|
def _store():
|
|
from storage.event_store import EventStore
|
|
return EventStore(os.environ.get("WATCHER_EVENT_DB_PATH", "momentum"))
|
|
|
|
|
|
def main(argv=None) -> int:
|
|
argv = list(sys.argv[1:] if argv is None else argv)
|
|
if not argv:
|
|
print("usage: python -m research.experiments {register <json>|run|adjudicate}")
|
|
return 2
|
|
cmd, rest = argv[0], argv[1:]
|
|
|
|
if cmd == "register":
|
|
if not rest:
|
|
print("usage: python -m research.experiments register <json_path>")
|
|
return 2
|
|
from research.experiments import registry
|
|
store = _store()
|
|
try:
|
|
registry.apply_schema(store.con)
|
|
exp_id = registry.register(store.con, rest[0])
|
|
print(f"registered {exp_id}")
|
|
finally:
|
|
store.close()
|
|
return 0
|
|
|
|
if cmd == "run":
|
|
from research.experiments import registry, replay
|
|
store = _store()
|
|
try:
|
|
# self-bootstrap: register the declared cohort (idempotent) so a fresh
|
|
# DB replays the experiments instead of silently running over nothing.
|
|
newly = registry.register_cohort(store.con)
|
|
if newly:
|
|
print(f"registered cohort: {', '.join(newly)}")
|
|
n = replay.run(store)
|
|
print(f"experiments run: {n} new run rows")
|
|
finally:
|
|
store.close()
|
|
return 0
|
|
|
|
if cmd == "adjudicate":
|
|
from research.experiments import adjudicate
|
|
store = _store()
|
|
try:
|
|
verdicts = adjudicate.adjudicate(store)
|
|
for v in verdicts:
|
|
print(f"{v['experiment_id']}: {v['verdict']} (n={v['n']})")
|
|
finally:
|
|
store.close()
|
|
return 0
|
|
|
|
print(f"unknown command: {cmd}")
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|