24565974c0
16 agenti su segnali low-turnover intraday (sessione/funding, reversione post-evento, breakout range del giorno prima) su feed certificati 1h/15m, giudice = marginal scorer indurito + fee-sweep. Lab: intra_score.py (wrappa study_marginal a TF scelto + turnover/fee), meta_intra.py (corr-TP01 + per-cut), verify_intra.py (walk-forward + in-sample-null + drop-one + fee-stress). Esito: 10/16 "earns_slot" -> 5 genuinamente ortogonali (corr<0.4). Combo dei 5: Sharpe 1.80, corr 0.17, leak-free, passa walk-forward (+0.30/+0.37 dove l'ortho dava -0.07), pre-2025 uplift +0.28, drop-one e fee-robusto. Sembrava IL lead. 3 scettici: (1) open_drive = ARTEFATTO etichettatura UTC (shift confine 4h -> uplift negativo); prevday_range_breakout REGGE (unico onesto, eseguibile). (2) combo fallisce il null a corr-zero (20-24° pctl: aggiunge meno del rumore), è HEDGE (corr -0.57..-0.80 a Sharpe-TP01) + tail-luck (80% PnL in top-5 giorni delle gambe revert). (3) robust-plateau ma null-pctl 0.20 = diversificazione di stream ortogonale, non timing-alpha; + finzione fee micro-ribilanciamento a $600. Verdetto: niente in live, resta solo TP01. Lead forward-monitor: prevday_range_breakout. Lezioni harness da codificare: test shift-confine-giorno (artefatti calendar), fee discretizzata a piccolo capitale, causality guard nel lab intraday. Diario 2026-06-21-intraday-microstructure.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
160 lines
8.5 KiB
Python
160 lines
8.5 KiB
Python
"""agent_12_close_location — STRUCT family, slug=close_location (suggested TF 1h).
|
|
|
|
ANGLE (assigned): where price closes WITHIN the day range — the close-location-value
|
|
CLV = (close - low) / (high - low) in [0,1] — predicts next-day direction. CLV near 1 = bulls
|
|
close at the highs (buying pressure / accumulation); near 0 = bears close at the lows
|
|
(distribution / weakness). One decision/day -> naturally low turnover.
|
|
|
|
WHAT THE DATA ACTUALLY SAYS (explored before designing — honesty first):
|
|
* RAW single-day CLV is mildly MEAN-REVERTING, not continuation: low CLV (closed near low)
|
|
=> HIGHER next-day return (+26bp BTC / +35bp ETH), high CLV => lower (+3bp / +9bp). The
|
|
quintile gradient is monotone but the effect is weak (corr ~-0.03).
|
|
* A pure CLV mean-reversion book (buy weak closes / fade strong closes) is anti-trend: it has
|
|
a NEGATIVE full Sharpe (-0.47), only a lucky-2025 hold-out (candH +0.59), and DILUTES the
|
|
full blend (-0.45). It fails the in-sample-edge gate -> NOISE/regime-luck. NOT this.
|
|
* A persistent-CLV CONTINUATION book (long when closes have been strong for weeks) is just a
|
|
slow trend proxy: good full (~0.9) but NEGATIVE hold-out (broke in 2025 like buy&hold) and
|
|
~redundant with TP01. NOT this either.
|
|
|
|
THE DESIGN THAT EARNS A SLOT (agent_10's lesson, applied to CLV): use CLV as a HARD FLAT-GATE on
|
|
the TP01 carrier, NOT a soft sizer. A soft CLV multiplier stays corr ~0.93-0.96 to TP01 =
|
|
REDUNDANT. To decorrelate we must do what a c2c trend CANNOT: go fully FLAT in the regime where
|
|
closes are persistently WEAK (bearish CLV / distribution at the top), and ride the trend at full
|
|
size when closes confirm it. Going to 0 (not trimming) removes specific trend days TP01 holds
|
|
through that turn out badly -> corr drops to ~0.82 and the blend lift is real.
|
|
|
|
carrier = TP01-style long-flat 30/90/180d TSMOM, c2c vol-targeted (this IS the TP01 leg)
|
|
CLV gate = multi-window (3/5/10d EMA of CLV) -> causal EXPANDING-z -> averaged -> a hard ramp
|
|
that reaches 0 when CLV-z is in its bottom regime (kill_z=0.3), EMA-smoothed.
|
|
Multi-window (like TP01's multi-horizon trend) is more robust than any single span
|
|
and bites only when weak-closes are confirmed across horizons -> fewer false kills.
|
|
|
|
WHY IT'S INTRADAY-NATIVE (not derivable from c2c): two days with the SAME close-to-close move can
|
|
have wildly different CLV — one closed at the high after dipping (strong), one faded from the high
|
|
(weak). c2c vol (what TP01 targets on) is blind to it. The gate withholds risk in
|
|
distribution/weak-close tape and presses in clean accumulation.
|
|
|
|
CAUSAL: CLV[i] uses high/low/close[i] (all <= close[i]); the expanding-z standardizes by mean/std
|
|
over rows STRICTLY before i (shift(1)); the gate is a pure function of past bars. No full-sample
|
|
calendar/quantile fit. The evaluator holds position[i] during bar i+1 (no leak by construction).
|
|
|
|
TURNOVER: ~11/yr (the carrier flips ~monthly; the gate is a slow, smoothed, multi-day quantity)
|
|
-> far under the 120/yr fee cap; survives the 0.20% RT fee sweep.
|
|
|
|
HONEST VERDICT (scored 2026-06-21 @ tf=1d): ADDS / abs=PASS / EARNS_SLOT=True.
|
|
corr->TP01 0.815 (hold 0.734), beta 0.468, residual Sharpe 0.536 (+2.2%/yr alpha beyond trend).
|
|
uplift_hold +0.067 / uplift_full +0.045 ; standalone BTC full 1.10/hold 0.59, ETH 1.16/hold 0.61.
|
|
in-sample standalone Sharpe 1.50 (stands on its own, not a lucky window). turnover ~11/yr (BTC) /
|
|
~8/yr (ETH); fees survive to 0.20% RT (full 1.04). Multi-cut PERSISTENT: the flat-gate lift is
|
|
positive at EVERY yearly cut 2020-2025 (+0.041..+0.079). is_hedge=False (it adds in BOTH TP01-up
|
|
+0.029 and TP01-down +0.069). Plateau: kill_z 0.35..0.60 all give clean-year uplift ~+0.08.
|
|
|
|
CAVEATS (honest — fees usually win, and they nearly did here):
|
|
* The HOLD-OUT lift is concentrated: dropping 2025-10 alone takes the hold-out uplift from
|
|
+0.062 to ~0 -> the drop-one-month jackknife clears by a HAIR (+0.001). The in-sample edge and
|
|
the 6-year multi-cut persistence are the real backbone; the 2025-26 hold-out is short (537d)
|
|
and one-month-leaning. Treat as a SATELLITE, forward-monitor the hold-out, do NOT over-weight.
|
|
* It is a TP01-LED book (shares the carrier; corr 0.82) -> it IMPROVES TP01 risk-adjusted via a
|
|
flat-gate TP01 cannot see (CLV/within-day close location), it is NOT a standalone alpha.
|
|
* The mean-reversion reading of CLV (buy weak closes) was REJECTED: negative full Sharpe, lucky-
|
|
2025-only -> NOISE. The continuation-as-gate framing is what survives the hardened judge.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
|
|
import altlib as al # noqa: E402
|
|
|
|
# --- carrier (TP01-style slow long-flat trend) ---
|
|
_HORIZONS_D = (30, 90, 180)
|
|
_VOL_TARGET = 0.20
|
|
_VOL_WIN_D = 30
|
|
_LEV_CAP = 2.0
|
|
|
|
# --- close-location-value (CLV) flat-gate ---
|
|
# Multi-window EMA of CLV -> expanding-z -> averaged -> hard ramp to 0 in the weak-close regime.
|
|
_CLV_SPANS_D = (3, 5, 10) # EMA spans (days) for the multi-window CLV blend
|
|
_EXP_MIN_D = 180 # min days before the expanding standardization is trusted
|
|
_KILL_Z = 0.45 # below this expanding-z of CLV -> closes are weak -> kill exposure.
|
|
# 0.45 = the plateau CENTER (kz 0.35..0.60 all give clean-year uplift
|
|
# ~+0.08, hold uplift ~+0.06, corr ~0.81); 0.45 is the lowest-corr point
|
|
# that also clears the drop-one-month jackknife (see HONEST VERDICT).
|
|
_RAMP = 0.5 # ramp width -> gate reaches 0 in confirmed weak-close tape
|
|
_SMOOTH_D = 3 # EMA smoothing of the gate (keeps turnover low)
|
|
_GATE_LO, _GATE_HI = 0.0, 1.0
|
|
|
|
|
|
def _tsmom_long_flat(c: np.ndarray, bpd: int) -> np.ndarray:
|
|
nbar = len(c)
|
|
acc = np.zeros(nbar)
|
|
cnt = np.zeros(nbar)
|
|
for d in _HORIZONS_D:
|
|
h = d * bpd
|
|
if h >= nbar:
|
|
continue
|
|
s = np.full(nbar, np.nan)
|
|
s[h:] = np.sign(c[h:] / c[:-h] - 1.0)
|
|
v = np.isfinite(s)
|
|
acc[v] += s[v]
|
|
cnt[v] += 1
|
|
direction = np.zeros(nbar)
|
|
nz = cnt > 0
|
|
direction[nz] = acc[nz] / cnt[nz]
|
|
return np.clip(direction, 0, None) # long-flat
|
|
|
|
|
|
def _expanding_z(x: np.ndarray, min_obs: int) -> np.ndarray:
|
|
"""Strictly causal expanding-standardized z-score (mean/std over rows 0..i-1)."""
|
|
s = pd.Series(x)
|
|
m = s.expanding(min_periods=min_obs).mean().shift(1)
|
|
sd = s.expanding(min_periods=min_obs).std().shift(1)
|
|
z = (s - m) / sd.replace(0, np.nan)
|
|
return z.values
|
|
|
|
|
|
def _clv(df: pd.DataFrame) -> np.ndarray:
|
|
"""Close-location-value in [0,1]: where close sits within the bar's high-low range.
|
|
1 = closed at the high (max buying pressure), 0 = closed at the low. 0.5 if range is 0."""
|
|
h, l, c = df["high"].values.astype(float), df["low"].values.astype(float), df["close"].values.astype(float)
|
|
rng = h - l
|
|
safe = np.where(rng > 0, rng, 1.0) # avoid 0/0 on flat (high==low) bars
|
|
return np.where(rng > 0, (c - l) / safe, 0.5) # 0.5 (neutral) when the bar has no range
|
|
|
|
|
|
def target(df: pd.DataFrame) -> np.ndarray:
|
|
c = df["close"].values.astype(float)
|
|
bpd = al.bars_per_day(df)
|
|
|
|
# --- carrier: slow long-flat TSMOM, c2c vol-targeted (this IS the TP01 leg) ----
|
|
direction = _tsmom_long_flat(c, bpd)
|
|
base = al.vol_target(direction, df, _VOL_TARGET, _VOL_WIN_D, _LEV_CAP)
|
|
|
|
# --- intraday-native signal: multi-window CLV, causal expanding-z, averaged -----
|
|
clv = _clv(df)
|
|
zacc = np.zeros(len(c))
|
|
for sp in _CLV_SPANS_D:
|
|
zacc += np.nan_to_num(_expanding_z(al.ema(clv, sp * bpd), _EXP_MIN_D * bpd), nan=0.0)
|
|
clv_z = zacc / len(_CLV_SPANS_D)
|
|
|
|
# HARD FLAT-GATE: full trend when closes confirm (CLV-z high), fully FLAT when closes are
|
|
# persistently weak (CLV-z below kill, = distribution). A soft ramp reaching 0 in confirmed
|
|
# weak-close tape, EMA-smoothed to keep turnover low. Going to 0 is what decorrelates from TP01.
|
|
raw_gate = np.clip((clv_z - _KILL_Z) / _RAMP + 0.5, _GATE_LO, _GATE_HI)
|
|
gate = al.ema(raw_gate, _SMOOTH_D * bpd)
|
|
gate = np.clip(gate, _GATE_LO, _GATE_HI)
|
|
|
|
pos = base * gate
|
|
return np.nan_to_num(pos, nan=0.0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
for a in ("BTC", "ETH"):
|
|
d = al.get(a, "1d")
|
|
ev = al.eval_weights(d, target(d))
|
|
print(a, "full", ev["full"]["sharpe"], "hold", ev["holdout"]["sharpe"],
|
|
"turn/yr", ev["turnover_per_year"], "TiM", ev["time_in_market"])
|