5ac4e16af8
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>
172 lines
7.3 KiB
Python
172 lines
7.3 KiB
Python
"""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))
|