feat(pairs): attiva ETH/BTC 15m flat-skip in PORT06 (BLEND, mezza size)
Origine: gioco "Blind Traders" (100 agenti ciechi su BTC/ETH anonimizzati) -> vincitore = spread ETH/BTC reversion a 15m. Testato sul serio col gate PORT06: non duplicato (corr 1h vs 15m = 0.37), robusto (16/16 celle Sharpe>1), edge NON artefatto delle candele flat ETH 15m (filtrandole resta l'83% dello Sharpe). Percorso live costruito e validato: - pairs_research.pairs_sim_flat: engine generalizzato con exit LIVE-REALIZABLE (arma exit_ready, esce alla 1a barra pulita); regression-lock a pairs_sim. - PairsWorker: flat_skip + exit_ready + rilevamento flat da OHLC (1h byte-exact). - runner: fetch diretto dei timeframe sub-orari + override position_size per-sleeve. - validate_worker_pairs: replay worker == backtest a 15m (8452 vs 8453 trade). - _defs/build_everything: sleeve PR_ETHBTC_15M (mezza size, pos 0.10) -> PORT06 FULL 6.43->7.20, OOS 8.58->9.66, DD giu'. Rischio bilanciato col 1h. - smoke live: Cerbero serve candele 15m fresche; worker ticca. Diari docs/diary/2026-06-09-*. Caveat slippage: mezza size = blend-tilt prudente. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
agent_brief — genera il "digest" ANONIMO che ogni agente cieco riceve.
|
||||
|
||||
L'agente non sa che sono BTC/ETH ne' che e' crypto: vede solo due serie X e Y
|
||||
(rinominate dal motore A/B), una finestra normalizzata (base 100) e statistiche
|
||||
aggregate. Da queste deve proporre una regola che "anticipi" i movimenti.
|
||||
|
||||
Genera anche il MENU dei blocchi (famiglie + range parametri) che l'agente puo'
|
||||
comporre, in modo che l'output sia una spec backtestabile.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import numpy as np
|
||||
|
||||
from scripts.games.engine import load_anon
|
||||
|
||||
|
||||
def _stats(close, high, low):
|
||||
r = np.diff(np.log(close))
|
||||
r = r[np.isfinite(r)]
|
||||
out = {
|
||||
"n_bars": int(len(close)),
|
||||
"ret_vol_pct": round(float(np.std(r) * 100), 4),
|
||||
"ret_autocorr_lag1": round(float(np.corrcoef(r[:-1], r[1:])[0, 1]), 4),
|
||||
"ret_autocorr_lag5": round(float(np.corrcoef(r[:-5], r[5:])[0, 1]), 4),
|
||||
"pct_up_bars": round(float(np.mean(r > 0) * 100), 2),
|
||||
"skew": round(float(((r - r.mean()) ** 3).mean() / (r.std() ** 3 + 1e-12)), 3),
|
||||
"kurtosis": round(float(((r - r.mean()) ** 4).mean() / (r.std() ** 4 + 1e-12)), 2),
|
||||
}
|
||||
# tendenza a rientrare dopo grandi mosse (|z|>2): segno del rendimento successivo
|
||||
z = (r - r.mean()) / (r.std() + 1e-12)
|
||||
big = np.where(np.abs(z[:-1]) > 2)[0]
|
||||
if len(big) > 20:
|
||||
nxt = r[big + 1]
|
||||
same = np.sign(r[big]) == np.sign(nxt)
|
||||
out["after_big_move_continues_pct"] = round(float(np.mean(same) * 100), 1)
|
||||
return out
|
||||
|
||||
|
||||
def make_digest(tf: str, window: int = 60, seed: int = 0):
|
||||
data = load_anon(tf)
|
||||
n = data["n"]
|
||||
# finestra recente normalizzata (base 100) per "vedere" la forma
|
||||
s = max(0, n - window)
|
||||
dig = {"timeframe_id": {"1h": "T1", "15m": "T2", "5m": "T3"}.get(tf, "T?"),
|
||||
"n_bars_total": n, "series": {}}
|
||||
for name in ("A", "B"):
|
||||
o = data[name]
|
||||
c = o["close"]
|
||||
norm = (c[s:] / c[s] * 100.0)
|
||||
dig["series"][{"A": "X", "B": "Y"}[name]] = {
|
||||
"stats": _stats(c, o["high"], o["low"]),
|
||||
"recent_window_norm": [round(float(v), 2) for v in norm],
|
||||
}
|
||||
# relazione fra le due serie
|
||||
ra = np.diff(np.log(data["A"]["close"]))
|
||||
rb = np.diff(np.log(data["B"]["close"]))
|
||||
m = min(len(ra), len(rb))
|
||||
dig["XY_return_correlation"] = round(float(np.corrcoef(ra[:m], rb[:m])[0, 1]), 4)
|
||||
lr = np.log(data["A"]["close"][:m + 1] / data["B"]["close"][:m + 1])
|
||||
dig["XY_logratio_ret_autocorr"] = round(
|
||||
float(np.corrcoef(np.diff(lr)[:-1], np.diff(lr)[1:])[0, 1]), 4)
|
||||
return dig
|
||||
|
||||
|
||||
MENU = {
|
||||
"obiettivo": ("Proponi UNA regola che anticipi i movimenti futuri per un PnL "
|
||||
"netto positivo dopo costi (0.10% andata+ritorno per trade). "
|
||||
"Servono >=10 operazioni al mese. Non sai cosa siano X e Y."),
|
||||
"famiglie": {
|
||||
"zscore": "fade/segui lo z-score del prezzo su 'lookback' barre (entry_thr in sigma)",
|
||||
"breakout": "rottura del canale max/min su 'lookback' barre (reversion=fade la rottura)",
|
||||
"ma_cross": "incrocio EMA veloce(lookback)/lenta(lookback*slow_mult)",
|
||||
"rsi": "RSI(lookback); entry_thr scala le bande attorno a 50",
|
||||
"momentum": "rendimento su 'lookback' barre vs soglia entry_thr (%)",
|
||||
"pairs": "market-neutral sullo z del log-rapporto X/Y (long una/short l'altra)",
|
||||
},
|
||||
"direzione": ["reversion (vai contro la mossa)", "trend (segui la mossa)"],
|
||||
"serie": ["X", "Y (solo per single-family)", "pairs usa entrambe"],
|
||||
"exit": "tp_atr / sl_atr (in unita' ATR), max_bars (durata massima)",
|
||||
"range": {
|
||||
"lookback": "5-120", "entry_thr": "1.0-3.5", "tp_atr": "0.5-4.0",
|
||||
"sl_atr": "1.0-5.0", "max_bars": "6-120", "slow_mult": "2-6",
|
||||
"exit_thr (pairs)": "0.2-1.0",
|
||||
},
|
||||
"output_schema": {
|
||||
"family": "una di [zscore,breakout,ma_cross,rsi,momentum,pairs]",
|
||||
"series": "X|Y|AB(pairs)", "direction": "reversion|trend",
|
||||
"params": "dict coi parametri scelti", "hypothesis": "1-2 frasi: cosa hai notato",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
tf = sys.argv[1] if len(sys.argv) > 1 else "1h"
|
||||
print(json.dumps(make_digest(tf), indent=2)[:2000])
|
||||
Reference in New Issue
Block a user