diff --git a/data/leaderboard.json b/data/leaderboard.json index 843724c..3005d1c 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -181,43 +181,23 @@ } }, { - "author_name": "Example Author", - "strategy_name": "Example Strategy", - "description": "An example strategy", - "development_metrics": { - "sharpe": 0.0, - "total_return": 0.0, - "max_drawdown": 0.0, - "n_trades": 0, - "win_rate": 0.0 - }, - "holdout_metrics": { - "sharpe": 0.0, - "total_return": 0.0, - "max_drawdown": 0.0, - "n_trades": 0, - "win_rate": 0.0 - }, - "last_updated": "2024-01-01T00:00:00Z" - }, - { - "author_name": "YOUR_NAME", - "strategy_name": "YOUR_STRATEGY_NAME", - "description": "DESCRIBE YOUR STRATEGY HERE", - "last_updated": "2025-05-17T08:23:59.481326Z", + "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": 0.0, - "total_return": 0.0, - "max_drawdown": 0.0, - "n_trades": 0, - "win_rate": 0.0 + "sharpe": 1.217803722952385, + "total_return": 36432.64411560105, + "max_drawdown": 0.8861273181842388, + "n_trades": 2787, + "win_rate": 0.5331898098313599 }, "holdout_metrics": { - "sharpe": 0.0, - "total_return": 0.0, - "max_drawdown": 0.0, - "n_trades": 0, - "win_rate": 0.0 + "sharpe": 1.2721023608824928, + "total_return": 0.11707790357354719, + "max_drawdown": 0.16707933054084675, + "n_trades": 76, + "win_rate": 0.5263157894736842 } } ] 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')