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>
66 lines
2.8 KiB
Python
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)
|