cleanup: archive everything the live system no longer touches

- retired-infra/: Railway config, Mac launchd runner, GH-Actions cron
  (worker is Fly.io arn now; Actions cron last fired 2026-07-02)
- live-research/: the June selection experiments (strategy/followability/
  pnl_basket/pnl_focused/backtest_june/clean_test + outputs)
- us-venue/: the scrapped Polymarket-US listability probe (+ its
  env-gated ONLY_CONDS replay filter stays in portfolio.py — generally
  useful for subset replays)
- root sweeps hunt/huntwide/oos/copyback/watch.json -> archive/
- untracked logs/CSVs of dead experiments -> archive/local/ (gitignored)
- READMEs updated: Fly migration, geoblock gotcha, new file map

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-06 17:18:45 -04:00
parent 824f7d7e12
commit 8958279ee8
28 changed files with 4325 additions and 22 deletions
+1
View File
@@ -41,3 +41,4 @@ live/*_scored.json
live/watch_prejune*.json
live/history/
live/slug_cache.json
archive/local/
+31 -19
View File
@@ -24,12 +24,13 @@ Three deployed pieces + one static dashboard:
| piece | where it runs | what it does |
|-------|--------------|--------------|
| **daily pipeline** (`live/daily.sh`) | this Mac, launchd **08:00** (runs on wake if the Mac was asleep) | refresh the bet cache → 5-gate skill scan → fee-aware sharp selection → conviction floors → backtest book → publish JSON feeds to GitHub |
| **copybot worker** (`copybot.py` via `host/start.sh`) | Railway, 24/7 | **push mode**: an Alchemy address-activity webhook (`copybot follow set`, Polygon) pings `POST /alchemy` the moment a followed wallet trades (~25s detection), signature-verified; a 60s heartbeat settles/publishes and a 5-min backstop poll catches dropped pushes. Paper-copies with real fees/lag/slippage, settles at CLOB resolution, commits its book back to the repo |
| **copybot worker** (`copybot.py` via `host/start.sh`) | **Fly.io app `wwf-copybot`, region `arn` (Stockholm), 24/7** — migrated off Railway 2026-07-06: Railway ran it in a US region, which Polymarket's IP geoblock would 403 the moment orders got real; Stockholm is unrestricted AND ~25ms from the CLOB's eu-west-2 primaries. Every boot self-checks the geo-gate (`host/geocheck.py`) | **push mode**: an Alchemy address-activity webhook (`copybot follow set`, Polygon) pings `POST /alchemy` the moment a followed wallet trades (~25s detection), signature-verified; a 60s heartbeat settles/publishes and a 5-min backstop poll catches dropped pushes. Paper-copies with real fees/lag/slippage, settles at CLOB resolution, commits its book back to the repo |
| **Discord digest** (`live/discord_daily.py`) | end of the daily pipeline | one message/day: the sharp list with profile links + 30-day conviction stats (per-trade pings retired 2026-07-04; the old Alchemy watcher lives in `archive/webhook_receiver.py`) |
| **dashboard** | [jaxperro.com/trading](https://jaxperro.com/trading) (static, in the `jaxperro` repo) | renders the three JSON feeds: live bot book, backtest book, sharp table |
**The July 2026 live test:** a fresh $1,000 paper book (started 2026-07-02, on
Railway) following the wallet set in **`live/copybot.paper.json`** — the single
**The July 2026 live test:** a fresh $1,000 paper book (started 2026-07-02 on
Railway; moved to Fly.io Stockholm 2026-07-06 with the book intact) following
the wallet set in **`live/copybot.paper.json`** — the single
source of truth (currently 5 volume + 3 whale wallets; the dashboard hero lists
them live). Two stake classes: **volume** wallets (4% of equity/bet) are copied
on their **conviction bets only** (auto p80 floor derived at boot); **whale**
@@ -43,7 +44,7 @@ up, real money follows (see [`LIVE_TEST.md`](LIVE_TEST.md)).
```
data layer selection execution display
────────── ───────── ───────── ───────
live/cache.duckdb ──▶ trust.py (trusted-row filter) copybot.py (Railway) ─▶ jaxperro.com/trading
live/cache.duckdb ──▶ trust.py (trusted-row filter) copybot.py (Fly arn) ─▶ jaxperro.com/trading
(schema v2: skill.py (5-gate funnel) · class % of equity, · copybot_live.json
35k wallets, conviction_scan.py (p80 + z_all) capped at the (live bot book)
20M+ resolved bets, validate_timing.py (fee-aware signal's own bet · portfolio.json
@@ -59,11 +60,11 @@ up, real money follows (see [`LIVE_TEST.md`](LIVE_TEST.md)).
| task | how |
|------|-----|
| **add / remove / reclass a LIVE wallet** | edit the `wallets` list in `live/copybot.paper.json` (`{"wallet","name","class":"volume"\|"whale","floor":123?}` — floor optional, auto p80 at boot; whales ignore floors) then run **`./live/deploy_bot.sh`** — it validates, previews, **syncs the Alchemy webhook's address list** (`live/sync_webhook.py`, Notify API), commits, pushes, redeploys Railway, and confirms the boot banner. Fully self-contained — nothing to click in any dashboard |
| **add / remove / reclass a LIVE wallet** | edit the `wallets` list in `live/copybot.paper.json` (`{"wallet","name","class":"volume"\|"whale","floor":123?}` — floor optional, auto p80 at boot; whales ignore floors) then run **`./live/deploy_bot.sh`** — it validates, previews, **syncs the Alchemy webhook's address list** (`live/sync_webhook.py`, Notify API), commits, pushes, restarts the Fly machine, and confirms the boot banner. Fully self-contained — nothing to click in any dashboard |
| **backtest any wallet set** | edit `live/backtest.json` (same entry shape) → `python3 live/portfolio.py`; ad-hoc without touching the dashboard: `python3 portfolio.py --wallets 0xabc,0xdef:whale --days 14 --out /tmp/t.json` |
| **promote a wallet to live** | prove it in `backtest.json` first, copy the same entry into `copybot.paper.json`, run `deploy_bot.sh` |
| **watch the live bot** | `railway logs --service copybot` (one summary line per 60s poll); the book is also committed as `live/copybot_live.json` and rendered on the dashboard |
| **restart / redeploy the bot** | `railway redeploy --service copybot --yes` (config/code changes: the worker clones the repo fresh at boot). **Changes to `host/start.sh`, `railway.json`, or env vars need a full rebuild: `railway up --service copybot --detach`** — redeploy reuses the old image and env snapshot (this bit us: the image's launcher was frozen for days) |
| **watch the live bot** | `flyctl logs --app wwf-copybot` (one summary line per 60s heartbeat); the book is also committed as `live/copybot_live.json` and rendered on the dashboard |
| **restart / redeploy the bot** | `flyctl apps restart wwf-copybot` (config/code changes: the worker clones the repo fresh at boot, so a restart IS the deploy). **Changes to `host/` or `fly.toml` need an image rebuild: `flyctl deploy --remote-only`.** Secrets: `flyctl secrets set K=V` (applies with an automatic restart). Keep it ONE machine — Fly loves creating a second for "high availability", and two bots = two writers on one book (`flyctl scale count 1`) |
| **run the daily pipeline manually** | `cd live && bash daily.sh` (launchd runs it 08:00; ~40 min, mostly collect). Never run two at once — the cache is single-writer |
| **refresh just the sharp list** | `cd live && python3 conviction_scan.py && python3 validate_timing.py` |
| **daily Discord digest** | sent by `live/discord_daily.py` at the end of `daily.sh`; webhook = `daily_webhook` in gitignored `config.json` |
@@ -71,10 +72,11 @@ up, real money follows (see [`LIVE_TEST.md`](LIVE_TEST.md)).
Three moving parts talk to each other: this repo (research + bot + feeds), the
`jaxperro` repo (static dashboard reading this repo's raw JSON feeds), and the
Railway project `magnificent-kindness` (service `copybot`; the old `web`
watcher service is retired). The bot **commits its own state/feed back to this
repo** every few minutes — always `git pull --rebase --autostash` before you
push (every script here already does).
Fly.io app `wwf-copybot` (Stockholm; the old Railway project
`magnificent-kindness` is stopped and can be deleted). The bot **commits its
own state/feed back to this repo** every few minutes — always
`git pull --rebase --autostash` before you push (every script here already
does).
---
@@ -85,20 +87,20 @@ push (every script here already does).
| `live/` | **the current system**: cache, scanners, sharp selection, backtest, daily pipeline ([live/README](live/README.md)) |
| `live/trust.py` | the trusted-row filter every selector must read through (see gotchas 89) |
| `live/backtest.json` · `live/copybot.paper.json` | the two wallet-set configs: backtest experiments vs. the live paper bot (same entry shape) |
| `live/deploy_bot.sh` | one-command live-bot deploy: validate → preview → Alchemy address sync → commit → push → Railway redeploy → confirm boot |
| `live/deploy_bot.sh` | one-command live-bot deploy: validate → preview → Alchemy address sync → commit → push → Fly restart → confirm boot |
| `live/sync_webhook.py` | diffs the follow set against the push-mode Alchemy webhook and patches add/remove (token in gitignored `config.json`) |
| `railway.json` · `nixpacks.toml` · `.railwayignore` | Railway build config: NIXPACKS builder + `bash host/start.sh` start command pinned as code; secrets/cache excluded from build uploads |
| `fly.toml` · `fly.Dockerfile` | Fly.io app config (wwf-copybot, region `arn`, single machine, `:8080` webhook ingress) + the worker image (python + git; the bot code itself is cloned fresh at boot) |
| `host/geocheck.py` | 3-probe geo-gate verdict (ipinfo → Polymarket's `/api/geoblock` → unauth CLOB order POST); runs at every boot, `GEOCHECK_ONLY=1` = probe-and-idle for testing a new host/region without a second book-writer |
| `live/discord_daily.py` | the daily Discord digest (the only Discord output) |
| `copybot.py` | the copy-trading bot: push/poll trigger → follow filter → execution engine (paper + live) |
| `archive/copytrade.py` | the execution engine the bot reuses: sizing, risk gates, price guard, paper/live executors |
| `host/start.sh` | 24/7 worker bootstrap for Railway/Fly/VPS (clones repo, resumes committed state) |
| `host/start.sh` | 24/7 worker bootstrap for Fly/any VPS (geo-gate check, clones repo, resumes committed state) |
| `LIVE_TEST.md` · `preflight_live.py` · `redeem.py` | real-money runbook, read-only credential preflight, on-chain redemption |
| `insider.py` | the original detector: z-score, pre-resolution timing, fresh-wallet flags, funding-cluster rings |
| `smart_money.py` | shared HTTP helper + survivorship-corrected win-rate dashboard (`:8899`) |
| `archive/webhook_receiver.py` | retired 2026-07-04: Alchemy webhook → per-trade Discord pings (replaced by `live/discord_daily.py`'s daily digest) |
| `wide/` | frozen-subgraph bulk scanner (1.76M wallets, historical only — subgraph froze Jan 2026) |
| `archive/` | the six strategies that didn't work, kept honest ([archive/README](archive/README.md)) |
| `hunt.py` · `huntwide.py` · `oos.py` · `copyback.py` | earlier research sweeps/backtests (superseded by `live/`) |
| `archive/` | everything retired, kept honest ([archive/README](archive/README.md)): the six failed strategies, earlier research sweeps (`hunt/huntwide/oos/copyback`), the superseded live selection layer (`live-research/`), the scrapped Polymarket-US venue probe (`us-venue/`), and retired infra (`retired-infra/`: Railway config, Mac launchd runner, GH-Actions cron) |
---
@@ -166,7 +168,7 @@ in [`live/README.md`](live/README.md).
| `live/copybot.paper.json` | **committed** (no secrets): the live paper bot's wallet set + classes + follow/risk params — deploy with `live/deploy_bot.sh` |
| `config.json` | `daily_webhook` (Discord digest) · `alchemy_notify_token` + `alchemy_webhook_id` (webhook address sync) · `alchemy_signing_key` (local push-mode runs) · Alchemy RPC key · a legacy curated watchlist + floors (`sync_floors.py`) |
| `config.live.json` | live-trading credentials (`private_key`, `funder_address`) + tiny test caps — see `LIVE_TEST.md` |
| Railway `copybot` service | `GITHUB_TOKEN` (fine-grained PAT, contents-RW on this repo — the bot commits its state/feed back) · `ALCHEMY_SIGNING_KEY` (presence = push mode; delete it to fall back to 60s polling) · `PORT=8080` (the public domain routes here) |
| Fly `wwf-copybot` secrets | `GITHUB_TOKEN` (fine-grained PAT, contents-RW on this repo — the bot commits its state/feed back) · `ALCHEMY_SIGNING_KEY` (presence = push mode; remove it to fall back to 60s polling) · `DISCORD_WEBHOOK`. The webhook ingress is `https://wwf-copybot.fly.dev/alchemy` (Alchemy webhook `wh_blf4qjjvfdbqs9mc`, address list auto-synced by `deploy_bot.sh`) |
---
@@ -215,7 +217,7 @@ backtest and bot share:
Safety: paper is the default; live requires `mode:"live"` **and** `--live`
**and** a typed confirmation phrase, under hard caps. The GH-Actions cron
runner is retired (GitHub throttled `*/5` to ~2h in practice — it copied 1 of
~104 qualifying trades in June; the always-on Railway poller replaced it).
~104 qualifying trades in June; the always-on worker replaced it).
---
@@ -278,7 +280,17 @@ runner is retired (GitHub throttled `*/5` to ~2h in practice — it copied 1 of
a transiently failed re-pull then hides the wallet's whole history from
exact `pulled_at` checks (trust.py has a 14-day fallback for this).
10. **Hold-to-resolution P&L is a copy ceiling, not the wallet's bank
10. **Polymarket geoblocks ORDER PLACEMENT by IP** (reads are open everywhere —
which is why the paper bot never noticed Railway was in a US region).
Restricted list has surprises: US, UK, France, Germany, Italy, Netherlands,
Poland, Singapore, Australia, Ontario, **Brazil**
(docs.polymarket.com/developers/CLOB/geoblock). `host/geocheck.py` gives a
3-probe verdict from any box and runs at every worker boot; **never go live
from a box that doesn't print `VERDICT: TRADABLE`** — and the machine's
location doesn't relocate *you* (trade live from Colombia months, paper
from US months).
11. **Hold-to-resolution P&L is a copy ceiling, not the wallet's bank
statement.** The dashboard's Conv/All-Time P&L columns price every entry
held to resolution at the wallet's own stakes — the right yardstick for a
copier that holds, and the wrong one for judging the wallet itself.
+14
View File
@@ -19,3 +19,17 @@ run one you'd adjust the import path.
| `xarb.py` | Cross-venue scanner: match the same event on Polymarket vs Kalshi, flag price gaps. | Venues priced efficiently (~1¢); both legs cost >$1 after fees. |
The keeper that came out of all this lives at the repo root: `insider.py`.
## Later additions (2026-07-06 cleanup)
| Path | What it was | Why it's here |
|------|-------------|---------------|
| `hunt.py` / `huntwide.py` / `oos.py` / `copyback.py` / `watch.json` | pre-`live/` research sweeps: candidate hunts, out-of-sample replays, copy-backtests | superseded by the `live/` selection layer (skill → conviction → validate_timing) |
| `live-research/` | the June 2026 selection experiments: `strategy.py`, `followability.py`, `pnl_basket/focused.py`, `backtest_june.py`, `clean_test.sh` + their outputs | the research is settled (see FINDINGS); the surviving ideas live on in `live/` |
| `us-venue/` | `us_listable.py` — mapped the replay stream against Polymarket US listings (95/794 matched; US settles off-chain via FCM/clearinghouse, so no wallet tracking) | the US move was scrapped 2026-07-06: no on-chain wallets, no copyable signal |
| `retired-infra/` | Railway build config (`railway.json`, `nixpacks.toml`, `.railwayignore`, `runtime.txt`), the Mac launchd runner (`com.jaxperro.copybot.plist`, `run_copybot.sh`), the GH-Actions cron (`copybot.workflow.yml`) | the worker now runs on Fly.io `arn` (see root README); the Actions cron was throttled to uselessness; the Mac poller was replaced by the cloud worker |
| `local/` | untracked logs/CSVs/state from the above experiments | kept out of git; safe to delete wholesale |
**Still-live code in this directory:** `copytrade.py` is imported by the root
`copybot.py` as its execution engine (sizing, risk gates, executors) — archived
as a *strategy* (raw copy-trading is EV), kept as a *library*.
View File
View File
View File
View File
File diff suppressed because it is too large Load Diff
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""Which of the replayed bets' markets were ALSO listed on Polymarket US?
Polymarket US (gateway.polymarket.us, public/no-auth) settles off-chain — no
wallets to copy — but it lists the same game markets, and it RETAINS closed
historical listings (back to Oct 2025). So we can check, for every bet in the
portfolio.py replay stream, whether a real US listing existed for that market:
tier 1 deterministic slug: US slug = "aec-"/"tec-" + intl slug
(holds for esports/team sports; batched via the repeatable ?slug= param)
tier 2 name+date+line match: tennis abbreviates differently per venue
(US trimcc/pedsak vs intl mccormi/sakamo; itfme-/itfwo- vs itf-), so
match full names from the intl question against a US window of the
bet date's moneyline/totals/spreads listings.
Sub-market winners ("Set 1 Winner: …") name-match the WRONG (full-match) US
market, so anything with a set/map/half qualifier is tier-1 only.
Output -> us_listable.json {cond: {listed, us_slug, how, title}}. Replay just
the listable subset with:
ONLY_CONDS=us_listable.json PORTFOLIO_OUT=/tmp/pf_us.json python3 portfolio.py
"""
import json
import os
import re
import ssl
import time
import unicodedata
import urllib.request
from collections import Counter
from concurrent.futures import ThreadPoolExecutor
import portfolio as pf
_SSL = ssl._create_unverified_context()
GATEWAY = "https://gateway.polymarket.us/v1/markets"
HERE = os.path.dirname(__file__)
OUT = os.path.join(HERE, "us_listable.json")
def get(url):
for attempt in range(3):
try:
r = urllib.request.urlopen(urllib.request.Request(
url, headers={"User-Agent": "Mozilla/5.0"}), timeout=30, context=_SSL)
return json.loads(r.read())
except Exception:
time.sleep(1 + attempt)
return {}
def norm(s):
s = unicodedata.normalize("NFD", s or "").encode("ascii", "ignore").decode().lower()
return re.sub(r"\s+", " ", re.sub(r"[^a-z0-9 ]", " ", s)).strip()
def name_in(name, hay):
ws = [w for w in name.split() if len(w) > 1]
return bool(ws) and all(w in hay for w in ws)
# sub-market qualifiers: a "Set 1 Winner"/"First Map" market would name-match
# the full-match US moneyline — a false positive — so these skip tier 2
QUAL = re.compile(r"\b(set \d|1st|first|2nd|second|3rd|third|map \d|game \d|handicap|"
r"to score|half|quarter|period|race to|双|correct score|doubles|"
r"corners|cards|btts|total sets)\b", re.I)
def parse_title(t):
"""intl question -> {a, b, kind, line} or None (None = tier-1 only)."""
s = str(t or "")
ou = re.search(r"O/U\s+(\d+(?:\.\d+)?)", s, re.I)
hc = re.search(r"\([+-](\d+(?:\.\d+)?)\)", s)
kind = "totals" if ou else "spreads" if hc else "moneyline"
line = ou.group(1) if ou else (hc.group(1) if hc else None)
if QUAL.search(s):
return None
s = re.sub(r"\(BO\d+\)", " ", s, flags=re.I)
s = re.sub(r"\([+-]?\d+(?:\.\d+)?\)", " ", s)
parts = re.split(r"\svs\.?\s", s, flags=re.I)
if len(parts) < 2:
return None
a = parts[0].split(":", 1)[1] if ":" in parts[0] else parts[0]
# esports format "Counter-Strike: A vs B (BO3) - IEM Cologne Playoffs":
# drop the " - Tournament" tail and any ": qualifier" tail from side b
b = parts[1].split(":")[0].split(" - ")[0]
a, b = norm(a), norm(b)
if not a or not b:
return None
return {"a": a, "b": b, "kind": kind, "line": line}
_WINDOWS = {} # (kind, date) -> [{slug, hay}]
def us_window(kind, date):
"""All US <kind> markets listed around <date> (startDate ≈ listing time,
day-of/day-before for game markets), closed ones included."""
key = (kind, date)
if key in _WINDOWS:
return _WINDOWS[key]
t = time.mktime(time.strptime(date, "%Y-%m-%d"))
iso = lambda x: time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(x))
lo, hi = iso(t - 2 * 86400), iso(t + 2 * 86400)
out = []
# Wimbledon-week windows run past 2k moneylines — never truncate silently
for off in range(0, 20000, 100):
d = get(f"{GATEWAY}?marketTypes={kind}&startDateMin={lo}&startDateMax={hi}"
f"&limit=100&offset={off}")
ms = d.get("markets", [])
for m in ms:
out.append({"slug": m["slug"],
"hay": norm(m.get("question", "")) + " " + norm(m["slug"])})
if len(ms) < 100:
break
_WINDOWS[key] = out
return out
def main():
# the exact bet stream portfolio.py replays (same dedupe: first entry per market)
resolved = pf.window_bets()
opened = pf.open_bets()
by = {}
for b in resolved + opened:
if b.get("cond") and (b["cond"] not in by or b["entry_t"] < by[b["cond"]]["entry_t"]):
by[b["cond"]] = b
conds = list(by)
print(f"[us] {len(conds)} unique markets in the {pf.DAYS}d replay stream", flush=True)
with ThreadPoolExecutor(max_workers=8) as ex:
list(ex.map(pf.market_meta, conds))
meta = {c: pf.market_meta(c) for c in conds}
result = {}
# ---- tier 1: deterministic prefixed slugs, batched --------------------
cand = {} # us_slug -> cond
for c in conds:
slug = meta[c]["slug"]
if slug:
cand["aec-" + slug] = c
cand["tec-" + slug] = c
slugs = list(cand)
for i in range(0, len(slugs), 25):
qs = "&".join("slug=" + s for s in slugs[i:i + 25])
for m in get(f"{GATEWAY}?{qs}&limit=100").get("markets", []):
c = cand.get(m["slug"])
if c and c not in result:
result[c] = {"listed": True, "us_slug": m["slug"], "how": "slug",
"title": meta[c]["title"]}
print(f"[us] tier 1 (direct slug): {len(result)} listed", flush=True)
# ---- tier 2: name + date + line ---------------------------------------
t2 = 0
for c in conds:
if c in result:
continue
title, slug = meta[c]["title"], meta[c]["slug"]
p = parse_title(title)
dm = re.search(r"(\d{4}-\d{2}-\d{2})", slug or "")
if not p or not dm:
result[c] = {"listed": False, "how": "no-parse", "title": title}
continue
hit = None
for m in us_window(p["kind"], dm.group(1)):
if not (name_in(p["a"], m["hay"]) and name_in(p["b"], m["hay"])):
continue
if p["line"] and norm(p["line"]) not in m["hay"]:
continue
hit = m
break
if hit:
t2 += 1
result[c] = {"listed": True, "us_slug": hit["slug"], "how": "name",
"title": title}
else:
result[c] = {"listed": False, "how": "unmatched", "title": title}
print(f"[us] tier 2 (name+date): {t2} more listed "
f"({len(_WINDOWS)} US windows fetched)", flush=True)
json.dump(result, open(OUT, "w"), indent=1)
n_listed = sum(1 for v in result.values() if v["listed"])
print(f"[us] TOTAL: {n_listed}/{len(conds)} replayed markets had a US listing "
f"-> {os.path.basename(OUT)}", flush=True)
# breakdown by intl slug prefix so we can see WHERE coverage holds
pre = lambda s: (s or "?").split("-")[0]
for label, keep in [("listed", True), ("unlisted", False)]:
ctr = Counter(pre(meta[c]["slug"]) for c, v in result.items() if v["listed"] == keep)
print(f"[us] {label:9s} " + " ".join(f"{k}:{n}" for k, n in ctr.most_common(14)),
flush=True)
if __name__ == "__main__":
main()
View File
+3 -3
View File
@@ -40,8 +40,8 @@ underdog/longshot prices — the copyable alpha), `balanced`, or `favorite`
| sharps | `conviction_scan.py` + `validate_timing.py` | conviction-profile scan → copy-positive-holder selection → `watch_sharps.json` (see "The repeatable find") |
| portfolio | `portfolio.py` | $1k paper book off the cache → `portfolio.json` (see "Paper portfolio") |
| dashboard | `dashboard.py` | self-contained `dashboard.html` — sortable, archetype-tagged, live recent-trade lookup |
| backtest | `backtest_june.py [arch]` | copy an archetype's June-1+ entries, $1000, no lag → P&L |
| clean test | `clean_test.sh` | **the honest test**: re-select on pre-June-1 data only, then backtest June-1+ forward |
| backtest | `../archive/live-research/backtest_june.py [arch]` | copy an archetype's June-1+ entries, $1000, no lag → P&L (archived 2026-07-06 with the rest of the June research layer) |
| clean test | `../archive/live-research/clean_test.sh` | **the honest test**: re-select on pre-June-1 data only, then backtest June-1+ forward |
## The cache is the point
@@ -84,7 +84,7 @@ turn into losers out-of-sample. **Don't copy favorite-riders.** The `value`
archetype (beats underdog prices) is where real alpha may live — test it with
`backtest_june.py value`.
## Strategy backtests
## Strategy backtests (June research layer — archived to `../archive/live-research/` 2026-07-06)
- `strategy.py` — train (pre-May-30) / test (June1+) wallet selection on copy-ROI
+ z + monthly consistency + diversification. → `selection.json`.
+13
View File
@@ -251,6 +251,19 @@ def main():
b["kind"] = "open"; by_mkt[b["cond"]] = b
stream = sorted(by_mkt.values(), key=lambda b: b["entry_t"])
# ONLY_CONDS=<json path>: replay only these markets — {cond: bool} or
# us_listable.py's {cond: {"listed": bool, ...}}. Models "same signal, but
# I can only execute the subset" (e.g. bets also listed on Polymarket US);
# thresholds/sizing still come from the full signal, capital only chases
# the executable bets.
_only = os.environ.get("ONLY_CONDS")
if _only:
_allow = {c for c, v in json.load(open(_only)).items()
if (v.get("listed") if isinstance(v, dict) else v)}
_pre = len(stream)
stream = [b for b in stream if b["cond"] in _allow]
print(f"portfolio: ONLY_CONDS filter kept {len(stream)}/{_pre} bets", flush=True)
# prefetch every replayed market's slug (threaded; disk-cached) so the
# event-correlation cap can group markets by real-world event
with ThreadPoolExecutor(max_workers=8) as ex: