14522262e6
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
191 lines
7.8 KiB
Python
191 lines
7.8 KiB
Python
"""
|
|
Arena del gioco-OPZIONI: 100 agenti ciechi propongono STRUTTURE in opzioni su due
|
|
serie anonime (A=BTC, B=ETH). Torneo identico al gioco-prezzi (3 finestre TRAIN/VALID/
|
|
TEST, 90 epoche, cull 10% ogni 10 epoche -> 10 finalisti), ma le strategie sono opzioni
|
|
prezzate con BS + skew + DVOL (scripts/games/options_engine.py).
|
|
|
|
uv run python -m scripts.games.options_arena # 100 agenti random (test)
|
|
GAME_SPECS_DIR=... GAME_OUT=... uv run python -m scripts.games.options_arena --from-specs
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import random
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
|
|
from scripts.games.options_engine import (load_opt, splits3, evaluate, STRUCTURES)
|
|
|
|
OUT = Path("data/games"); OUT.mkdir(parents=True, exist_ok=True)
|
|
|
|
# spazio parametri: (min, max, tipo)
|
|
PSPACE = dict(otm=(0.02, 0.20, "f"), width=(0.02, 0.12, "f"), dte=(7, 45, "i"))
|
|
SERIES = ["A", "B"]
|
|
|
|
|
|
def _rand(rng, lo, hi, typ):
|
|
return int(rng.randint(int(lo), int(hi))) if typ == "i" else round(rng.uniform(lo, hi), 3)
|
|
|
|
|
|
def random_spec(rng):
|
|
p = {k: _rand(rng, *v) for k, v in PSPACE.items()}
|
|
return {"structure": rng.choice(STRUCTURES), "series": rng.choice(SERIES), "params": p}
|
|
|
|
|
|
def _normalize(spec):
|
|
st = spec.get("structure")
|
|
if st not in STRUCTURES:
|
|
st = "short_put"
|
|
out = {"structure": st, "series": spec.get("series") if spec.get("series") in SERIES else "A",
|
|
"params": {}}
|
|
src = spec.get("params", {})
|
|
for k, (lo, hi, typ) in PSPACE.items():
|
|
v = src.get(k, (lo + hi) / 2)
|
|
try:
|
|
v = float(v)
|
|
except Exception:
|
|
v = (lo + hi) / 2
|
|
v = max(lo, min(hi, v))
|
|
out["params"][k] = int(round(v)) if typ == "i" else round(v, 3)
|
|
# flatten per evaluate (structure/otm/width/dte)
|
|
out["structure"] = st
|
|
return out
|
|
|
|
|
|
def _flat(spec):
|
|
return {"structure": spec["structure"], **spec["params"]}
|
|
|
|
|
|
def mutate(spec, rng, strength=0.25):
|
|
s = json.loads(json.dumps(spec))
|
|
keys = list(PSPACE)
|
|
for k in rng.sample(keys, k=rng.randint(1, 2)):
|
|
lo, hi, typ = PSPACE[k]
|
|
span = (hi - lo) * strength
|
|
nv = max(lo, min(hi, s["params"][k] + rng.uniform(-span, span)))
|
|
s["params"][k] = int(round(nv)) if typ == "i" else round(nv, 3)
|
|
if rng.random() < 0.12:
|
|
s["structure"] = rng.choice(STRUCTURES)
|
|
if rng.random() < 0.05:
|
|
s["series"] = rng.choice(SERIES)
|
|
return s
|
|
|
|
|
|
class Agent:
|
|
def __init__(self, aid, spec, brief=""):
|
|
self.id = aid
|
|
self.spec = _normalize(spec)
|
|
self.brief = brief
|
|
self.train_fit = self.valid_fit = -1e9
|
|
self.metrics = self.vmetrics = {}
|
|
self.alive = True
|
|
|
|
@property
|
|
def series(self):
|
|
return self.spec["series"]
|
|
|
|
def score(self, datasets, splits_map):
|
|
d = datasets[self.series]; tr, va, _ = splits_map[self.series]
|
|
self.metrics = evaluate(d, _flat(self.spec), tr)
|
|
self.vmetrics = evaluate(d, _flat(self.spec), va)
|
|
self.train_fit = self.metrics["fitness"]; self.valid_fit = self.vmetrics["fitness"]
|
|
|
|
|
|
def run_tournament(specs, briefs=None, seed=2026, epochs=90, cull_every=10, cull_n=10,
|
|
out_name="options_result.json", log=print):
|
|
rng = random.Random(seed)
|
|
datasets = {"A": load_opt("BTC"), "B": load_opt("ETH")}
|
|
splits_map = {k: splits3(datasets[k]) for k in datasets}
|
|
briefs = briefs or [""] * len(specs)
|
|
agents = [Agent(i, s, briefs[i] if i < len(briefs) else "") for i, s in enumerate(specs)]
|
|
for a in agents:
|
|
a.score(datasets, splits_map)
|
|
alive = lambda: [a for a in agents if a.alive]
|
|
log(f"[epoch 0] {len(alive())} agenti | best VALID {max(a.valid_fit for a in agents):.1f}")
|
|
for ep in range(1, epochs + 1):
|
|
for a in alive():
|
|
cand = _normalize(mutate(a.spec, rng))
|
|
d = datasets[cand["series"]]; tr, va, _ = splits_map[cand["series"]]
|
|
m = evaluate(d, _flat(cand), tr)
|
|
if m["fitness"] > a.train_fit:
|
|
a.spec, a.metrics, a.train_fit = cand, m, m["fitness"]
|
|
a.vmetrics = evaluate(d, _flat(cand), va); a.valid_fit = a.vmetrics["fitness"]
|
|
if ep % cull_every == 0:
|
|
av = sorted(alive(), key=lambda a: a.valid_fit)
|
|
k = cull_n if len(av) - cull_n >= 10 else max(0, len(av) - 10)
|
|
for a in av[:k]:
|
|
a.alive = False
|
|
log(f"[epoch {ep:2d}] cull {k:2d} -> {len(alive()):3d} | best VALID "
|
|
f"{max(a.valid_fit for a in alive()):.1f} | worst {min(a.valid_fit for a in alive()):.1f}")
|
|
survivors = sorted(alive(), key=lambda a: a.valid_fit, reverse=True)
|
|
results = []
|
|
for rank, a in enumerate(survivors, 1):
|
|
d = datasets[a.series]; _, _, te = splits_map[a.series]
|
|
results.append({"rank": rank, "agent": a.id, "spec": a.spec, "brief": a.brief,
|
|
"series": a.series, "train": a.metrics, "valid": a.vmetrics,
|
|
"test": evaluate(d, _flat(a.spec), te), "full": evaluate(d, _flat(a.spec), None)})
|
|
payload = {"n_agents": len(specs), "survivors": len(survivors), "results": results,
|
|
"reveal": {"A": "BTC", "B": "ETH"}, "game": "options"}
|
|
(OUT / out_name).write_text(json.dumps(payload, indent=2))
|
|
return payload
|
|
|
|
|
|
def leaderboard(payload, top=10, log=print):
|
|
log("\n========= CLASSIFICA FINALE OPZIONI (top %d) =========" % top)
|
|
log(f"{'#':>2} {'ag':>4} {'ser':>3} {'struttura':>14} {'otm':>5} {'dte':>4} "
|
|
f"{'TEpnl%':>8} {'TEwin':>5} {'TEtpm':>6} {'TEsh':>6}")
|
|
for r in payload["results"][:top]:
|
|
sp = r["spec"]; te = r["test"]; p = sp["params"]
|
|
log(f"{r['rank']:>2} {r['agent']:>4} {sp['series']:>3} {sp['structure']:>14} "
|
|
f"{p['otm']:>5.2f} {p['dte']:>4} {te['pnl_pct']:>8.0f} {te['win_rate']*100:>4.0f}% "
|
|
f"{te['tpm']:>6.0f} {te['sharpe']:>6.1f}")
|
|
|
|
|
|
def load_specs(specs_dir, n=100):
|
|
rng = random.Random(7); specs, briefs = [], []
|
|
for i in range(n):
|
|
f = Path(specs_dir) / f"agent_{i}.json"
|
|
spec = None
|
|
if f.exists():
|
|
try:
|
|
raw = json.loads(f.read_text())
|
|
params = {k: raw.get(k, raw.get("params", {}).get(k)) for k in PSPACE}
|
|
spec = _normalize({"structure": raw.get("structure"),
|
|
"series": {"X": "A", "Y": "B"}.get(raw.get("series"), raw.get("series")),
|
|
"params": params})
|
|
briefs.append(str(raw.get("hypothesis", ""))[:300])
|
|
except Exception:
|
|
spec = None
|
|
if spec is None:
|
|
spec = random_spec(rng); briefs.append("(spec mancante -> random)")
|
|
specs.append(spec)
|
|
return specs, briefs
|
|
|
|
|
|
def main():
|
|
if "--from-specs" in sys.argv:
|
|
sd = os.environ.get("GAME_SPECS_DIR", "data/games/specs_opt")
|
|
on = os.environ.get("GAME_OUT", "options_result.json")
|
|
specs, briefs = load_specs(sd)
|
|
n_real = sum(1 for b in briefs if "mancante" not in b)
|
|
print(f"caricati {n_real}/100 spec da agenti reali")
|
|
payload = run_tournament(specs, briefs=briefs, out_name=on)
|
|
else:
|
|
rng = random.Random(42)
|
|
payload = run_tournament([random_spec(rng) for _ in range(100)], seed=42)
|
|
leaderboard(payload)
|
|
rev = payload["reveal"]; w = payload["results"][0]
|
|
print(f"\n>>> RIVELAZIONE: A={rev['A']}, B={rev['B']}. Gli agenti non lo sapevano. <<<")
|
|
print(f"VINCITORE: #{w['agent']} {w['series']} {w['spec']['structure']} "
|
|
f"otm{w['spec']['params']['otm']} dte{w['spec']['params']['dte']}")
|
|
print(f" ipotesi: {w['brief']}")
|
|
print(f" TEST: PnL {w['test']['pnl_pct']:.0f}% | win {w['test']['win_rate']*100:.0f}% | "
|
|
f"{w['test']['tpm']:.0f} tr/mese | Sharpe {w['test']['sharpe']:.1f}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|