import backtrader as bt import numpy as np # 📈 Advanced EMA Crossover with Noise Filtering and Dynamic Risk Control # Designed for trend-following with robust entry/exit logic and institutional-style metrics class AdvancedEMACross(bt.Strategy): params = dict( fast=50, slow=200, atr_period=14, sl_atr=1.5, tp_atr=3.0, dmi_period=14, use_cross_switch=True, # 🚀 Enables automatic position reversal on EMA cross use_trailing_stop=True, # 🛡️ Enables dynamic trailing stop based on ATR trail_atr=1.0 # ATR-based distance for trailing stop adjustment ) def __init__(self): # ✅ Trend indicators self.ema_fast = bt.ind.EMA(self.data.close, period=self.p.fast) self.ema_slow = bt.ind.EMA(self.data.close, period=self.p.slow) # ✅ Volatility and trend confirmation indicators self.atr = bt.ind.ATR(self.data, period=self.p.atr_period) self.dmi = bt.ind.DirectionalMovementIndex(period=self.p.dmi_period) # ✅ EMA crossover trigger self.crossover = bt.ind.CrossOver(self.ema_fast, self.ema_slow) self.order = None self.entry_price = None self.sl_price = None self.tp_price = None self.trades = [] # 💰 Track PnL of each closed trade self.long_trades = 0 # 📊 Metrics for long trades self.short_trades = 0 # 📊 Metrics for short trades def next(self): if self.order: return # 📥 Entry logic if not self.position: if self.ema_fast[0] > self.ema_slow[0] and self.crossover > 0 and self.dmi.plusDI[0] > self.dmi.minusDI[0]: self.order = self.buy() self.entry_price = self.data.close[0] self.set_exit_prices(True) self.long_trades += 1 elif self.ema_fast[0] < self.ema_slow[0] and self.crossover < 0 and self.dmi.minusDI[0] > self.dmi.plusDI[0]: self.order = self.sell() self.entry_price = self.data.close[0] self.set_exit_prices(False) self.short_trades += 1 else: # 🔁 Smart position switching based on trend reversal if self.p.use_cross_switch: if self.position.size > 0 and self.crossover < 0 and self.ema_fast[0] < self.ema_slow[0]: pnl = self.data.close[0] - self.entry_price self.close() self.trades.append(pnl) self.order = self.sell() self.entry_price = self.data.close[0] self.set_exit_prices(False) self.short_trades += 1 return elif self.position.size < 0 and self.crossover > 0 and self.ema_fast[0] > self.ema_slow[0]: pnl = self.entry_price - self.data.close[0] self.close() self.trades.append(pnl) self.order = self.buy() self.entry_price = self.data.close[0] self.set_exit_prices(True) self.long_trades += 1 return # 🚪 Exit logic (TP/SL or trailing stop) if self.position.size > 0: if self.data.close[0] <= self.sl_price or self.data.close[0] >= self.tp_price: pnl = self.data.close[0] - self.entry_price self.close() self.trades.append(pnl) elif self.p.use_trailing_stop: self.sl_price = max(self.sl_price, self.data.close[0] - self.p.trail_atr * self.atr[0]) elif self.position.size < 0: if self.data.close[0] >= self.sl_price or self.data.close[0] <= self.tp_price: pnl = self.entry_price - self.data.close[0] self.close() self.trades.append(pnl) elif self.p.use_trailing_stop: self.sl_price = min(self.sl_price, self.data.close[0] + self.p.trail_atr * self.atr[0]) # 🧠 Calculates SL/TP dynamically using ATR volatility def set_exit_prices(self, is_long): if is_long: self.sl_price = self.entry_price - self.p.sl_atr * self.atr[0] self.tp_price = self.entry_price + self.p.tp_atr * self.atr[0] else: self.sl_price = self.entry_price + self.p.sl_atr * self.atr[0] self.tp_price = self.entry_price - self.p.tp_atr * self.atr[0] def notify_order(self, order): if order.status in [order.Completed, order.Canceled, order.Rejected]: self.order = None # 📊 Final backtest summary with key metrics for quant validation def stop(self): returns = np.array(self.trades) if len(returns) > 0: win_rate = np.sum(returns > 0) / len(returns) sharpe = np.mean(returns) / (np.std(returns) + 1e-9) * np.sqrt(252) downside = returns[returns < 0] sortino = (np.mean(returns) / (np.std(downside) + 1e-9)) if len(downside) > 0 else 0 peak = np.maximum.accumulate(np.cumsum(returns)) drawdown = np.cumsum(returns) - peak max_drawdown = drawdown.min() walk_forward_pass = sharpe > 1 and sortino > 0.7 and max_drawdown > -100 else: win_rate = sharpe = sortino = max_drawdown = 0 walk_forward_pass = False final_value = self.broker.getvalue() print('==== STRATEGY RESULTS ====') print(f'Total Trades: {len(returns)}') print(f'Long Trades: {self.long_trades}, Short Trades: {self.short_trades}') print(f'Win Rate: {win_rate:.2f}') print(f'Sharpe Ratio: {sharpe:.2f}') print(f'Sortino Ratio: {sortino:.2f}') print(f'Max Drawdown: {max_drawdown:.2f}') print(f'Walk Forward Pass: {walk_forward_pass}') print(f'Net PnL: {np.sum(returns):.2f}') print(f"Final Portfolio Value: {final_value:.2f} USD") print('==========================')