For Agents
Reverse-chronological session log. Newest entries at top, grouped by date (
## YYYY-MM-DD). Each bullet: one piece of work, short summary, wikilinks to docs touched. Updated byobsidian-documenteron every project doc write. Read byhistorianat bootstrap.
2026-07-02
- Full-family strategy synthesis (14-agent workflow, adversarially skeptic’d) → per-lane VERDICTS. US taker lock-and-hold PILOT NOW (1,500; BLOCKER = position_manager
fee_usd=0fix; pass gate n≥50 fills & roi_lcb_80>0 net of true fees & median slip ≤2c & wr≥75%; honest EV −2c…+8c/8 median fillable-within-3c depth, whales sweep the liquid moment in 1 min — london 0.72→0.90 behind a 300 on ≥50% of days AND first sweeper >15 min post-lock). EU maker/LR PILOT after infra (madrid+paris quoting vacuums 12–18c spreads, 150–200, no modal quoting 15:00–18:00). Asia RESEARCH-MORE, no capital (Seoul FOLD at 65% lockout; CN/Tokyo decided by one binding lock-timed IEM replay + a day of book snapshots, bar ≥+0.10/1–3k/day US-wide. Synthesis file:/Users/levander/coding/pmv2/research/weather-strategy-synthesis-2026-07-02.md— weather-strategy-verdicts-2026-07-02 - Prior beliefs OVERTURNED (live-verified): weather LR pools EXIST (~55/mkt/day; seoul-lowest 1,010, munich 703 — prior “no LR on weather” was wrong); resolution stations pinned engine-critical (paris = LFPB Le Bourget NOT LFPG, london = EGLC City NOT Heathrow, denver = Buckley SFB, istanbul/moscow/tel-aviv via NOAA weather.gov, HK = HKO 0.1°C);
fee_usd=0bug in position_managerplace_orderoverstates live ROI 0.45–0.67%/side (true fee 0.05·p·(1−p));api.weather.compublic web key polls the WU resolution truth directly (~30-min, every city — ToS-gray, keep METAR fallback); universe is ~7× bigger (48 cities, 41 new, + a lowest-temperature series whose seoul pool is the biggest); °C boundary gate fixable via national tenths feeds (DWD/FMI/IMGW/JMA-AMeDAS/SYNOP-OGIMET) for 7/8 EU cities (london/seoul are the gaps) — weather-prior-corrections-2026-07-02 - Methodology trap CAUGHT — proxy-timed entries inflated EV ~2–3×; two headline results overturned. The fresh Jun 14–Jul 1 backtest gated on the 15:30-local modal price but entered earlier prints (mean 7.5c below conviction; 57/59 US + 29/31 AS trades affected): AS +0.455→+0.145/. Also test-set selection: the 0.90 cap, flatten-at-18:00, and PT-0.92 rejection were all chosen on their own scoring window. Binding rules: entries at-or-after ALL gating info exists; never tune a parameter on its scoring window. Survives: gate direction (wr 65→85–92% monotone), hold-to-settle, and the original clean causal backtest (n=67, +29.9%/trade, real IEM locks — weakness is fills, not signal). Same failure class as the 1.96× convergence-conditioning and the win-label price-picker — weather-lookahead-trap-2026-07-02
2026-06-25
- AS-lane DRY order-publish ARMED (~20:15Z): pin
9a07960→e5521c4+ envWEATHER_AS_PUBLISH=1. First time the AS lane has a path to publish to NATS. New pin is env-gated, default OFF at the binary level — ongate_admit,scan()publishes the pmv2-contracts entry envelope topmv2.order.weather_as.entry, once per market/day (self-measure dedup leverages the same ledger as the M9 fixes). Boot version line:e5521c4 feat(weather-as): env-gated order publish on gate-admit (WEATHER_AS_PUBLISH, default off). Money safety intact:weather_as=dry_run, globaldry_run, PM gates at build+sign — NO POST hits the exchange; envelopes flow into NATS so downstream can dogfood the message shape but the chain dead-ends at the signer. Env push viartk proxy cat secrets/pmv2-weather-as-algo.env | rtk proxy gh secret set PMV2_WEATHER_AS_ALGO_ENV -R wowjeeez/terraform([[gcp-terraform-ansible-gotchas|gotcha #28]]); verified inside container viadocker exec printenv | grep WEATHER_AS_PUBLISH. NATS NKey already hadpmv2.order.weather_as.>PUB permit from pin5db34c0;PMV2_ORDERSwildcardpmv2.order.>captures the subject — no NATS-side change. First scan post-arm: 11 cities scanned, 1 locked (paris, gate_blocked → no admit), published=0 (normal idle), settled=1; arm is hot, first publish fires on next gate_admit. Kill switch: unset env + re-push secret + redeploy — weather-as-dry-publish-arm-2026-06-25, weather-as-deploy-pattern-b-2026-06-23
2026-06-24
- Live
--loopcrash FIXED — Python%Zstrptime can’t parse abbreviated zone names (@ 7939da2). Found in PRODUCTION by @deploy in babylonweather-shadow#442: the livepmv2-weather-as-algo --loopcontainer spammedloop_error: time data '2026-06-23 15:56 PDT' does not match format '%Y-%m-%d %H:%M %Z'(also JST, CST). Root cause = a strftime→strptime round-trip through%Z:rehearsestrftime’s the station-local lock time INTOtrace["lock_local_time"], thenshadow_engine.lock_from_tracere-parsed it withdatetime.strptime(s, "%Y-%m-%d %H:%M %Z")— but Python’s%Zstrptime CANNOT parse abbreviated zone names (PDT/JST/CST; only UTC/GMT/numeric offsets) → ValueError on EVERY locked city → exception propagated out ofscan()and aborted the whole scan tick → the live service recorded nothing on locked ticks. Fix: don’t round-trip through a%Zstring — derive the local datetime directly from data in hand:datetime.fromtimestamp(trace["lock_ts"], tz=timezone.utc).astimezone(ZoneInfo(st["tz"])). Thelock_local_timestrftime field stays as a human-readable trace value (strftime is fine; only the strptime parse-back was broken);build_signaluses that datetime only for an HH:MM display in itsreason, so a tz-aware local datetime is correct. Lessons: (1) never round-trip a localized datetime throughstrftime('%Z')→strptime('%Z')— carry epoch + IANA zone and reconstruct viafromtimestamp(ts, utc).astimezone(ZoneInfo(zone)); (2) test-coverage gap that shipped it — every existing test MOCKEDlock_from_traceitself, so the round-trip inside it was never exercised end-to-end; when you mock the function under test, the real internal path goes uncovered — added regression tests across the real JST + PDT paths; (3) a win for deploy-mode-OFF-first — the forward dry-run (mode OFF, zero economic risk) surfaced a real bug before any capital. Full suite 46 tests green on Python 3.14; fix pushedmain@ 7939da2, new image in GAR, awaiting @deploy’s container bump — weather-as-selfmeasure-pnl-fixes-2026-06-23
2026-06-23
- Self-measure P&L CORRECTNESS fixes — resolves HANDOVER PART 2 findings M7/M8/M9 + the L7 cache bug (all in
selfmeasure.py+config.py+shadow_engine._measure_gated; TDD + multi-agent adversarial review, 44 tests green on Python 3.14). M7 (fill realism): reject fills above the producer’s ACTUAL limitmin(0.95, current_at_lock + PRICE_BUMP), not just the 0.95PRICE_CAP;limit_pricethreaded lock →_measure_gated→record_signal→realizable_fill(rejectfill > limit_price + 1e-9; PRICE_CAP backstop when limit_price None for old records). M8 (P&L leak + void economics): closed-but-non-binary terminals were settled immediately as void at terminal mid — wrong twice ((a)closed:truecan be mid-dispute pre-0/1 → premature mislabel; (b) a real Polymarket void REFUNDS stake, P&L≈0, not a mid payout). Fix: never infer void from price —_resolve_payoffreturns binary only when closed&binary else None (pending);settleforce-settles a still-unresolved row as a STAKE-REFUND void (payoff=our_fill_price → gross_pnl 0, net_pnl=-cost) only afterMAX_SETTLE_AGE_DAYS(default 7). ALSO L7:_RES_CACHEwas slug-keyed storing the whole event token map gated only on the queried token’s closed-ness → resolving one closed bucket cached OPEN siblings forever; now keyed per(slug, token), cached only when that token is resolved, open tokens re-fetch. M9 (cost honesty): records BOTHgross_pnlandnet_pnl; slippage mode-aware (live=0 correct, fill is book ask w/ spread; replay fills are/prices-historyMIDPOINTS soSLIPPAGE_REPLAYmodels half-spread); new config knobsPOLYMARKET_FEE/SLIPPAGE_LIVE/SLIPPAGE_REPLAY/MAX_SETTLE_AGE_DAYS. Lessons: void=stake refund not mid payout; don’t infer resolution fromoutcomePricesalone; per-event caches poison sibling tokens (key per (event,token)); replay fills are spreadless midpoints (cost must be mode-aware). Open follow-up: void-age clock uses lock_ts (harmless on live--loop; switch to max(record_ts, entry_ts) if backfill/replay-of-old-locks is added). Unblocks the deploy-note P&L-trust warning — weather-as-selfmeasure-pnl-fixes-2026-06-23 - AS-lane run-shape FLIPPED — from two OnCalendar cron jobs (
--scan+--settle) to a continuouskind=servicerunningpython shadow_engine.py --loop. Why: (1) the fleet-wide #354 startup-announce directive converged (every pmv2 service announces on boot totelegram.outbound) + deploy is minting a weather_as NKey with PUB ontelegram.outbound(#366) → a continuous service announces cleanly ONCE on boot, an OnCalendar cron would announce every 30-min run or not at all; (2) symmetry with weather-US, which also pivoted to Python (as_weather_algoformat) and runs--loopkind=service.--loopinternally = a cohort scan everySCAN_INTERVAL_SECS(default 1800) + a daily settle once per UTC day at/afterSETTLE_HOUR_UTC(default 12), both env-tunable. Boot announce publishes the canonicaltelegram.outboundenvelope (#362:msg_id/text/chat/parse_mode/kind) with textcomponent: pmv2-weather-as-algo started with version: <sha> <title>; sha/title fromGIT_SHA/GIT_TITLEDocker build-args (wired in Dockerfile + build-push.yml per deploy #358). CONSEQUENCE: the lane is NO LONGER NATS-free — boot announce needsNATS_URL+ the weather_as NKey seed (NATS_NKEY_SEED, PUBtelegram.outbound); order publishing stays OFF (no--live), NKey only for the announce until the publish leg arms; announce is best-effort (missing creds →startup_announce:false, never crashes). Built TDD, full suite green (22 tests on Python 3.14):shadow_engine.py(loop/_announce_startup/_git_version+--loopwired),publisher.py(DRY_connect_and_publish+publish_telegram_outbound),Dockerfile(ARG/ENV GIT_SHA/GIT_TITLE, CMD--loop),build-push.yml(gitmeta step + build-args); correction posted to deploy inweather-shadow(reply to #349). Still pending from deploy/Andras: WIF provider + SA literals for CI, the weather_as NKey, Andras’s terraformci_github_reposgrant — weather-as-deploy-pattern-b-2026-06-23 - pmv2 contract invariant captured —
position_managerkeys on the wiresourcevalue, NOT the agent/service/repo handle (so handle renames are free). From @positionmanager babylon pmv2 #340. 3-way invariant that MUST stay equal: producer’s emitted wiresource==pmv2_autotrade_sources.source_id(operator DB row) == NATS subject segment inpmv2.order.<source>.entry; any drift → PM sees unknown source → no sources row → lane silently fail-closes toMode::Off(no orders, no error). Locked: USsource=weather(pmv2.order.weather.entry), ASsource=weather_as(pmv2.order.weather_as.entry). Renaming asource_id= coordinated 3-way change (producer emit + operator sources row + subject) → ping @positionmanager FIRST. GOTCHA: handle != wire source. AS lane is consistent (handleweather_as, sourceweather_as— born that way) but US lane intentionally does NOT match — handle/repo becomeweather_uswhile wiresourceSTAYSweather. Do NOT “fix” the US source toweather_usto match the handle; that’s the 3-way change that fail-closes US to Off. weather_us must coordinate with @positionmanager before anysource_idchange (acked AS-side in pmv2 #344) — pmv2-source-id-3way-invariant-2026-06-23 - AS-lane deploy convention DECIDED + implemented — containerized “Pattern B” via the
appsAnsible role, run as TWO cron jobs (scan + settle), NOT a continuous service. Answered by @deploy in babylonweather-shadow#338. Image → GAReurope-west3-docker.pkg.dev/<project>/levandor-apps/pmv2-weather-as-algo:<sha>; deploy =deploys/apps.ymlentry + dispatchdeploy-apps.yml; the role auto-injects OTel env (OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:4318,OTEL_SERVICE_NAME=<entry>) → OTel is zero-config once the Python OTLP SDK is initialized inmain(). Run shape =kind=job+OnCalendarbecause the strategy is windowed/entry-only. scan jobpython shadow_engine.py --scan(NEW entrypoint, built TDDtest_scan.py6 tests, reuseslock_from_trace+_measure_gated): sweeps the 12-station Asian cohort (skipsiem_id=null), per-city lock→gate→size→self-measure record, NO publish, idempotent (selfmeasure dedups by(yes_token, entry_ts)deterministic lock ts), run every ~30 min UTC ~06:00–15:00. settle job--settleonce daily ~16:00 UTC. Container artifacts in-repo:Dockerfile(python:3.13-slim— gate pickle needs scikit-learn 1.9.0/pandas 3.0.3/CPython 3.13+; non-root; shipsgate/gate_model.pkl+asia_stations.json),.dockerignore,.github/workflows/build-push.yml(WIF→GAR sha+latest).pytestin newrequirements-dev.txt, kept OUT of leanrequirements.txt. Dry-run jobs need NO secrets (public Gamma/CLOB/IEM only; NATS publish leg dormant;env_fileempty until publish arms). WARNING: accumulated self-measured P&L validates the PLUMBING not the EDGE — still affected by open code-review bugs M7/M8/M9 (fill-vs-limit, non-binary-terminal leakage, cost=0); don’t trust the numbers until fixed (HANDOVER.md PART 2). Pending from @deploy/Andras: reusable-workflow ref, WIF provider resource, deploy SA email, GCP project id, tagging policy, @Andras authorizing the WIF repo-allowlist grant (terraform.tfvars) — weather-as-deploy-pattern-b-2026-06-23
2026-06-22
- AS lane gets its own babylon coordination identity (
weather_as). Provisioned 2026-06-22 via/babylon:initforas_weather_algo/pmv2-weather-as-algo(source=weather_as); token in a project-local, gitignored, 0600.mcp.jsonthat shadows the plugin global babylon MCP per-repo (NO globalBABYLON_TOKENenv). Part of splitting the old combinedweatherhandle intoweather_as(Asian) +weather_us(US, not yet on babylon). Subscribed: general, pmv2, weather-shadow, weather-jetstream. Prior coordination underweather: weather-shadow #302 (OPEN — @deploy deploy-convention Q for the Python producer), general #306 (lane align), general #313 (deploy SigNoz/OTel conventions), pmv2 #315 (contracts conform). Transition: general #335 (handle-transition status), weather-shadow #336 (re-anchored #302), general #337 (acked #313 — producer will runOTEL_SERVICE_NAME=pmv2-weather-as-algobefore--live). Open blockers: #302 deploy convention,PMV2_ORDERSstream + operatorpmv2_autotrade_sourcesrowsource_id=weather_as, OTel instrumentation (print-only now) before live — weather-as-babylon-identity-2026-06-22 - pmv2 RE-ARCHITECTURE — monolith
polymarket_fetchretiring → split into independent service repos (Andras’s direction via a Miro board). NATS JetStream becomes the spine; a sharedpmv2-contractscrate is the seam; deps unidirectional producers → executor → notify. PRODUCERS emit signals: weather-US (THIS,source=weather), weather-AS (source=weather_as, separate repo/session), cohort (source=first_mover_core), crypto (source=crypto_shortterm_latency_test) (+ future @dionysus/@shipping lanes). POSITION MANAGER = the executor (pmv2-position-manager, @deploy/@positionmanager): consumes NATS DIRECTLY (no separate runner), gates (3-layer off/dry/live + caps + breaker), signs EIP-712, POSTsclob.polymarket.com, records/settles —polymarket_fetch’s consume+exec moves here; has NO GammaClient (producers own ALL gating); Mode currently OFF. NOTIFICATION SERVICE (dedicated, owner TBD) centralizes the ~25–30 scattered Telegram send-sites behind a NATS notify subject. Transport: streamPMV2_ORDERSoverpmv2.order.<source>.<entry|exit>; PM binds ONE durable pull consumer onpmv2.order.>(explicit ack,Nats-Msg-Id=iddedup, needs aduplicate_window). Supersedes the old single-streamWEATHER_SIGNALS/weather.signals+ hand-trackeddocs/signal-schema.mddesign — pmv2-rearchitecture pmv2-contractscrate PUBLISHED (github.com/wowjeeez/pmv2-contracts). serde + rust_decimal only; holds the envelope types, aPricingdiscriminator enum (limit|derived),Side,SCHEMA_VERSION=2, and subject builders.sourceis a FREE String, NOT an enum — adding a lane = publish the new source string + insert an operatorpmv2_autotrade_sourcesDB row (no contracts release). Producers + PM vendor it so the wire can’t drift. GOTCHA: a wrong/typo’dsourcesilently routes toMode::Off(acked-and-skipped, no error) — pmv2-rearchitecture- Trade wire-contract v2 SIGNED OFF (weather’s
limitarm) — supersedes the olddocs/signal-schema.md“Directive”/v1 envelope. Weather emits a READY BUY entry-order topmv2.order.weather.entry: commonid=uuid5("{source}|{market_slug}|{action}")(=Nats-Msg-Id),source="weather"(EXACT DB key),schema_version=2,produced_at,valid_until_ts(past→Term),market{condition_id, outcome_index, token_id, title, event_slug, slug, +neg_risk/tick/min as HINTS},origin_ref(= our Supabaseweather_signalsrow id),side="BUY";pricing="limit"→limit_price(≤0.95 hard cap) +size_shares. PM places FAK marketable-limit atfloor_to_tick(min(limit_price, best_ask))(fills ≤ cap; ask>0.95 simply won’t fill = correct), re-resolves neg_risk/tick/min authoritatively, sized-out (shares<min)→Skip. ENTRY-ONLY (weather holds to settlement → no exit-signal; the...exitpath is cohort/crypto only). SIZING moved to producers: weather now sizessize_shares = clamp(floor(notional/limit_price), min_size=5, cap), default notional $50; PM caps/breaker = hard backstop. Weather context (city/bucket_label/observed_high_f/lock_ts/reason) stays OFF the wire (lives in the Supabase row viaorigin_ref; PM ignores unknown fields) — pmv2-rearchitecture - wx-signal v2 producer adaptation = PENDING (not built). Scoped but NOT done; was blocked on
pmv2-contracts(now published → unblocked): rename action→side, max_price→limit_price, fold flat fields intomarket{}, addpricing="limit"+size_shares,schema_version→2, switch subjectweather.signals→pmv2.order.weather.entry, add a sizing module, vendorpmv2-contracts. The existing engine (crates/wx-signal: lock/price/nats/sink) + validated lock logic are UNCHANGED;docs/signal-schema.mdin-repo is still v1 — weather-bet-live-status - Dry-test “too close to resolution” gate saga → FIXED (
i64::MINexemption, Position Managermain@43023ba). The dry rehearsal (capital gate) was blocked by the PM consumer’sliveness_gate: the lock strategy signals NEAR resolution by design (lock mid-afternoon, market resolves end-of-local-day → every signal lands ~6–9h out). Root cause: gate skips whensecs_to_resolution < min; the first Directive exemption set min=0, which only exempts POSITIVE secs — but the re-fired STALE Jun-19 NYC market is PAST resolution (negative secs) so it kept skipping (a FRESH signal already passes with min=0). FIX =i64::MINfull exemption for Directive sources (also unblocks stale replays). Two earlier theories RULED OUT: (a) weather “ask>max_price” — PM capsmin(limit_price,best_ask)and still builds → would’ve alerted; (b) “best_ask=None / no liquidity”. STATUS: dry-test completion PENDING — needs redeploy of43023ba+ a FRESH US afternoon lock → 🧪 DRY-RUN alert fires → cross-check → capital gate cleared — weather-bet-live-status - Repo RENAMED + MOVED. GitHub
github.com/wowjeeez/weather-bet→github.com/wowjeeez/pmv2-weather-us-algo(PRIVATE); local/Users/levander/coding/weather_bet→/Users/levander/coding/pmv2/us_weather_algo/. Now one of a sibling set of pmv2 service repos under/Users/levander/coding/pmv2/:us_weather_algo,as_weather_algo,cohort_algo,crypto_algo,position_manager,telegram_connector(GitHub: pmv2-weather-us-algo, pmv2-weather-as-algo, pmv2-cohort-algo, pmv2-crypto-algo, pmv2-position-manager, pmv2-telegram-connector, pmv2-contracts) — weather-bet-live-status - AS-lane current-state note written — the
weather_asproducer + full pmv2 reorg from the Asian-weather side. New authoritative note capturing: (1) the multi-producer → single-executor reorg —position_manager= the SINGLE execution authority (consume NATS → gate off/dry/live + caps/breaker → sign → POST → record;polymarket_fetchretires), producers (cohort_algo,crypto_algo,us_weather_algo,as_weather_algo) own strategy+sizing and publish READY entry-orders; thepmv2-contractswire-contract (ONE envelope,pricingdiscriminatorlimit/derived, subjectspmv2.order.<source>.<entry|exit>, weather entry-only, TTR gate exempt for Directive); (2) repos — weather-bet →pmv2-weather-us-algo(/coding/pmv2/us_weather_algo, US + research) + new PRIVATEpmv2-weather-as-algo(/coding/pmv2/as_weather_algo, committede9415dc); (3) the AS lane (source=weather_as, subjectpmv2.order.weather_as.entry, lean pipeline observe IEM→causal lock→match→convergence-gate ≥0.68→size beats per-city +0.09 / ungated +0.06 on backtest, but single-season (~11d June), thin (n 14–19/latency), convergence-selected (optimistic ceiling), book reprices past us ~40–50%, NEVER RUN LIVE, end-of-stage-3, dry-run is next; (5) pending — research moving into the AS repo,pmv2-contractsthen the publisher rewrite, @deploy guidance, then the dry-run. Companion to the US-side pmv2-rearchitecture — pmv2-reorg-and-weather-as-2026-06-22 - Win-label re-score promoted into a full vault note (was report-only) + given the reorg banner. weather-gate-winlabel-2026-06-19 — the discharge of shadow-rehearsal caveat #1 (“re-score the gate against a true WIN label”): selecting on P(WIN) instead of P(convergence) collapses the headline EV ~+0.14/** at matched 8.5% admit, and the win-label model loses to a per-city win-rate lookup (+0.0939), an ex-ante-only model (+0.0772), and ties ungated (+0.0553) —
entry_pricealone (OOS AUC 0.7136) beats the win-model (0.6838). Signature of a disguised price-picker; CONDITIONAL-GO downgraded to LIVE-MEASUREMENT-ONLY (no capital, no Rust until a live loop beats an ungated/per-city baseline). Win base rate ≈0.83 (favorites); true-resolution OOS coverage 74.7% (not 93.6%), per-city 47–100% and biased with the signal. Research-onlybuild_win_gateintentionally excluded from theas_weather_algoproducer — weather-gate-winlabel-2026-06-19 - Banners + key-reference fixes across the six AS-lane research notes so the vault is internally consistent: added an
UPDATE 2026-06-22callout to weather-asia-shadow-rehearsal-2026-06-19, weather-exante-gate-2026-06-19, weather-netcost-precheck-2026-06-19, weather-lag-distribution-2026-06-19, weather-gate-winlabel-2026-06-19, weather-top50-decypher-2026-06-19 noting the lane is nowsource=weather_asonpmv2.order.weather_as.entryinpmv2-weather-as-algo(/coding/pmv2/as_weather_algo) with research underpmv2-weather-us-algo(/coding/pmv2/us_weather_algo). Fixed load-bearing stale refs (weather_shadow→weather_as,weather.shadow→pmv2.order.weather_as.entry,research/shadow/+weather_betpaths → the new repos); marked the shadow-rehearsal’s caveat-#1 “re-score” open item as DONE (→ the win-label note); reframed the net-cost city-universe-mismatch warning as addressed by the AS lane; superseded the top50’s design-onlycopytrade-rides-WEATHER_SIGNALS note with thecohort_algoproducer; cross-linked all six into the current-state note — pmv2-reorg-and-weather-as-2026-06-22
2026-06-19
- FINAL fillability verdict — unbiased full-population reprice-lag study → weak-GO, materially weaker than the first pass. Measured EVERY cheap-Yes buy (0.55–0.95) across the top-50 — no highest-paid-40 cap that biased the prior cohort — 28,664 entries, all 6 shards, adversarially critic-reviewed. Headline downgrade: realized EX-ANTE hit rate ~34.4% (unconditional reach@15 = 0.344), NOT the 63–67% conditional figure, because ~49% of cheap entries never reprice to 0.97 (conditional reach@15 = 0.675; the 1.96× gap is the never-converge haircut). Size for ~65% per-entry miss. Thesis FLIP (most robust finding): winners pay a ~2-tick PREMIUM, not a discount — same-token/same-instant
fair_gapp25 +0.00 / p50 +0.02 / p75 +0.06, 75.1% above book → a convergence-timing edge bought slightly rich, NOT cheap-fill (the decypher’s discount withdrawal was correct). Binding economic gate = net-of-cost recovery of the +0.02 premium after taker fee + slippage — a GO precondition, answerable only in the live dry rehearsal; do NOT wait forpaid<book(present only ~25% of the time). Gate must be EX-ANTE (city/session/METAR) — observed lag is forward-measured → look-ahead, critic-killed; lag distribution (converged median 37.1min, p25 8.2, p75 81.6, p90 127.6) is descriptive for fill-window sizing only. Per-wallet unbiased lag medians: HondaCivic 8.6 / vip68 10.7 (both INSIDE the 15-min window → can’t gate per-wallet) / Happening9014 20.6 (clears → “casualty” retracted, was a 120/180 artifact). City universe (the gate primitive): tier-A core = london, nyc, seoul, hong-kong, atlanta, paris, wuhan (+ tier-B lagos/seattle/los-angeles cap-to-CI); thin/provisional held out = istanbul (n=96)/sao-paulo/lucknow/wellington; fast-repricing EXCLUDED = denver/manila/karachi/taipei/tel-aviv/etc. Methodology integrity: 120-vs-180 horizon bug fixed (aggregator now re-derives at 180, horizon-authoritative — wx-lag-pipeline-bug); shard_2 (16.7%) dropped then restored (n 23,907→28,664), aggregate barely moved (conv 0.511→0.509, reach@15 0.677→0.675) → robustness confirmed, per-city no longer provisional. Reportresearch/reports/weather-lag-distribution-2026-06-19.md; dataresearch/copytrade/top50/lag/lag_distribution.json— weather-lag-distribution-2026-06-19 - DATA-PIPELINE BUG in the repricing-lag study:
convergedbaked at 120-min, report published 180-min, and the 180-min numbers were NEVER persisted. The 6-shard lag pipeline (research/copytrade/top50/lag/:measure_shard_N.pyper shard,aggregate_lag.pyaggregator) computesreprice_lag_minat a 180-min window but baked theconvergedboolean at 120 min (measure_shard_1.py:105 lag<=120.0,shard_3.py:96,shard_4.py:110 reprice_lag(...,120)) → the exact 120/180 mismatch the report’s R3 says was “fixed.” Empirically proven: every row withreprice_lag_min ∈ (120,180]hasconverged=Falsein all 5 shippedmeasured_shard_*.json(0,1,3,4,5). The report’s 180-min headline (convergence 0.511, conditional reach@15 0.677, median lag 37.03) was re-derived at analysis time fromreprice_lag_minand never written back —lag_distribution.jsonon disk still showed STALE 120-min numbers (0.4492 / 0.6331 / 29.56). Key insight: 180-min convergence is fully recoverable WITHOUT re-pulling CLOB becausereprice_lag_minis already a clean 180-min measurement (converged@180 == reprice_lag_min is not None); re-derivation exactly reproduced the report (0.5109 / 0.6774 / 37.03, p25=8.37/p75=81.99/p90=127.26, unconditional 0.3461, exactly 1,475 entries gained 120→180). Fix: (a)measure_shard_2.py:113threshold 120.0→180.0; (b)aggregate_lag.pynow computes convergence at aggregation time viais_converged(e)againstCONVERGE_HORIZON_MIN=180.0(lines 51/54–56/61), making the aggregator authoritative on the horizon and immune to stale baked flags in any shard — the DRY fix, matching how the report’s numbers were actually derived. Gotcha: NOlib.pyexists (task referenced one) — CLOB-fetch logic is inlined (and drifted) permeasure_shard_N.py. Each shard ~4,807 entries; shard_2 (16.7% of population) missing its measured output, concentrated in headline cities (london 510, nyc 443, seoul 292, hong-kong 278, atlanta 169, paris 164). Reportresearch/reports/weather-lag-distribution-2026-06-19.md— wx-lag-pipeline-bug - Top-50 copy-trade decypher (v2 of copytrade-top-weather-traders) → “capacity, not return”; ≤0.95 fillable edge plausible but NOT proven. Official Polymarket weather leaderboard (
category=weather, month+all-time union) → 87 candidates → 65-wallet activity gate (min 20 resolved markets, min 17.44M → $1.52M reconstructed PnL** (8.7% cap-weighted, 11.5% median ROI); ROI inversely correlates with absorbable capital — late-settlement whales (n=22) hold 70% of capital at the lowest ROI (6.2%), longshots (n=4) highest ROI (55.5%) on trivial size → whales win on turnover + execution at the close, not edge-per-dollar. Own-edge leans NEGATIVE: the 99c carry is NOT fillable for us; the candidate edge is the repricing-lag layer (cheap-Yes 0.55–0.85 post-METAR-lock pre-reprice), but only 3/10 cohort wallets clear the latency bar, reprice-lag is right-skewed (typical observed <15min vs 20–50min population medians → a 20-min gate rarely fires), and the “cheap fill on a laggy book” claim is contradicted trade-by-trade (winners paid ABOVE book in 11–13/15 → possibly risk-taking not lag-harvest). Dry-rehearsal gate NOT answered affirmatively — reframed as the experiment to settle discount-to-fair vs residual-risk. Copy-trade doctrine = “detect, don’t mirror” (we lose the latency race → detect the lock from our own METAR feed earlier, cohort = confirmation): Tier A copyable (Happening9014, vip68, EngineOfHondaCivic-marginal, WeatherTraderBot, 0x686880, 0bot, cmcbrown, Pityok1) vs Tier B confirmation-only carry whales (NoonienSoong, 1-800-LIQUIDITY). Design-onlycopytradeNATS source spec: rides existing WEATHER_SIGNALS stream, source-routed, cohort-first-mover K-of-N in window W,max_price ≤0.95, UUIDv5Nats-Msg-Iddedup. All per-leg PnL is FIFO-inferred (no settlement outcomes in raw data) → diverges both ways (ColdMath 170× under; longshots ~1/3); 3,500-trade/wallet cap truncates whales; fillability capped at 40 entries/wallet (highest-paid → biased samples). Corrected from v1: BeefSlayer = mid-range directional (not 0.99 whale), ColdMath = recon-failure (not negative control). Reportresearch/reports/weather-top50-decypher.md; dataresearch/copytrade/top50/{top50_profiles.json,fillability.json}— weather-top50-decypher-2026-06-19 - LOWEST-temperature late-settlement = NO-GO (do NOT build the lowest mode). Polymarket runs BOTH highest AND lowest daily temp markets; our engine only trades highest. Clean causal backtest of the symmetric LOW play (
research/strategy/clean_backtest_low.py, resultsclean_low_results.json): running-MIN, morning no-FALL lock,lowest-temperature-…slug. US cohort nyc+miami, 66 locked days (atlanta/chicago had NO lowest markets on Gamma). Verdict NO-GO: reliability caps at 0.803 all-locked / 0.667 tradeable; return mean −6.1% / 80%-LCB −23.1% — BOTH gates fail. Root cause = structural asymmetry: the daily MAX is reliably mid-afternoon (15:00+90min-no-rise lock is safe), but the daily MIN is NOT time-pinned — an evening cold front pushes the min below the dawn low ~20% of the time (12 of 13 misses were post-lock falls), which no lock-hour avoids (sweep flat across 7–12h, since the min is firmly set by 07:00). Confirms the highest play is special; stay highest-only — weather-bet-live-status - June-18 had NO highest market (one-off Polymarket gap) → 0 signals despite 4/4 US cities locking correctly (ATL 87 / MIA 92 / NYC 89 / CHI 72 °F). Gamma: highest june-16/17/19 all exist, june-18 highest MISSING, june-18 lowest present. Engine healthy (locked all 4 on live data), just nothing to trade; the dry rehearsal couldn’t fire. This is what surfaced the lowest-market avenue (→ the NO-GO backtest above) — weather-bet-live-status
2026-06-18
wx-signalLIVE + publishing real signals; dry-rehearsal armed. Engine deployed onpolymarket-infra(GCP) in--loop, pina2fa5ee(pushedgithub.com/wowjeeez/weather-bet, branchfeat/wx-signal-engine). SigNoz confirms real acked publishes 2026-06-17 — Atlanta (16:56 EDT) + Chicago (17:27 CDT) — plus correct skip-on-untradeable once a bucket drifts >0.95. End-to-end NATS pipeline: publisher → JetStreamWEATHER_SIGNALS/weather.signals→pmv2durable consumer (built by @polymarket, dormantmode=off). General source-routed envelope (docs/signal-schema.md, cohort rides the same stream with zero weather change); deterministicsignal_id = uuid5("{source}|{market_slug}|{action}"); separate publisher/consumer NKey identities. At the dry-rehearsal gate (operator flippingweather→dry; next US-afternoon lock = the rehearsal, no live order). Open: publish-retry gap — best-effort publish gated on the Supabase insert, an ack-timeout leaves the signal in Supabase but not on the stream with no retry (flagged to @polymarket: consumer Supabase-reconcile vs publisher re-publish-until-acked). Full snapshot — weather-bet-live-status- ⭐ Live ~400% lag observed on London (EU °C) → automate intl capture (NEXT STEP). The °C NO-GO holds for the late-settlement lock-and-hold (enter near resolution = expensive, 60.8% reliability), but a 400% lag implies a cheap early entry (~0.20) on a strong obs-lead — a different +EV profile (even ~70% reliability at 0.20 entry is hugely +EV). Hypothesis: intl is exploitable via a distinct early-cheap-entry variant, which needs its own backtest before building. This is the next build direction — weather-bet-live-status
2026-06-17
- Honest re-backtest → GO on US fillable cohort, NO-GO intl °C. Clean causal backtest (
research/strategy/clean_backtest.py, reportresearch/reports/strategy-revalidation-honest-2026-06-16.md) corrected the earlier inflated +10.7%. US (NYC/Chicago/Miami/Atlanta), 151 locked days: 96.7% all-locked reliability, but ~53% already >0.95 at lock (locked out of the easy winners); on the ~44% fillable (≤0.95) cohort: 94% win, +29.9% mean return, +17.4% 80%-LCB — real positive edge, smaller day-count. Intl °C: NO-GO (60.8% tradeable reliability, return 80% CI (−13.3%, +31.1%)). Load-bearing unknown = real fills (what the dry-rehearsal measures) — weather-bet-live-status wx-signalengine built + twice max-effort code-reviewed + hardened, committed + pushed. Rust crate mirrorsengine.py(80/80 golden parity). Headline fix: reversed amax_price-defaults-to-0.95 bug into skip-on-untradeable (emit nothing when no confirmed ask ≤0.95, retry next tick). Atomic Supabase INSERT+pg_notifyin one tx; dual-write NATS publisher (dormant untilNATS_URLset);--smokeon-demand e2e flag; serde tolerant to Gamma string/number drift; one sharedreqwest::Client; connect-sink-once — weather-bet-live-status
2026-06-16
- Captured
async-nats0.49 NKey-auth API + edition-2024set_vargotcha (from wiring NKey auth intowx-signal,crates/wx-signal/src/nats.rs). (1) NKey seed auth =ConnectOptions::with_nkey(seed: String)— sync + infallible (returnsSelf), unlikewith_credentials_file(path).await(async,io::Result); builder formConnectOptions::new().nkey(seed); gated behind thenkeysfeature which IS in async-nats’defaultset; seed is validated lazily at connect time (nkeys::KeyPair::from_seed+ nonce sign → bad seed =ConnectErrorAuthentication).SignalPublisher::connect()picks auth by precedenceNATS_NKEY_SEED>NATS_CREDS> no-auth, readsNATS_URLfirst → returnsNone(dormant) if unset before any auth/network work, never logs secrets. (2) GOTCHA:std::env::set_var/remove_varbecameunsafe fnin edition 2024, and this workspace’s[workspace.lints.rust] unsafe_code = "forbid"makes them an absolute dead end in tests — evenunsafe { }won’t compile (forbidcan’t be overridden by#[allow]). Workaround for the dormancy test: assertNATS_URLunset +connect().await.is_none()(holds regardless of seed, since URL is read first) without ever mutating env; plus clippy-pedanticused_underscore_bindingfires if you USE a_-prefixed binding — wx-signal-nats-nkey-auth - VALIDATED weather strategy plan → late-settlement / repricing-lag is the GO play. Deep-dive (full report
research/reports/weather-trader-deep-dive.md, 36.5K) reframes everything: Polymarket closes the book at a FIXED 12:00:00Z for every city → lands at different local times. Asia (UTC+9) closes 21:00 local = day OVER, bucket already ~1.0 (no edge); US (07-08:00 local) and London (13:00) close BEFORE the afternoon peak, so the winning bucket trades only 0.40-0.57 at close = genuine forecast game, 0.43-0.61 gap to 2.88M/2.07M at 0.998 median entry — revealed-preference, can’t withdraw losing capital); early-forecast real but tiny (2%); round-trip/sell-the-pop = lowest PnL, AVOID. Edge quantified across 113 full-coverage market-days: winning bucket first crosses 0.90 with ~6-8c left (~3h out) → ~1.5c at 2h → ~0.4c at 1h; >=0.90 in final 2-3h resolved YES 55/55 = 100% (risk-free once daily high observed). Whales leave 4-8c on the table (wait for 0.99, ~0.5c at size) → alpha = take the 0.90-0.95 crossing they decline, gated to final 2-3h (the one false positive — Beijing 29C@0.93 then 30C won — was a degree-boundary day acted on 6h early → the time gate is load-bearing). Entry rule: poll resolution station METAR per-minute → running whole-degree daily max → high pinned + final 2-3h → BUY 0.90-0.95 crossing → hold to 750 bond). Wrong-station traps: Dallas=KDAL not DFW, Houston=KHOU, Denver=KBKF, Moscow=UUWW, Chicago=KORD, skip Hong Kong (0.1°C city station). TWO products: global=Wunderground (we trade this) vs US/CFTC=NWS CLI/KNYC. Resources: repos suislanchez/polymarket-kalshi-weather-bot (441★), alteregoeth-ai/weatherbot (332★), warproxxx/poly_data (2.1k★), py-clob-client + real-time-data-client, avwx-engine; real PM weather leaderboard (gopfan2 ~277k); IEM ASOS archive (backtest ground-truth); ColdMath autopsy (polymarketweather.com). NEXT (validate-first): Python backtest w/ real multi-city Wunderground/METAR resolution feed to confirm 6-8c edge OOS → then build live Rust nowcaster on the validated rule — weather-strategy-validated-plan - Copy-trade recon → the edge is nowcasting the winning bucket, NOT buy-cheap/sell-pop. Reverse-engineered on-chain trades (
data-api.polymarket.com/trades?user=<addr>, ~3,500 most-recent each, offset cap 3000) for the top weather traders off weatherbot.fi/api/leaderboard(13 listed but ranks GENERAL PnL not weather; Poligarch & russell110320 trade ~0 weather → ~10 real ones, trading dozens of cities). Three archetypes: (1) late near-certain settlement = the whales / real money — HondaCivic 1 (+ BeefSlayer/aboss/ColdMath); edge = capacity+fast execution at close, low variance; (2) early forecast value — Railbird (favorites ~0.85 at open), dpnd (median 33h pre-resolution, cheap/mid), Poligarch; (3) round-trip/sell-the-pop = the user’s original idea — ONLY huskyvs (47% round-trip) & ShyGuy1 (34%), and huskyvs is LAST in PnL ($18K, rank 12). Killer example: HondaCivic London 18°C Apr 30 — BUY 7,523@0.328 then SELL 4,001@0.999 six minutes later (bucket repriced 0.33→1.00 intraday because they saw it win first). Independently confirms the drift arc: naive drift NO-GO −28% → forecast-conditioning priced-in/breakeven → live nowcast = only surviving edge, and the top weather trader makes their money exactly there. Script+data:research/copytrade/{analyze.py,profiles.json,raw/}. NEXT: scope a nowcast strategy (entry rule, obs source/latency, repricing-lag backtest off cached price curves) — copytrade-top-weather-traders
2026-06-15
- Intraday drift study v2 → forecast-conditioned is a live CANDIDATE. Re-analysis off the same 112-market-day cache (no re-fetch): forecast proximity is a real winner-predictor (unlikely 10–25¢, NEAR
|dist|≤1°Cof lead-2 forecast: n=125, 20.0% win, blended +1.2% after costs vs FAR −19.9%; win-rate peaks at dist 0/+1 ~21%, collapses to ~5% at ≤−2°C → buy AT/just-ABOVE forecasted high). Exit timing dominates: same NEAR universe, first-+50%-pop → +1.2% but hold to 18:00 local convergence → +9.6% (80% LCB −6.9%, hi +26.4%). Liquidity NOT the blocker (median bucket vol ~1K). Progression buy-blind ≈−15% → near-forecast +1% → near-forecast+18:00 +9.6%; two levers stack. CANDIDATE not GO (n=125, single month, LCB still negative). Confirms 2026-06-11 nowcast/information-timing pivot. NEXT: extend window to several months (tighten CI), tighten to dist∈{0,+1} + sweep exit hour 16–20h (free off cache), add live intraday nowcast — intraday-drift-study-naive-no-go - Intraday drift study → naive buy-open/sell-pop is NO-GO. 112 market-days (28 dates × 4 cities; London/Paris/Seoul/Beijing max-temp), 1,224 buckets w/ intraday curves + lead-0/1/2 Open-Meteo forecasts + realized highs (cache
research/data/, reportresearch/report.md, code onresearch/intraday-drift). Drift is REAL (unlikely 10–25¢ cohort: 49% hit ≥+50% peak, median MFE 10¢, peaks 17–19h local) but pre-registered test on ultimately-LOST unlikely buckets w/ +50% causal exit after spread costs = mean net −27% (80% boot CI −32%..−22%, n=422) due to survivorship bias (~half decay to zero, can’t pick winners at open). Same exit on ultimately-WIN buckets = +54% → edge is in PREDICTING winners (forecast-distance), not the drift; confirms the 2026-06-11 nowcast/information-timing pivot. NEXT: condition on ±1°C of lead-2 forecast + time-based exits around 17–19h peak (runs off cache in seconds) — intraday-drift-study-naive-no-go - Added
wx-researchcrate skeleton to workspace (branchresearch/intraday-drift): 7 empty module stubs (cache,charts,fetch,metrics,report,slugs,types), new workspace depsplotters 0.3+csv 1, builds cleanly (86 crates) — wx-research-crate - Added
ClobPriceHistorytowx-market/src/prices_history.rs: read-only CLOB price-history client, two public async methods (historyinterval-capped,history_windowunix-timestamp windowed), 3-retry / 200ms-base backoff, TDD with wiremock — wx-market-prices-history - Added
TradesClienttowx-market/src/trades.rs: read-only data-api trades client,page(asset, limit, offset)single-page fetch +all_by_asset(asset)auto-paginator (PAGE=500, cap 20k),de_flex_i64flex deserializer for string/number timestamp API drift, 3-retry / 200ms linear backoff, wiremock test passing — wx-market-trades-client
2026-06-12
- Fixed
resolve_oneto use TWC as primary resolution source with IEM as cross-check fallback; was silently skipping ~half of markets due to IEM lag/flakiness; 3 regression tests added, 25/25 green — fix-resolve-twc-primary-source
2026-06-09
- Storage migrated SQLite → Supabase Postgres (dedicated schema
weather_bet), killing the ephemeral-storage footgun (missingWXBET_DB_PATH→ data loss on every container recreate). Captured the durable fleet knowledge: dedicated-schema isolation convention, idempotent embeddedschema.sqlviaraw_sql(deliberately NOTsqlx::migrate!), the SQLite→Postgres strict-typing gotchas ($Nplaceholders,DOUBLE PRECISIONfor f64,BIGINT↔i64,CURRENT_DATE+::datecasts,INNER JOIN LATERALfor latest-per-group), the session-pooler-:5432-NEVER-:6543connection rule, and thetestcontainers-behind-a-testkit-feature test harness (88/88 green vs real Postgres). Code complete + verified (clippy/fmt clean), NOT yet committed; @deploy handed off (addSUPABASE_DB_URL, dropWXBET_DB_PATH+/var/lib/weather-betvolume). — weather-bet-supabase-migration
2026-06-07
- Created weather-bet project; documented verified Polymarket weather market structure (11 neg-risk 1°C buckets, ICAO/Wunderground whole-°C resolution, Gamma API endpoints, observed liquidity) — polymarket-weather-market-structure
- Initialized project overview — weather-bet