From 464f4b1f9f43fd8b3f1e2c573f41d28188691b3c Mon Sep 17 00:00:00 2001 From: Lucien Lee Date: Sat, 17 May 2025 20:03:28 +0800 Subject: [PATCH 1/2] feat: EMA Crossover With Adaptive ATRFilter Strategy --- src/strategies/lucien_ema-v1.py | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/strategies/lucien_ema-v1.py diff --git a/src/strategies/lucien_ema-v1.py b/src/strategies/lucien_ema-v1.py new file mode 100644 index 0000000..b85e417 --- /dev/null +++ b/src/strategies/lucien_ema-v1.py @@ -0,0 +1,62 @@ +""" +EMA Crossover + Adaptive ATR Filter strategy for Bitcoin. +Buy when fast EMA > slow EMA and ATR% exceeds its rolling quantile threshold. +Sell when EMA crosses down or ATR% falls below that adaptive threshold. +""" + +import pandas as pd +from src.core.strategy import Strategy # adjust import as needed + +class EMACrossoverWithAdaptiveATRFilterStrategy(Strategy): + def __init__(self, + initial_capital=10000, + fast=30, + slow=120, + atr_period=14, + atr_quantile_window=100, + atr_quantile=0.5): + super().__init__( + initial_capital=initial_capital, + author_name="Lucien", + strategy_name="EMA Crossover + Adaptive ATR Filter", + description=( + "Buy when fast EMA > slow EMA and ATR% > rolling " + "quantile threshold; sell on reversal or low adaptive volatility." + ) + ) + self.fast = fast + self.slow = slow + self.atr_period = atr_period + self.atr_quantile_window = atr_quantile_window + self.atr_quantile = atr_quantile + + def get_signals(self, df: pd.DataFrame) -> pd.Series: + signals = pd.Series('hold', index=df.index) + + # 1. Compute EMAs + ema_fast = df['close'].ewm(span=self.fast, adjust=False).mean() + ema_slow = df['close'].ewm(span=self.slow, adjust=False).mean() + + # 2. Compute ATR% (using close-only TR approximation) + prev_close = df['close'].shift(1) + tr = (df['close'] - prev_close).abs() + atr = tr.rolling(window=self.atr_period).mean() + atr_pct = atr / df['close'] + + # 3. Build adaptive threshold: rolling quantile of ATR% + atr_threshold = atr_pct.rolling(window=self.atr_quantile_window) \ + .quantile(self.atr_quantile) + + # 4. Entry / Exit conditions + buy_cond = (ema_fast > ema_slow) & (atr_pct > atr_threshold) + sell_cond = (ema_fast < ema_slow) | (atr_pct < atr_threshold) + + signals[buy_cond] = 'buy' + signals[sell_cond] = 'sell' + + # 5. Warm-up / prevent lookahead + warmup = max(self.slow, self.atr_period, self.atr_quantile_window) + signals.iloc[:warmup] = 'hold' + + # 6. Shift by one to avoid lookahead bias + return signals.shift(1).fillna('hold') From 7a4f007747008861ef86fb567c5f1b37453bc31e Mon Sep 17 00:00:00 2001 From: Lucien Lee Date: Sat, 17 May 2025 20:47:59 +0800 Subject: [PATCH 2/2] chore: update leaderboard --- data/leaderboard.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/data/leaderboard.json b/data/leaderboard.json index e7e1cb9..e187022 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -20,6 +20,26 @@ "win_rate": 0.21052631578947367 } }, + { + "author_name": "Lucien", + "strategy_name": "EMA Crossover + Adaptive ATR Filter", + "description": "Buy when fast EMA > slow EMA and ATR% > rolling quantile threshold; sell on reversal or low adaptive volatility.", + "last_updated": "2025-05-17T12:47:24.735129Z", + "development_metrics": { + "sharpe": 1.217803722952385, + "total_return": 36432.64411560105, + "max_drawdown": 0.8861273181842388, + "n_trades": 2787, + "win_rate": 0.5331898098313599 + }, + "holdout_metrics": { + "sharpe": 1.2721023608824928, + "total_return": 0.11707790357354719, + "max_drawdown": 0.16707933054084675, + "n_trades": 76, + "win_rate": 0.5263157894736842 + } + }, { "author_name": "Example Author", "strategy_name": "Example Strategy",