1afb1014c9
Flotta di 52 subagenti "esperti di segnali" su storico BTC/ETH ANONIMIZZATO (Series A/B rebased a 100, calendario sintetico, split 70/30) — non sanno cosa siano. Ognuno scrive un signal(df)->position causale (script o ML), tunato solo sul train. Orchestratore valuta su PnL e maxDD nel test held-out. Harness cieco leak-free (riusabile): - make_blind.py: export anonimo + overlay; blindlib.py: evaluator con shift della posizione + GUARDIA DI CAUSALITA' online (squalifica ogni look-ahead, ML incluso); blind_eval.py CLI; score_all.py giudice OOS; verify_top.py (corr-al-trend, fee-stress, jackknife). - 52/52 passano la guardia (zero leak su tutta la flotta). Esito OOS (benchmark buy&hold: -7% PnL, 68% DD): - top = macd (+21%, DD 11%, Sh 0.84), accel, vol_of_vol, regime_switch, rf, obv — tutti trend/vol-regime. Sharpe OOS ~0.84 decade dal train ~1.4. Mean-rev e ML in fondo. - 3 scettici indipendenti: REFUTED. regime-luck (top-5 bar = 67-102% del PnL); trend-redundancy (HAC alpha t=+0.9..+1.5, nessuno >1.96 — TSMOM travestito); overfit (accel/vov knife-edge). Verdetto: ri-conferma CIECA e indipendente del soffitto direzionale ~1.3. macd = classe-TP01, forward-monitor non deploy. Diario 2026-06-21-blind-signal-fleet.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
103 lines
4.2 KiB
Python
103 lines
4.2 KiB
Python
"""make_blind — export the CERTIFIED BTC/ETH 1d feed as ANONYMIZED, OVERLAID curves.
|
|
|
|
The blind-signal fleet (~50 "signal expert" agents) must NOT know the series are
|
|
BTC/ETH crypto — otherwise they pattern-match the 2020 covid crash / 2022 bear /
|
|
2024 halving from memory instead of finding a real, transferable timing edge.
|
|
|
|
So we strip every tell:
|
|
* relabel BTC->"A", ETH->"B" (no ticker anywhere)
|
|
* REBASE each series to 100 at its first bar (multiply all OHLC by 100/open[0]) ->
|
|
constant rescale, returns/backtest UNCHANGED, but the price LEVEL no longer says
|
|
"this is $60k bitcoin". Both curves now start at 100 = literally "curve sovrapposte".
|
|
* synthetic DAILY calendar starting 2001-01-01 (so 1 bar = 1 day for annualization,
|
|
but no 2020/2022 era to recognize).
|
|
* normalize volume to its own median (=1) -> shape kept, scale anonymized.
|
|
|
|
Split: first SPLIT_FRAC of bars = TRAIN (handed to the agents), the rest = TEST
|
|
(held out; only the orchestrator ever evaluates on it -> a true out-of-sample PnL/DD).
|
|
|
|
Outputs (data/blind/, gitignored-friendly):
|
|
blind_A_train.parquet blind_B_train.parquet <- agent-visible
|
|
blind_A_full.parquet blind_B_full.parquet <- orchestrator-only (full series, for
|
|
OOS eval with proper warmup)
|
|
blind_meta.json <- split index, lengths (NO mapping to BTC/ETH in plain sight)
|
|
overlay.png <- the two overlaid anonymized curves (for the human)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
|
|
import altlib as al # noqa: E402
|
|
|
|
OUT = Path("/opt/docker/PythagorasGoal/data/blind")
|
|
SPLIT_FRAC = 0.70
|
|
SYNTH_START = "2001-01-01"
|
|
# mapping kept OUT of the agent-visible meta; only here in source for our own audit.
|
|
_REAL = {"A": "BTC", "B": "ETH"}
|
|
|
|
|
|
def _anonymize(df: pd.DataFrame, n_bars: int) -> pd.DataFrame:
|
|
df = df.reset_index(drop=True).copy()
|
|
base = float(df["open"].iloc[0])
|
|
scale = 100.0 / base
|
|
out = pd.DataFrame()
|
|
synth = pd.date_range(SYNTH_START, periods=len(df), freq="1D", tz="UTC")
|
|
out["timestamp"] = (synth.view("int64") // 1_000_000).astype("int64")
|
|
for col in ("open", "high", "low", "close"):
|
|
out[col] = df[col].values.astype(float) * scale
|
|
vmed = float(np.nanmedian(df["volume"].values)) or 1.0
|
|
out["volume"] = df["volume"].values.astype(float) / vmed
|
|
out["datetime"] = synth
|
|
return out
|
|
|
|
|
|
def main() -> None:
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
meta = {"split_frac": SPLIT_FRAC, "series": {}}
|
|
curves = {}
|
|
for label, asset in _REAL.items():
|
|
raw = al.get(asset, "1d")
|
|
anon = _anonymize(raw, len(raw))
|
|
n = len(anon)
|
|
cut = int(n * SPLIT_FRAC)
|
|
anon.to_parquet(OUT / f"blind_{label}_full.parquet", index=False)
|
|
anon.iloc[:cut].reset_index(drop=True).to_parquet(
|
|
OUT / f"blind_{label}_train.parquet", index=False)
|
|
meta["series"][label] = {"n_bars": n, "train_bars": cut, "test_bars": n - cut}
|
|
curves[label] = anon["close"].values
|
|
print(f" Series {label}: {n} bars train={cut} test={n-cut} "
|
|
f"(rebased start=100, level now {anon['close'].iloc[-1]:.0f})")
|
|
|
|
(OUT / "blind_meta.json").write_text(json.dumps(meta, indent=2))
|
|
|
|
# overlay chart for the human (agents work on the numbers, not the png)
|
|
try:
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
fig, ax = plt.subplots(figsize=(12, 5))
|
|
for label, c in curves.items():
|
|
ax.plot(np.arange(len(c)), c, label=f"Series {label}", lw=0.8)
|
|
ax.axvline(int(min(len(c) for c in curves.values()) * SPLIT_FRAC),
|
|
ls="--", color="k", alpha=0.4, label="train/test cut")
|
|
ax.set_yscale("log")
|
|
ax.set_title("Anonymized overlaid curves (rebased to 100) — train | held-out test")
|
|
ax.legend()
|
|
fig.tight_layout()
|
|
fig.savefig(OUT / "overlay.png", dpi=110)
|
|
print(f" overlay.png written")
|
|
except Exception as e:
|
|
print(f" (chart skipped: {e})")
|
|
|
|
print(f"\n wrote -> {OUT}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|