refactor: riorganizzazione script — Strategy ABC, folder strategies/waste/analysis
- src/strategies/base.py: Strategy ABC con Signal, BacktestResult, YearlyStats - src/strategies/indicators.py: keltner_ratio, detect_squeezes, ema, atr, rv, corr - scripts/strategies/: SQ01-SQ04 (squeeze puro/filtri), ML01 (squeeze+GBM) - scripts/waste/: W01-W22 script scartati + REF originali - scripts/analysis/: compare, best_yearly, final_report, paper_status - CLAUDE.md aggiornato con nuova struttura e tabella strategie Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""S2-05: Gap fade + overnight reversal.
|
||||
Crypto non ha gap di apertura classici, ma ha "gap di sessione":
|
||||
- Asia open (00 UTC): tende a continuare il trend USA precedente
|
||||
- EU open (07 UTC): spesso corregge eccessi notturni
|
||||
- USA open (13-14 UTC): alta volatilità, breakout o reversal
|
||||
|
||||
Strategia: fai fade dell'overextension al cambio sessione.
|
||||
Se il prezzo ha fatto >1.5% nella sessione precedente, aspettati reversal.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from src.data.downloader import load_data
|
||||
|
||||
FEE = 0.001
|
||||
INITIAL = 1000
|
||||
LEVERAGE = 3
|
||||
|
||||
|
||||
def run_gap_fade(asset, tf="1h"):
|
||||
print(f"\n{'#'*60}")
|
||||
print(f" {asset} {tf} — GAP FADE / SESSION REVERSAL")
|
||||
print(f"{'#'*60}")
|
||||
|
||||
df = load_data(asset, tf)
|
||||
close = df["close"].values
|
||||
high = df["high"].values
|
||||
low = df["low"].values
|
||||
n = len(close)
|
||||
split = int(n * 0.7)
|
||||
timestamps = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
|
||||
hours = timestamps.dt.hour.values
|
||||
|
||||
session_opens = {
|
||||
"asia": 0,
|
||||
"eu": 7,
|
||||
"usa": 14,
|
||||
}
|
||||
|
||||
configs = [
|
||||
# (session_name, lookback_hours, entry_thr, hold_hours, stop_pct, name)
|
||||
("eu", 7, 0.015, 4, 0.012, "eu_fade_1.5pct"),
|
||||
("eu", 7, 0.02, 4, 0.015, "eu_fade_2pct"),
|
||||
("eu", 7, 0.01, 6, 0.01, "eu_fade_1pct_6h"),
|
||||
("usa", 7, 0.015, 4, 0.012, "usa_fade_1.5pct"),
|
||||
("usa", 7, 0.02, 4, 0.015, "usa_fade_2pct"),
|
||||
("asia", 8, 0.02, 6, 0.015, "asia_fade_2pct"),
|
||||
("eu", 7, 0.025, 3, 0.015, "eu_fade_2.5pct_fast"),
|
||||
("usa", 6, 0.015, 3, 0.01, "usa_fade_fast"),
|
||||
]
|
||||
|
||||
for session, lookback, entry_thr, hold, stop, name in configs:
|
||||
capital = float(INITIAL)
|
||||
correct = 0
|
||||
total = 0
|
||||
daily_trades = {}
|
||||
|
||||
session_hour = session_opens[session]
|
||||
|
||||
for i in range(max(split, lookback + 1), n - hold):
|
||||
if hours[i] != session_hour:
|
||||
continue
|
||||
|
||||
day = timestamps.iloc[i].strftime("%Y-%m-%d")
|
||||
if daily_trades.get(day, 0) >= 1:
|
||||
continue
|
||||
|
||||
prev_ret = (close[i] - close[i - lookback]) / close[i - lookback]
|
||||
|
||||
direction = None
|
||||
if prev_ret > entry_thr:
|
||||
direction = "short" # fade the rally
|
||||
elif prev_ret < -entry_thr:
|
||||
direction = "long" # fade the dump
|
||||
|
||||
if direction is None:
|
||||
continue
|
||||
|
||||
entry = close[i]
|
||||
exit_price = close[min(i + hold, n - 1)]
|
||||
|
||||
for j in range(i + 1, min(i + hold + 1, n)):
|
||||
if direction == "long":
|
||||
if (close[j] - entry) / entry >= stop * 2:
|
||||
exit_price = close[j]
|
||||
break
|
||||
if (entry - close[j]) / entry >= stop:
|
||||
exit_price = close[j]
|
||||
break
|
||||
else:
|
||||
if (entry - close[j]) / entry >= stop * 2:
|
||||
exit_price = close[j]
|
||||
break
|
||||
if (close[j] - entry) / entry >= stop:
|
||||
exit_price = close[j]
|
||||
break
|
||||
exit_price = close[j]
|
||||
|
||||
if direction == "long":
|
||||
trade_ret = (exit_price - entry) / entry
|
||||
else:
|
||||
trade_ret = (entry - exit_price) / entry
|
||||
|
||||
net = trade_ret * LEVERAGE - FEE * 2 * LEVERAGE
|
||||
capital += capital * 0.2 * net
|
||||
capital = max(capital, 0)
|
||||
|
||||
total += 1
|
||||
if trade_ret > 0:
|
||||
correct += 1
|
||||
daily_trades[day] = daily_trades.get(day, 0) + 1
|
||||
|
||||
if total < 15:
|
||||
continue
|
||||
|
||||
acc = correct / total * 100
|
||||
ret = (capital - INITIAL) / INITIAL * 100
|
||||
test_days = (n - split) / 24
|
||||
test_years = test_days / 365.25
|
||||
ann = ((capital / INITIAL) ** (1 / test_years) - 1) * 100 if test_years > 0 and capital > 0 else -100
|
||||
dpnl = (capital - INITIAL) / test_days if test_days > 0 else 0
|
||||
days_active = len(daily_trades)
|
||||
|
||||
tag = "✅" if acc >= 58 and ann >= 30 else ""
|
||||
print(f" {name:25s}: trades={total:4d} acc={acc:.1f}% ret={ret:+.1f}% ann={ann:+.1f}% €/day={dpnl:.2f} active={days_active} {tag}")
|
||||
|
||||
|
||||
for asset in ["ETH", "BTC"]:
|
||||
run_gap_fade(asset)
|
||||
Reference in New Issue
Block a user