research(premium): ondata premi cross-venue auto-calcolati — CBPREM morto, KIMCHI d30 ucciso dallo scettico (spike+lag)
CBPREM (Coinbase vs feed cert, 2015->): HEDGE, hold -0.46. KIMCHI (Upbit/ECB, 2017->, ancora 00:00 UTC verificata): EARNS_SLOT=True al marginal scorer (ADDS, robust_oos, uplift + ogni anno) ma DSR 0.891 -> scettico obbligatorio: niente plateau (d15/30/45/60 = 0.44/1.07/0.51/0.24) e lag +1g azzera (hold 0.74->0.14) = parameter luck. Lezione codificata: EARNS_SLOT con DSR<0.95 -> sempre plateau+lag skeptic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""r0724_premium_wave — premi cross-venue AUTO-CALCOLATI: Coinbase premium + Kimchi (2026-07-24).
|
||||
|
||||
Seconda parte dell'ondata "trova altre strategie" (dopo r0724_onchain_wave): la ricerca
|
||||
web (agente on-chain/dati) indica i premi regionali come UNICA famiglia flow con dati
|
||||
100% auto-calcolabili da candele raw -> zero rischio-revisione/vintage del vendor
|
||||
(coerente con la dottrina dati del progetto). Evidenza accademica: kimchi = anomalia
|
||||
documentata (violazione persistente della legge del prezzo unico, capital controls);
|
||||
lead-lag ASIMMETRICO e TEMPO-VARIANTE (MDPI 2026) -> nessuna regola pubblicata onesta,
|
||||
qui si meccanizza da zero.
|
||||
|
||||
SEGNALI (mai il prezzo): per asset a e giorno d
|
||||
CBPREM_a(d) = close Coinbase USD (00:00 UTC) / close feed certificato - 1
|
||||
KIMCHI_a(d) = close Upbit KRW (00:00 UTC, ancora 09:00 KST) / (USDKRW_ECB x close cert) - 1
|
||||
Le candele Upbit daily sono ancorate a mezzanotte UTC (=09:00 KST) -> stesso istante di
|
||||
chiusura del feed certificato, nessun premio finto da mismatch orario. FX = fixing ECB
|
||||
del giorno (ffill weekend; il KRW si muove ~nulla vs la vol crypto — caveat dichiarato).
|
||||
NB: il feed certificato Deribit e' esso stesso un indice multi-exchange che include
|
||||
Coinbase -> il CBPREM misurato e' SMORZATO (caveat strutturale).
|
||||
|
||||
Griglie (piccole, tutte contate nel deflated-Sharpe): z-score rolling 180g del livello
|
||||
(follow / contrarian) e segno della variazione 30g (follow), LF e LS -> 6 celle/famiglia.
|
||||
Gate: study_family_honest (cella in-sample, DSR, marginal scorer vs TP01).
|
||||
|
||||
Dati: data/external/premium/ (fetch: scratchpad/fetch_premium.py — Coinbase Exchange
|
||||
public candles, Upbit public candles, frankfurter.app ECB; tutti tokenless).
|
||||
|
||||
Uso: `uv run python scripts/research/r0724_premium_wave.py`
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
|
||||
|
||||
import altlib # noqa: E402
|
||||
from altlib import study_family_honest, fmt_marginal, get # noqa: E402
|
||||
|
||||
EXT = ROOT / "data" / "external" / "premium"
|
||||
|
||||
|
||||
def _cert_close_by_day(asset: str) -> pd.Series:
|
||||
df = get(asset, "1d")
|
||||
days = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True)).floor("D")
|
||||
return pd.Series(df["close"].values.astype(float), index=days)
|
||||
|
||||
|
||||
def _cb_close(asset: str) -> pd.Series:
|
||||
d = pd.read_csv(EXT / f"cb_{asset.lower()}.csv")
|
||||
idx = pd.DatetimeIndex(pd.to_datetime(d["ts"], unit="s", utc=True)).floor("D")
|
||||
return pd.Series(d["close"].values.astype(float), index=idx)
|
||||
|
||||
|
||||
def _upbit_close(asset: str) -> pd.Series:
|
||||
d = pd.read_csv(EXT / f"upbit_{asset.lower()}.csv")
|
||||
idx = pd.DatetimeIndex(pd.to_datetime(d["utc"], utc=True)).floor("D")
|
||||
return pd.Series(d["close_krw"].values.astype(float), index=idx)
|
||||
|
||||
|
||||
def _fx() -> pd.Series:
|
||||
d = pd.read_csv(EXT / "usdkrw.csv")
|
||||
idx = pd.DatetimeIndex(pd.to_datetime(d["date"], utc=True))
|
||||
s = pd.Series(d["usdkrw"].values.astype(float), index=idx)
|
||||
full = pd.date_range(s.index[0], s.index[-1] + pd.Timedelta(days=3), freq="D", tz="UTC")
|
||||
return s.reindex(full).ffill()
|
||||
|
||||
|
||||
def _premia() -> dict:
|
||||
fx = _fx()
|
||||
out = {}
|
||||
for a in ("BTC", "ETH"):
|
||||
cert = _cert_close_by_day(a)
|
||||
cb = _cb_close(a).reindex(cert.index)
|
||||
up = _upbit_close(a).reindex(cert.index)
|
||||
out[a] = pd.DataFrame({
|
||||
"cbprem": cb / cert - 1.0,
|
||||
"kimchi": up / (fx.reindex(cert.index) * cert) - 1.0,
|
||||
}, index=cert.index)
|
||||
return out
|
||||
|
||||
|
||||
_PREM = _premia()
|
||||
|
||||
|
||||
def prem_factory_maker(col: str):
|
||||
def factory(tf: str, sig: str = "z180", mode: str = "folLF"):
|
||||
def fn(df, asset):
|
||||
p = _PREM[asset][col]
|
||||
if sig == "z180":
|
||||
mu = p.rolling(180, min_periods=90).mean()
|
||||
sd = p.rolling(180, min_periods=90).std()
|
||||
z = (p - mu) / sd
|
||||
else: # d30: variazione 30g del premio
|
||||
z = p - p.shift(30)
|
||||
s = np.sign(z) if mode.startswith("fol") else -np.sign(z)
|
||||
pos = s if mode.endswith("LS") else (s > 0).astype(float)
|
||||
days = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True)).floor("D")
|
||||
return np.nan_to_num(pos.reindex(days).values.astype(float))
|
||||
return fn
|
||||
return factory
|
||||
|
||||
|
||||
GRID = [
|
||||
dict(sig="z180", mode="folLF"), dict(sig="z180", mode="conLF"),
|
||||
dict(sig="z180", mode="folLS"), dict(sig="z180", mode="conLS"),
|
||||
dict(sig="d30", mode="folLF"), dict(sig="d30", mode="folLS"),
|
||||
]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(" ONDATA PREMI CROSS-VENUE — CBPREM + KIMCHI via study_family_honest")
|
||||
for a in ("BTC", "ETH"):
|
||||
P = _PREM[a].dropna()
|
||||
print(f" {a}: {len(P)} giorni | cbprem medio {P['cbprem'].mean()*1e4:+.1f}bps "
|
||||
f"(p1/p99 {P['cbprem'].quantile(0.01)*1e4:+.0f}/{P['cbprem'].quantile(0.99)*1e4:+.0f}) | "
|
||||
f"kimchi medio {P['kimchi'].mean()*1e4:+.1f}bps "
|
||||
f"(p1/p99 {P['kimchi'].quantile(0.01)*1e4:+.0f}/{P['kimchi'].quantile(0.99)*1e4:+.0f})")
|
||||
print("=" * 100)
|
||||
for fam, col in (("CBPREM-coinbase", "cbprem"), ("KIMCHI-korea", "kimchi")):
|
||||
print("-" * 100)
|
||||
rep = study_family_honest(fam, prem_factory_maker(col), GRID, tfs=("1d",))
|
||||
if rep.get("chosen") is None:
|
||||
print(f"=== {fam}: nessuna cella valida in-sample")
|
||||
continue
|
||||
ch = rep["chosen"]
|
||||
print(f"=== {fam}: cella IS {ch['params']} (IS Sh {ch['insample_sharpe']}, "
|
||||
f"full {ch['full_sharpe']}) su {rep['n_cells']} celle")
|
||||
print(f" deflated-Sharpe {rep['deflated_sharpe']} (null-max {rep['expected_null_max']})"
|
||||
f" dsr_pass={rep['dsr_pass']}")
|
||||
print(fmt_marginal(rep["marginal"]))
|
||||
print(f" >>> EARNS_SLOT_HONEST = {rep['earns_slot_honest']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user