What the measurement harness computes, how positions pay costs, and the measured cost sensitivity of the selected book. Dated 2026-09-27, measured at the run 18 book (HEAD 3108547).
What it measures
27 causal signals over synthetic hourly crypto-path families — family 1 whale leads always strike, family 2 Student-t tails with ~30 % unidentifiable false lead-ins, family 3 (segment 4) jump diffusion + latent market beta + separable false lead-ins — combined into one weighted ensemble over 15 default paths. All three families are burned after segment 4. The loop objective is edge_score = 10 + median(all selected paths' net Sharpes) — the +10 offset keeps the metric positive; it is not a floor. Positions at bar t earn the close-to-close return t → t+1 and pay the cost model; Sharpe annualizes with sqrt(8760). The runner enforces causality (recompute-at-50 %/80 % prefix checks) and the catalog/weights audit set(SIGNAL_BY_NAME) == set(WEIGHTS).
Cost model
Under the default flat model each earning bar pays 0.0010 * abs(p[t] - p[t-1]) — 10 bps per unit of position change, no final liquidation charge: r[t] = p[t] * (close[t+1] / close[t] - 1) - 0.0010 * abs(p[t] - p[t-1]). Optional stylised stress terms — slippage growing with realised volatility, funding carry, short borrow, position cap — are cost sensitivity, not market-calibrated fills.
| Preset | fee_bps | slippage_k | funding_carry | borrow_bps_per_day | max_position |
|---|---|---|---|---|---|
flat (loop default) | 10 | 0.0 | False | 0.0 | 1.0 |
realistic | 10 | 0.5 | True | 5.0 | 1.0 |
harsh | 20 | 1.0 | True | 10.0 | 1.0 |
Funding carry charges p[t] * funding_rate[t] / 8 per hourly bar — the funding_rate column is an 8 h rate (perp funding settles every 8 h), so each hourly bar pays one eighth of it; longs pay positive funding, shorts receive. An earlier CostModel draft charged the full 8 h rate on every hourly bar (8× too much; it showed realistic family-1 median −0.72) and was corrected before the sweep below was measured.
--costs {flat,realistic,harsh} picks the preset; --costs-json '{"slippage_k": 0.25}' overrides single fields on top of it; unknown fields, invalid JSON, and non-finite/negative rates exit 2. --cost-sweep rescores the same selected paths under every preset — one compact table per family, no METRIC lines.
Measured cost sweep
python3 -B harness/run.py --cost-sweep at HEAD 43c0d28 (CostModel commit), run #16 book, 8 h funding carry, 2026-09-27 — median Sharpe / worst path / max drawdown per family:
| Preset | family 1 | family 2 | family 3 |
|---|---|---|---|
| flat | 4.817 / 3.398 / 0.067 | 2.085 / −1.131 / 0.104 | 4.353 / 2.863 / 0.070 |
| realistic | 2.526 / 1.623 / 0.100 | 0.455 / −3.564 / 0.173 | 2.627 / 0.858 / 0.090 |
| harsh | 0.229 / −0.208 / 0.155 | −1.303 / −6.348 / 0.254 | 0.638 / −1.354 / 0.155 |
- Realistic-cost medians: family 1 2.53, family 2 0.46, family 3 2.63 — the book keeps roughly half its flat edge under stylised realistic fills.
- Under harsh costs family 2 goes negative (median −1.30, worst path −6.35): the higher real costs hit the hourly, high-turnover book first.
- The flat rows are the final book (run #16 = run #18 restore): f1 4.817, f2 2.085, f3 4.353, edge 13.44.
Family status
| Family | Character | Cost-sweep role | Status |
|---|---|---|---|
| 1 | whale leads always strike | selection core (IN1/HO1) | burned; fresh single touches 3.39 (1111001-5) / 3.79 (1212001-5) |
| 2 | Student-t tails, ~30 % unidentifiable false whale leads, informative funding after segment 3 | generalization probe (IN2/HO2) | burned; fresh single touches 1.79 worst −1.21 (2222001-5) / 0.08 (2323001-5) |
| 3 | jump diffusion + latent beta + separable false lead-ins | segment-4 out-of-family probe (IN3/HO3) | closed; run #16 = run #18 book, flat median 4.35, fresh touches 4.84 / 4.90 |
Offline explorer
tools/explore.py scores candidates without touching the loop: --seeds prints per-seed Sharpes, --baseline prints one ensemble summary plus pooled regime dictionary per set, --family {1,2,3} affects --seeds only, and --costs {flat,realistic,harsh} passes the presets through to ensemble scoring. It asserts the weights audit at import time. Admission and touch accounting live in the validation page.
Real-market feeds — the next segment, waiting on data
harness/real_fixtures.py maps real provider exports (Glassnode, CryptoQuant, Binance/Bybit/Deribit, DefiLlama, Santiment, GitHub) onto the frozen 24-column Bars contract. The ingest is purely mechanical — no scraping, no API calls, no invented gap fills — with a causal regime labeler; the --demo smoke run is exercised (exit 0, output re-validated through harness.data.load_path). Real fixtures open a new autoresearch segment: they are never mixed into fixtures/fixtures2/fixtures3, and the segment gets its own IN / held-out / TEST split and burn ledger. Status: ingest ready, blocked on user-supplied data.
# Smoke test: synthesises 3 tiny input CSVs under the system temp dir, runs the
# full pipeline, validates the output with harness.data.load_path.
python3 harness/real_fixtures.py --demo
# Real run (after building the normalized inputs):
python3 harness/real_fixtures.py \
--ohlcv ohlcv.csv --onchain onchain.csv --derivs derivs.csv \
[--dev dev.csv] --out fixtures_real/path_0.csv
# Native daily exports need no heartbeat rows — widen the row-gap limit instead:
python3 harness/real_fixtures.py --ohlcv ohlcv.csv --onchain onchain_daily.csv \
--derivs derivs.csv --max-gap-hours 25 --out fixtures_real/path_0.csvSources
harness/README.mdin the local repo~/dev/research/auto/market-signals/— cost model fields, preset table, CLI parser behavior, exit codes, catalog contractfixtures_real/README.mdin the same repo — provider export mapping, gap rules, isolation rule- Explainer (local file, not hosted):
/Users/mbp/dev/explainers/2026-09-27_market-signals/index.html