research(alt): sweep 104 strategie alternative su Deribit (153 agenti) + marginal scorer
Ondata di ricerca onesta a largo spettro su BTC/ETH+DVOL certificati: 104 ipotesi distinte (11 famiglie), un agente-finder per ipotesi, verifica avversariale a 3 scettici sui promettenti, sintesi (153 agenti totali). Esito: NIENTE di nuovo regge -> conferma del soffitto strutturale ~1.3 BTC/ETH-direzionale; lo stack TP01+XS01+VRP01 resta imbattuto. - altlib.py: harness condiviso vettoriale leak-free (eval_weights/study_weights, fee-sweep, both-asset + hold-out 2025+). Riproduce i numeri canonici di TP01. - MARGINAL SCORER (study_marginal/marginal_vs_tp01): Sharpe INCREMENTALE vs baseline TP01 (corr, blend uplift OOS, alpha residua) + jackknife OOS (clean-year + drop-best-month). earns_slot = abs!=FAIL & ADDS & robust_oos. Smaschera gli overlay su TSMOM con PASS assoluti fasulli (CMB04, VOL11, ...) e il falso positivo KAMA (ADDS ma muore al jackknife). - runs/*.py (104) script riproducibili per ipotesi; wf_altstrat.js workflow. - Verdetto: 0 candidati deployabili; 2 LEAD fragili (VOL08, STA05_LS) da forward-monitor. - test_marginal_scorer.py blocca baseline + invarianti. Suite: 32 verde. Diario: docs/diary/2026-06-20-alt-strategies-100agent-sweep.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
"""SEA07 — Monday Effect (expanding Monday expectancy).
|
||||
|
||||
IDEA: On 1d bars, use the expanding-window mean Monday return as a directional signal.
|
||||
- Compute an expanding (causal) mean of Monday returns seen so far.
|
||||
- If the expanding Monday mean > 0 (continuation): go long (+1) on Mondays, flat otherwise.
|
||||
- If the expanding Monday mean < 0 (reversal): go short (-1) on Mondays, flat otherwise.
|
||||
- Also try "Friday signal": what happened last Friday may predict the Monday direction.
|
||||
We track expanding Friday return mean and use its sign to predict the following Monday.
|
||||
|
||||
Signal styles tested (4 configs, 1 TF = 1d, 2 assets = <=8 cells total):
|
||||
1. Monday continuation: long on Mondays when expanding E[Monday ret] > 0, else flat
|
||||
2. Monday always long: always long on Mondays regardless of prior expectancy (baseline)
|
||||
3. Friday-to-Monday: on Monday, go in the direction of last Friday's expanding mean
|
||||
4. Monday vol-adjusted: same as #1 but NOT vol-targeted (raw position, to isolate the signal)
|
||||
|
||||
All signals are on 1d only (as required).
|
||||
|
||||
Causal guarantee:
|
||||
- Expanding Monday mean at bar i uses only Monday returns j < i (causal).
|
||||
- Friday-to-Monday: expanding Friday mean uses only Friday returns j < i (causal).
|
||||
- lib shifts position by 1 bar automatically (decided at close[i], held during bar i+1).
|
||||
WAIT: Monday bar i means we hold on Monday. close[i] of a Monday is ALREADY the end of Monday.
|
||||
So to hold DURING Monday, we must decide at close[i-1] (Sunday or prior day).
|
||||
Implementation: set target[i] = 0 always; set target[i-1] = signal for Monday i.
|
||||
But altlib shifts target[i] -> held at bar i+1. So to be in position DURING bar i:
|
||||
we need target[i-1] != 0, which becomes pos[i] = target[i-1].
|
||||
Correct approach: for each Monday bar at index i, we set target[i-1] = signal.
|
||||
This means at close of Sunday (i-1), we enter; held during bar i (Monday).
|
||||
Since 1d bars, Sunday doesn't exist: previous bar is Friday at i-1.
|
||||
So: at close of Friday (i-1), we set the position to be held on Monday (i).
|
||||
This is the natural way: target[i-1] = signal, lib shifts to pos[i] = target[i-1].
|
||||
|
||||
Expanding stats use only data BEFORE the current Monday being evaluated:
|
||||
- When setting target[i-1] for Monday i: we have seen all Monday returns up to i-1 (none of
|
||||
which are Mondays in typical weeks; so effectively all Mondays before this one).
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
|
||||
import altlib as al
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def sea07_monday_continuation(df: pd.DataFrame, min_samples: int = 10,
|
||||
use_friday: bool = False,
|
||||
vol_tgt: bool = True) -> np.ndarray:
|
||||
"""
|
||||
Monday-effect signal on daily bars.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
min_samples : int
|
||||
Minimum Monday (or Friday) samples needed before trusting the expectancy.
|
||||
use_friday : bool
|
||||
If True, use the expanding mean of Friday returns to predict Monday direction.
|
||||
If False, use the expanding mean of Monday returns (continuation/reversal).
|
||||
vol_tgt : bool
|
||||
Whether to apply vol-targeting to the direction signal.
|
||||
"""
|
||||
dt = pd.to_datetime(df["datetime"])
|
||||
c = df["close"].values.astype(float)
|
||||
r = al.simple_returns(c)
|
||||
n = len(df)
|
||||
|
||||
# Day of week: 0=Monday, 1=Tuesday, ..., 4=Friday, 5=Saturday, 6=Sunday
|
||||
dow = dt.dt.dayofweek.values # 0=Mon, 4=Fri
|
||||
|
||||
# Expanding stats for Monday and Friday returns
|
||||
mon_sum = 0.0
|
||||
mon_cnt = 0
|
||||
fri_sum = 0.0
|
||||
fri_cnt = 0
|
||||
|
||||
# target[i]: position decided at close[i], held during bar i+1
|
||||
# To be in position DURING Monday bar i, we set target[i-1].
|
||||
# target is indexed by bar where decision is made.
|
||||
target = np.zeros(n, dtype=float)
|
||||
|
||||
for i in range(1, n):
|
||||
# Update stats with bar i-1 (the bar we just closed)
|
||||
prev_dow = dow[i - 1]
|
||||
prev_r = r[i - 1]
|
||||
|
||||
if prev_dow == 0: # previous bar was a Monday
|
||||
# We accumulate Monday return AFTER using it for the next decision
|
||||
# (this bar i is Tuesday or later; the Monday return r[i-1] is now known)
|
||||
pass # will update after computing signal for i
|
||||
|
||||
# Current bar i: what day is it?
|
||||
curr_dow = dow[i]
|
||||
|
||||
if curr_dow == 0:
|
||||
# Bar i is a Monday. We want to be in position during this bar.
|
||||
# Decision must be made at close[i-1] (Friday or whatever preceded it).
|
||||
# So we set target[i-1] based on stats available BEFORE bar i.
|
||||
if use_friday:
|
||||
# Use expanding Friday expectancy to decide Monday direction
|
||||
if fri_cnt >= min_samples and fri_sum != 0:
|
||||
fri_mean = fri_sum / fri_cnt
|
||||
direction = 1.0 if fri_mean > 0 else -1.0
|
||||
else:
|
||||
direction = 0.0
|
||||
else:
|
||||
# Use expanding Monday expectancy: continuation or reversal
|
||||
if mon_cnt >= min_samples and mon_sum != 0:
|
||||
mon_mean = mon_sum / mon_cnt
|
||||
direction = 1.0 if mon_mean > 0 else -1.0
|
||||
else:
|
||||
direction = 0.0
|
||||
|
||||
target[i - 1] = direction
|
||||
|
||||
# Now update the expanding stats with bar i-1's return (after using stats for bar i)
|
||||
# This ensures we never use r[i-1] to decide signal for bar i
|
||||
if prev_dow == 0:
|
||||
mon_sum += prev_r
|
||||
mon_cnt += 1
|
||||
elif prev_dow == 4:
|
||||
fri_sum += prev_r
|
||||
fri_cnt += 1
|
||||
|
||||
if vol_tgt:
|
||||
return al.vol_target(target, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
|
||||
else:
|
||||
return target
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
results = []
|
||||
|
||||
# Grid: 4 configs on 1d only
|
||||
grid = [
|
||||
# (name_suffix, min_samples, use_friday, vol_tgt)
|
||||
("mon-cont-ms10-vt", 10, False, True), # Monday continuation, vol-targeted
|
||||
("mon-cont-ms20-vt", 20, False, True), # Monday continuation, more samples
|
||||
("fri2mon-ms10-vt", 10, True, True), # Friday->Monday, vol-targeted
|
||||
("fri2mon-ms20-vt", 20, True, True), # Friday->Monday, more samples
|
||||
]
|
||||
|
||||
# Use study_weights (continuous position style is appropriate for "hold on Mondays")
|
||||
for suffix, min_s, use_fri, vt in grid:
|
||||
name = f"SEA07-{suffix}"
|
||||
rep = al.study_weights(
|
||||
name,
|
||||
lambda df, ms=min_s, uf=use_fri, v=vt: sea07_monday_continuation(
|
||||
df, min_samples=ms, use_friday=uf, vol_tgt=v
|
||||
),
|
||||
tfs=("1d",),
|
||||
)
|
||||
print(al.fmt(rep))
|
||||
print("JSON:", al.as_json(rep))
|
||||
print()
|
||||
|
||||
best_cell = max(rep["cells"], key=lambda c: c.get("min_asset_holdout_sharpe", -9))
|
||||
results.append((
|
||||
rep["verdict"].get("best_holdout_sharpe",
|
||||
best_cell.get("min_asset_holdout_sharpe", -9)),
|
||||
rep["verdict"].get("best_full_sharpe",
|
||||
best_cell.get("min_asset_full_sharpe", -9)),
|
||||
name,
|
||||
rep,
|
||||
))
|
||||
|
||||
# Pick best config by hold-out Sharpe (tie-break: full Sharpe)
|
||||
results.sort(key=lambda x: (x[0], x[1]), reverse=True)
|
||||
best_hold, best_full, best_name, best_rep = results[0]
|
||||
|
||||
print("\n=== BEST CONFIG ===", best_name)
|
||||
print(al.fmt(best_rep))
|
||||
print("JSON:", al.as_json(best_rep))
|
||||
Reference in New Issue
Block a user