From 309b18a73914dc0cef40c488733ce37ebda58d98 Mon Sep 17 00:00:00 2001 From: Barnabas Debreceni Date: Sat, 17 May 2025 22:11:14 +0800 Subject: [PATCH] barnabas Choppy multi-EWMA --- data/leaderboard.json | 128 ++++++++++++++++++-------------- src/strategies/barnabas-ewma.py | 94 +++++++++++++++++++++++ 2 files changed, 168 insertions(+), 54 deletions(-) create mode 100644 src/strategies/barnabas-ewma.py diff --git a/data/leaderboard.json b/data/leaderboard.json index 9913547..59c47aa 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -60,6 +60,26 @@ "win_rate": 0.4847161572052402 } }, + { + "author_name": "Jim", + "strategy_name": "Enhanced Price Momentum", + "description": "Enhanced momentum strategy with trend confirmation, volatility-based position sizing, and risk management.", + "last_updated": "2025-05-17T12:50:15.376811Z", + "development_metrics": { + "sharpe": 1.5028615712789264, + "total_return": 83691.01305699791, + "max_drawdown": 0.8047181682279175, + "n_trades": 175, + "win_rate": 0.4685714285714286 + }, + "holdout_metrics": { + "sharpe": 2.3589800460822663, + "total_return": 0.25319737220833005, + "max_drawdown": 0.09536946074759874, + "n_trades": 8, + "win_rate": 0.75 + } + }, { "author_name": "Mahak", "strategy_name": "Advanced Buy And Hold Strategy", @@ -140,6 +160,26 @@ "win_rate": 0.25 } }, + { + "author_name": "Aditya", + "strategy_name": "SMA Crossover 1", + "description": "Goes long when the 30-period SMA crosses above the 120-period SMA, and exits when it crosses below.", + "last_updated": "2025-05-17T12:42:57.466976Z", + "development_metrics": { + "sharpe": 1.2890757652003817, + "total_return": 16908.019911407955, + "max_drawdown": 0.8924143378220439, + "n_trades": 584, + "win_rate": 0.4126712328767123 + }, + "holdout_metrics": { + "sharpe": 0.1546100984967055, + "total_return": -0.005179828456650437, + "max_drawdown": 0.2720590455045794, + "n_trades": 20, + "win_rate": 0.25 + } + }, { "author_name": "Will", "strategy_name": "SMA Crossover 1", @@ -160,26 +200,6 @@ "win_rate": 0.2962962962962963 } }, - { - "author_name": "Yuan", - "strategy_name": "Volume SMA Confirmation Strategy", - "description": "Goes long when the price breaks above the resistance with high volume, and exits when it breaks below the support with high volume.", - "last_updated": "2025-05-17T10:50:33.555705Z", - "development_metrics": { - "sharpe": 1.1497768598234075, - "total_return": 4483.2488594820325, - "max_drawdown": 0.8781365323919945, - "n_trades": 735, - "win_rate": 0.48707482993197276 - }, - "holdout_metrics": { - "sharpe": 0.7297079146266436, - "total_return": 0.07258730731958374, - "max_drawdown": 0.19649304246128135, - "n_trades": 26, - "win_rate": 0.46153846153846156 - } - }, { "author_name": "Lucien", "strategy_name": "EMA Crossover + Adaptive ATR Filter", @@ -200,26 +220,6 @@ "win_rate": 0.5263157894736842 } }, - { - "author_name": "Aditya", - "strategy_name": "SMA Crossover 1", - "description": "Goes long when the 30-period SMA crosses above the 120-period SMA, and exits when it crosses below.", - "last_updated": "2025-05-17T12:42:57.466976Z", - "development_metrics": { - "sharpe": 1.2890757652003817, - "total_return": 16908.019911407955, - "max_drawdown": 0.8924143378220439, - "n_trades": 584, - "win_rate": 0.4126712328767123 - }, - "holdout_metrics": { - "sharpe": 0.1546100984967055, - "total_return": -0.005179828456650437, - "max_drawdown": 0.2720590455045794, - "n_trades": 20, - "win_rate": 0.25 - } - }, { "author_name": "Will", "strategy_name": "ML Enhanced Trading Strategy", @@ -241,23 +241,43 @@ } }, { - "author_name": "Jim", - "strategy_name": "Enhanced Price Momentum", - "description": "Enhanced momentum strategy with trend confirmation, volatility-based position sizing, and risk management.", - "last_updated": "2025-05-17T12:50:15.376811Z", + "author_name": "Yuan", + "strategy_name": "Volume SMA Confirmation Strategy", + "description": "Goes long when the price breaks above the resistance with high volume, and exits when it breaks below the support with high volume.", + "last_updated": "2025-05-17T10:50:33.555705Z", "development_metrics": { - "sharpe": 1.5028615712789264, - "total_return": 83691.01305699791, - "max_drawdown": 0.8047181682279175, - "n_trades": 175, - "win_rate": 0.4685714285714286 + "sharpe": 1.1497768598234075, + "total_return": 4483.2488594820325, + "max_drawdown": 0.8781365323919945, + "n_trades": 735, + "win_rate": 0.48707482993197276 }, "holdout_metrics": { - "sharpe": 2.3589800460822663, - "total_return": 0.25319737220833005, - "max_drawdown": 0.09536946074759874, - "n_trades": 8, - "win_rate": 0.75 + "sharpe": 0.7297079146266436, + "total_return": 0.07258730731958374, + "max_drawdown": 0.19649304246128135, + "n_trades": 26, + "win_rate": 0.46153846153846156 + } + }, + { + "author_name": "Barnabas", + "strategy_name": "Multi EWMA Crossover (Choppy)", + "description": "Goes long when the fast EWMA crosses above the slow EWMA, and exits when it crosses below. Prevents trades when the market is choppy.", + "last_updated": "2025-05-17T17:57:56.951056Z", + "development_metrics": { + "sharpe": 1.1292850486779653, + "total_return": 4629.7711500823425, + "max_drawdown": 0.911266631874821, + "n_trades": 328, + "win_rate": 0.34146341463414637 + }, + "holdout_metrics": { + "sharpe": 0.5300982500415287, + "total_return": 0.04090393135053816, + "max_drawdown": 0.15072754709598601, + "n_trades": 7, + "win_rate": 0.2857142857142857 } }, { diff --git a/src/strategies/barnabas-ewma.py b/src/strategies/barnabas-ewma.py new file mode 100644 index 0000000..8ba457e --- /dev/null +++ b/src/strategies/barnabas-ewma.py @@ -0,0 +1,94 @@ +import pandas as pd +import numpy as np + +from src.core.strategy import Strategy + +def choppiness(high: pd.Series, + low: pd.Series, + close: pd.Series, + length: int = 14) -> pd.Series: + """ + Choppiness Index (CHOP) + + Parameters + ---------- + high, low, close : pd.Series + length : look-back period (default 14) + + Returns + ------- + pd.Series of CHOP values in the 0-100 range + """ + # True Range (vectorised) + tr = pd.concat([ + high - low, + (high - close.shift(1)).abs(), + (low - close.shift(1)).abs() + ], axis=1).max(axis=1) + + sum_tr = tr.rolling(length).sum() + price_range = high.rolling(length).max() - low.rolling(length).min() + + # Avoid divide-by-zero if range is zero + chop = 100 * np.log10(sum_tr / price_range.replace(0, np.nan)) / np.log10(length) + return chop + +class BarnabasEWMA(Strategy): + def __init__(self, initial_capital=10000, fast=20, slow=200): + super().__init__( + initial_capital=initial_capital, + author_name="Barnabas", + strategy_name="Multi EWMA Crossover (Choppy)", + description="Goes long when the fast EWMA crosses above the slow EWMA, and exits when it crosses below. Prevents trades when the market is choppy." + ) + self.prices = [] + self.fast = fast + self.slow = slow + self.fast2 = fast * 2 + self.slow2 = slow * 2 + self.last_signal = 'hold' + + def process_bar(self, bar): + """ + Process each bar of data. + This is where you implement your strategy logic. + + Args: + bar: Dictionary containing 'time', 'close', and 'volume' data + """ + self.current_bar = bar + + # Add your strategy logic here + # For example: + # if self.current_bar['close'] > self.previous_close * (1 + self.threshold): + # self.last_signal = 'buy' + # elif self.current_bar['close'] < self.previous_close * (1 - self.threshold): + # self.last_signal = 'sell' + # else: + # self.last_signal = 'hold' + + def get_signal(self): + """ + Return the current trading signal. + Must return one of: 'buy', 'sell', 'hold' + """ + # Add your signal generation logic here + return 'hold' + + def get_signals(self, df: pd.DataFrame) -> pd.Series: + """ + Vectorized version of signal generation. + Override this if you want to implement a more efficient vectorized version. + """ + fast_ma = df['close'].ewm(span=self.fast).mean() + slow_ma = df['close'].ewm(span=self.slow).mean() + fast2_ma = df['close'].ewm(span=self.fast2).mean() + slow2_ma = df['close'].ewm(span=self.slow2).mean() + signals = pd.Series('hold', index=df.index) + choppy = choppiness(df['high'], df['low'], df['close'], length=self.slow2) + signals[(fast_ma > slow_ma) & (fast2_ma > slow2_ma) & (choppy < 60)] = 'buy' + signals[(fast_ma < slow_ma)] = 'sell' + signals.iloc[:self.slow-1] = 'hold' + + signals = signals.shift(1).fillna('hold') + return signals \ No newline at end of file