For Agents

Spec #1 (“PortWatch resolution nowcaster — backtest to go/no-go”) is BUILT, fully reviewed, and has now been RUN live end-to-end (2026-06-08). Single Rust crate hormuz_bet at ~/coding/hormuz_bet, 10 modules, built subagent-driven (TDD per module + two-stage spec+quality review each). Final state: cargo clippy --all-targets -- -D warnings clean; cargo test --lib = 30 passed / 3 ignored; golden integration test passes against live APIs. VERDICT: INCONCLUSIVE at both fee levels (fee_k=0 and fee_k=0.072) — point PnL is positive but the bootstrap-5% lower bound is deeply negative, the sample is tiny (~2-3 resolved weekly events in an anomalous wartime regime), and the determinacy split is backwards (high-confidence late trades LOST, early made money), so the positive point edge is noise, not skill. No robust edge → do not deploy capital; INCONCLUSIVE routes to the Spec #2 paper-trade. The full numbers and the honest read are in the Live Run section below. Resolution rules it implements live in hormuz-bet-resolution-mechanics; the validation-design intent it realizes lives in hormuz-bet-backtesting. Not a git repo (owner rule: never commit/init).

First implementation milestone of hormuz-bet: the backtest harness that turns the free PortWatch history into a defensible go / no-go / inconclusive verdict on whether there is a tradable edge.

Build Status (2026-06-08)

Spec #1 — “PortWatch resolution nowcaster — backtest to go/no-go” — is BUILT, fully reviewed, and has now been RUN live end-to-end (2026-06-08). It produced its verdict: INCONCLUSIVE at both fee levels — meaning no robust, deployable edge, route to the Spec #2 paper-trade. Full numbers, the honest read, and the three live-bring-up fixes are in the Live Run (2026-06-08) section below.

What “built” means here

  • Single Rust crate hormuz_bet, project dir /Users/levander/coding/hormuz_bet.
  • 10 modules, built subagent-driven: TDD per module, with a two-stage review (spec-conformance + code-quality) on each module.
  • Final verified state:
    • cargo clippy --all-targets -- -D warningsclean.
    • cargo test --lib30 passed / 3 ignored (the 3 ignored are the network-gated live tests).
    • The golden integration test (tests/golden_resolution.rs) compiles.
  • Not a git repo. Per the owner rule, the project is never committed or git init-ed.

Live run done (2026-06-08) → verdict INCONCLUSIVE (both fee levels)

The crate compiled, linted clean, its pure core was tested, and it has now been run end-to-end against the live APIs (read-only). The executed sequence was:

  1. cargo run -- ingest-portwatch — snapshotted 2708 PortWatch daily rows (2019-01-01 → present) into local SQLite (data/hormuz.db).
  2. cargo run -- backtest --fee-k 0 — idealized (fees off) → INCONCLUSIVE.
  3. cargo run -- backtest --fee-k 0.072 — real Polymarket taker fee on → INCONCLUSIVE.

Both fee levels (and a ±0/10/25% revision-sensitivity sweep) return INCONCLUSIVE — point PnL positive but bootstrap-5% deeply negative. No robust edge. Numbers + honest read below.

Spec / plan provenance (exact paths, verified):

  • Spec: docs/superpowers/specs/2026-06-07-hormuz-portwatch-nowcaster-design.md
  • Plan: docs/superpowers/plans/2026-06-07-hormuz-portwatch-nowcaster.md
  • README at repo root summarizes the run sequence and verdict meanings.

Live Run (2026-06-08) — VERDICT: INCONCLUSIVE

The engine ran end-to-end against the live APIs (read-only) on 2026-06-08. These are the real numbers printed by the binary, not estimates.

Golden spine passes live. The golden integration test reconstructs the real UMA-settled markets from the raw PortWatch series: the week-of-May-11 event reconstructs into the 40–59 bucket, matching the actual settlement. Ingestion pulled 2708 PortWatch daily rows (2019-01-01 → present) into data/hormuz.db. So the instrument is wired correctly to reality — the reconstruction is trustworthy.

Verdict: INCONCLUSIVE at both fee levels.

fee_kverdictstrategy PnL (trades)bootstrap-5%Brierneg-ctrlprice-followrandommodal
0INCONCLUSIVE+737 (53)−14610.093−2032−0.0−1350−2032
0.072INCONCLUSIVE+488 (51)−1710−2193
  • Determinacy split (fee_k=0): late-determinacy (high-confidence) −1300 (13 trades); early +2037 (40 trades).
  • Revision-sensitivity sweep ±0/10/25%: all INCONCLUSIVE — the verdict is stable to PortWatch revisions.

Honest read: NO robust edge — do not deploy capital

The point PnL is positive (+737 / +488) but the bootstrap-5% lower bound is deeply negative (−1461 / −1710) — the confidence interval straddles 0, so the point estimate is not distinguishable from luck. The sample is tiny (~2–3 resolved weekly events) and drawn from an anomalous wartime / depressed-traffic regime (non-stationary; see hormuz-bet-basis-risk).

The damning tell: the late-determinacy (high-confidence) trades LOST money (−1300) while the early trades made it (+2037) — that is backwards from what a real nowcaster edge would produce. A genuine edge should sharpen as the resolution window fills in (more signal → more confidence → more PnL). The opposite pattern means the +737 is noise, not skill.

Structural reason there is no edge: the count markets resolve on PUBLIC PortWatch data the market also sees → there is little-to-no informational asymmetry, and the books are thin (see hormuz-bet-liquidity). Forecasting a public number a hair faster than an efficient, thin market is not a tradable advantage.

Correct call: do not deploy capital. INCONCLUSIVE routes to the Spec #2 paper-trade, not to live trading. The engine is a reusable instrument that gains statistical power as more weekly markets accrue — re-run it as the sample grows.

Live Bring-up Fixes (three, all sound)

Getting the engine to run against the live payloads surfaced exactly three issues. All three fixes are correct (verified against the live payloads / the math), and the third is a reusable methodology rule.

1. PortWatch date is an ArcGIS date-only field → serialized as a STRING, not epoch-millis

The PortWatch FeatureServer date field is esriFieldTypeDateOnly, which serializes as a string "YYYY-MM-DD", not the epoch-milliseconds that ordinary ArcGIS date fields use. Fix: Attrs.dateOption<String>, parsed with NaiveDate::parse_from_str. (The Gamma and CLOB structs were verified correct against the live payloads and needed no change — only PortWatch’s date typing was the surprise.)

2. perturb panicked on rel == 0.0 (empty gen_range)

The revision-sensitivity perturb called gen_range(-0.0..0.0) when the relative perturbation was 0, which is an empty range and panics. Fix: short-circuit and return a clone when rel == 0.0. (This is why the ±0% arm of the sensitivity sweep now runs cleanly.)

3. METHODOLOGY (high value, reusable): the negative-control gate must be ONE-SIDED

A losing blind control is EXPECTED — only a winning one is a leak

The negative control originally gated on neg_ok = negc.total().abs() <= bound, i.e. it flagged any large-magnitude control PnL as a leak. That is wrong: a losing blind control is exactly what you expect against an efficient market, and flagging it produced a misleading “NO-GO (negative control leaked)“. A leak/lookahead signal only shows up as a control that spuriously WINS — a strongly positive control PnL it has no business earning.

Fix: make the gate one-sidedneg_ok = negc.total() <= bound (only a positive overshoot fails). This flipped the verdict from a misleading NO-GO to the honest INCONCLUSIVE.

Reusable rule for ALL backtest negative controls: gate one-sided on the positive side. A blind/null control that loses money is healthy evidence of an efficient market; only a control that makes money you can’t explain indicates leakage or lookahead. Pairs with learning #1 below (price the control at the null’s own belief). See hormuz-bet-backtesting.

Architecture As Built

The whole design rests on a single idea: one daily-count model, exposed to three market types as Monte-Carlo functionals. There is exactly one statistical model of the PortWatch daily transit-count series; every market structure from hormuz-bet-resolution-mechanics is just a function evaluated on simulated paths from that model.

One model, three markets, via resolve reuse

  • forecast simulates the not-yet-finalized window days. The generative model:
    • Negative-Binomial day observations (overdispersed counts).
    • Day-of-week effects layered on an EWMA log-scale local level (a slowly drifting log-level the day-of-week multipliers ride on).
    • A shared level path per draw, so all days within one simulated draw move together — this is what gives the simulation cross-day correlation (weekly sums and 7-day MAs are not sums of independent days).
  • For each simulated path, forecast calls resolve.
  • resolve is a pure function that maps a daily-count path to a market outcome (weekly TOTAL bucket, 7-day MA bucket, single-day-max threshold leg, or the deep MA≥60 binary).
  • Because the same resolve is used for both forecasting and settlement, forecast pricing and actual settlement cannot diverge — they are, by construction, the same code path. (See the consistency learning below: this property is the spine of the cost-model correctness too.)

Point-in-time correctness: portwatch::as_of

  • portwatch::as_of models the weekly publish calendar + finalization to produce point-in-time views of the series (what was knowable at a given historical instant).
  • A leakage-guard test forbids any future-published date from entering an as-of view — this is the concrete, automated defense for the purged/embargoed-walk-forward intent described in hormuz-bet-backtesting.

The verdict gate (three-way)

The harness emits GO / NO-GO / INCONCLUSIVE. It gates GO on ALL of:

  • bootstrap 5%-quantile PnL > 0 (lower CI bound positive, not just the point estimate), AND
  • beats price-follow (buying at the quoted probability), AND
  • beats random, AND
  • beats a modal-historical baseline, AND
  • calibrated (Brier score), AND
  • negative-control ≈ 0 (no leakage; see learnings).

If the point edge is positive but the bootstrap CI straddles 0 (small sample), the verdict is INCONCLUSIVE → proceed to the Spec #2 paper-trade rather than trading live. A negative-control leak forces NO-GO (it indicates a bug, not an edge).

Also included

  • Determinacy stratification — splits trades into early vs late (how settled the resolution window already was at trade time) and reports edge per stratum.
  • Revision-sensitivity band — how much the verdict moves if PortWatch later revises the published numbers.
  • A historicMoment ArcGIS probe — queries PortWatch’s time-travel layer to sanity-check point-in-time reconstruction against the source.

Module Map

Ten modules under src/ (verified layout), grouped:

  • Core / glue: lib.rs, main.rs (clap CLI: ingest-portwatch, backtest), types.rs, store.rs (local SQLite via sqlx), resolve.rs (the pure outcome function reused by forecast + settlement), edge.rs (signal → bet sizing / EV).
  • market/ — Polymarket side: gamma.rs (events), clob.rs (book), parse.rs (parse each event’s resolution text → structure + per-event bucket edges; never hard-coded — see hormuz-bet-resolution-mechanics).
  • forecast/model.rs (EWMA log-level + day-of-week), nb.rs (Negative-Binomial draw / pmf via statrs ln_gamma), mod.rs (Monte-Carlo driver calling resolve).
  • portwatch/client.rs (ArcGIS FeatureServer fetch), asof.rs (point-in-time + leakage guard), mod.rs.
  • backtest/metrics.rs (bootstrap PnL, Brier), baselines.rs (price-follow / random / modal-historical), mod.rs (the verdict engine).
  • Integration: tests/golden_resolution.rs (golden reconstruction; network-gated).

Key Learnings & Gotchas

High-value, hard-won. These are the reasons the harness is trustworthy.

1. Negative-control test design — price the synthetic market at the null’s own belief

A negative control must read ≈0 against an efficiently-priced market

A correct negative control reads ~0 edge only when the synthetic market it trades is priced at the null’s own belief. An initial implementation gamed the control by shrinking size_cap until PnL slipped under a hardcoded bound — that is defanging, not fixing. The actual root cause was a fixture that priced a near-certain outcome at a uniform 0.33, so even the blind null “won” against the mispricing. Fix: price the near-certain bucket at ~0.99 → the null makes 0 trades → it reads exactly 0 for the right reason, and the control still catches real leaks. Lesson: when a control “passes” only after you constrain sizing, suspect you defanged the test instead of removing the leak.

2. Fee consistency — the fee is charged unconditionally on BOTH sides (refuted a reviewer)

= stake × signals_EV exactly, only if the fee is charged unconditionally everywhere

The identity E[settle] = stake × signals_EV holds exactly when the fee is charged unconditionally in both:

  • the ex-ante EV — a flat -fee_k*(1-eff) term, and
  • the ex-post settlementstake*(1-eff)*fee_k charged on win AND on loss.

This matches Polymarket’s documented taker fee shares*0.072*p*(1-p), charged at trade time (so it is paid regardless of outcome). A reviewer claimed settlement should skip the fee on losing trades — that is WRONG: it would break the signals↔settle consistency that the shared-resolve architecture exists to guarantee. Verify review findings against the math before implementing (receiving-code-review rigor). Related cleanup: removed a redundant BtCfg.fee_k that duplicated costs.fee_k — a divergence footgun that could have broken this very consistency by letting the two fee values drift apart.

3. RTK proxy hides test stderr (and can mis-display clippy)

rtk gotcha in this repo

The rtk proxy summarizes cargo test output and SUPPRESSES eprintln! / --nocapture stderr. To see test debug output here, bypass it:

rtk proxy cargo test ... -- --nocapture

Also, rtk’s clippy summary can cosmetically mis-display “1 errors / 1 warnings” when raw cargo clippy is actually clean — trust raw cargo over the summary. (Separately: rtk also intercepts find with compound predicates — use rtk proxy find ... or plain paths.)

4. Toolchain

  • sqlx (SQLite) — uses the RUNTIME query API, not compile-time macros, deliberately, so the build does not require a live DB. Backtest data store is local SQLite (data/hormuz.db).
  • reqwestconnect_timeout set (rustls-tls, no default features).
  • rand / rand_distr — Monte-Carlo draws.
  • statrsln_gamma for the Negative-Binomial pmf.
  • clap — the ingest-portwatch / backtest CLI.
  • Verified deps (Cargo.toml): tokio, reqwest (rustls), serde/serde_json, sqlx (runtime-tokio, sqlite, chrono), chrono, rand, rand_distr, statrs, clap, anyhow.

How To Reproduce The Verdict

Live run sequence (needs network) — already executed 2026-06-08, output INCONCLUSIVE

cargo run -- ingest-portwatch              # snapshot PortWatch daily history → data/hormuz.db (2708 rows)
cargo run -- backtest --fee-k 0            # idealized (fees off)      → INCONCLUSIVE
cargo run -- backtest --fee-k 0.072        # real Polymarket taker fee → INCONCLUSIVE

Read the emitted GO / NO-GO / INCONCLUSIVE. Optional: cargo test -- --ignored --nocapture exercises the 3 network-gated live tests (PortWatch fetch, Gamma event, golden resolution reconstruction) — they pass live. This was the boundary between Research/Build and the Backtest milestone in the hormuz-bet pipeline; with INCONCLUSIVE in hand, the next milestone is the Spec #2 paper-trade.

  • hormuz-bet — project index; this is the first build milestone of its pipeline.
  • hormuz-bet-backtesting — the validation-design intent this crate realizes (purged walk-forward, negative control, baselines, fee + slippage, regime-break caveat).
  • hormuz-bet-resolution-mechanics — the exact market structures the resolve function and market/parse.rs implement; the never-hard-code-buckets rule.
  • hormuz-bet-portwatch-data — the FeatureServer the portwatch/ client ingests and the as_of layer models point-in-time.
  • hormuz-bet-liquidity — the thin-book reality the sizing/edge model must respect.
  • polymarket-fetch — the purged-backtest validation bar this mirrors; reference for PM fee/slippage modelling.