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: