Files
momentum-system/tests/unit/experiments/test_registry.py
T
momentum devandClaude Fable 5 315afcf497 experiment-os: volume-fade exit experiment + fix fire reconstruction (0-fires bug)
The live loop fills resting-limit brackets via position-snapshot diffing and
emits NO order_filled (verified: 0 all-time in prod), so reconstruct_fires
skipped every fire and the ENTIRE harness replayed nothing (0 run rows).
Reconstruction now falls back to the order_submitted timestamp for entry timing
and reads entry/stop/exit from position_closed — restores the whole flagship
(0 -> 324 run rows on prod; pm1's overdue FAIL verdict now posts).

Adds the volume_fade intervention (exit to lock the gain when a bar's volume
drops below fade_frac x the entry-burst average, once in profit; original stop
still caps downside; adverse-tick fills on entry AND exit) + volume_fade_exit_v1
(pre-registered bars/futility/fidelity). Interim n=15 read: -0.68R vs baseline
(INCONCLUSIVE, min_n=100) — the harness answers 'does volume-fade help?' from
data before any live change. +6 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:36:16 -04:00

66 lines
2.8 KiB
Python

"""Registry: schema apply, immutability, required-key/fidelity validation."""
import pytest
from research.experiments import registry
from tests.unit.experiments.conftest import base_spec, write_spec
def test_tables_exist(store):
# apply_schema (fixture) created all three; a select proves it.
for tbl in ("experiments", "experiment_runs", "experiment_verdicts"):
assert store.con.execute(f"SELECT count(*) FROM {tbl}").fetchone()[0] == 0
def test_register_then_get(store, tmp_path):
exp_id = registry.register(store.con, write_spec(tmp_path, base_spec()))
assert exp_id == "demo_v1"
got = registry.get(store.con, "demo_v1")
assert got["hypothesis"] == "demo hypothesis"
assert got["population"] == {"min_entry_gap_pct": 0.10} # jsonb round-trips to dict
assert got["status"] == "running"
def test_immutable_refuses_overwrite(store, tmp_path):
path = write_spec(tmp_path, base_spec())
registry.register(store.con, path)
with pytest.raises(ValueError, match="already registered"):
registry.register(store.con, path)
def test_missing_fidelity_refused(store, tmp_path):
spec = base_spec()
del spec["fidelity"]
with pytest.raises(ValueError, match="required keys"):
registry.register(store.con, write_spec(tmp_path, spec))
def test_incomplete_fidelity_floors_refused(store, tmp_path):
spec = base_spec(fidelity={"atr_frac": 0.25}) # missing spread_mult + adverse_ticks
with pytest.raises(ValueError, match="fidelity missing"):
registry.register(store.con, write_spec(tmp_path, spec))
def test_list_running_only(store, tmp_path):
registry.register(store.con, write_spec(tmp_path, base_spec(id="a_v1")))
registry.register(store.con, write_spec(tmp_path, base_spec(id="b_v1")))
store.con.execute("UPDATE experiments SET status='failed' WHERE id='b_v1'")
running = registry.list_experiments(store.con, running_only=True)
assert [e["id"] for e in running] == ["a_v1"]
def test_register_cohort_bootstraps_and_is_idempotent(store):
"""run's self-bootstrap: register_cohort registers every experiments/*.json
from the real cohort dir, and a second call adds nothing (idempotent)."""
from research.experiments import registry
first = registry.register_cohort(store.con)
assert len(first) >= 6 # the shipped cohort (>=6 after volume_fade)
assert {"pm1_v1", "time_stop_10m_v1", "volume_fade_exit_v1"} <= set(first)
# every one landed as a running experiment
running = {e["id"] for e in registry.list_experiments(store.con, running_only=True)}
assert set(first) <= running
# idempotent: re-running registers nothing new, raises nothing
second = registry.register_cohort(store.con)
assert second == []
assert len(registry.list_experiments(store.con)) == len(first)