| Overall Statistics |
|
Total Orders 10001 Average Win 0.25% Average Loss -0.13% Compounding Annual Return 23.070% Drawdown 12.100% Expectancy 0.042 Start Equity 100000.00 End Equity 119960.18 Net Profit 19.960% Sharpe Ratio 0.829 Sortino Ratio 1.889 Probabilistic Sharpe Ratio 39.582% Loss Rate 64% Win Rate 36% Profit-Loss Ratio 1.86 Alpha 0.147 Beta 0.181 Annual Standard Deviation 0.208 Annual Variance 0.043 Information Ratio 0.095 Tracking Error 0.315 Treynor Ratio 0.955 Total Fees $0.00 Estimated Strategy Capacity $87000.00 Lowest Capacity Asset BTCUSD 2XR Portfolio Turnover 2245.99% Drawdown Recovery 228 |
# region imports
from AlgorithmImports import *
# endregion
class MACDStrategy(QCAlgorithm):
def Initialize(self):
self.set_start_date(2020, 1, 1)
self.set_end_date(2022, 1, 1)
self.set_cash(100000)
self.btc = self.add_crypto("BTCUSD", Resolution.MINUTE, Market.COINBASE)
fast_period = 12
slow_period = 26
signal_period = 9
rsi_period = 14
atr_period = 14
volume_period = 20
self.rsi_oversold = 30
self.rsi_overbought = 70
self.atr_multiplier = 2.5
self.min_macd_histogram = 0.01
self.MACD = self.macd(self.btc.symbol, fast_period, slow_period, signal_period, MovingAverageType.EXPONENTIAL, Resolution.MINUTE)
self.RSI = self.rsi(self.btc.Symbol, rsi_period, MovingAverageType.EXPONENTIAL, Resolution.MINUTE)
self.ATR = self.atr(self.btc.Symbol, atr_period, MovingAverageType.EXPONENTIAL, Resolution.MINUTE)
self.volume_sma = self.sma(self.btc.symbol, volume_period, Resolution.MINUTE, Field.VOLUME)
self.previous_macd_signal = 0
self.stop_loss_ticket = None
self.entry_price = 0
def OnData(self, data: Slice):
if not all([self.MACD.is_ready, self.RSI.is_ready, self.ATR.is_ready, self.volume_sma.is_ready]):
return
macd_value = self.MACD.current.value
macd_signal = self.MACD.signal.current.value
macd_histogram = self.MACD.histogram.current.value
current_macd_signal = 0
if macd_value > macd_signal and macd_histogram > 0:
current_macd_signal = 1
elif macd_value < macd_signal and macd_histogram < 0:
current_macd_signal = -1
is_bullish_crossover = (self.previous_macd_signal <= 0 and current_macd_signal == 1)
is_bearish_crossover = (self.previous_macd_signal >= 0 and current_macd_signal == -1)
current_rsi = self.RSI.Current.Value
current_volume = self.securities[self.btc.symbol].volume
avg_volume = self.volume_sma.current.value
current_price = self.securities[self.btc.Symbol].Close
is_volume_above_average = current_volume > avg_volume * 1.2
is_strong_macd_signal = abs(macd_histogram) > self.min_macd_histogram
if is_bullish_crossover and not self.portfolio.invested:
rsi_not_overbought = current_rsi < self.rsi_overbought
macd_above_zero = macd_value > 0
if rsi_not_overbought and is_volume_above_average and is_strong_macd_signal:
self.log(f"MACD Bullish Crossover: MACD={macd_value:.4f}, Signal={macd_signal:.4f}, Histogram={macd_histogram:.4f}")
self.log(f"RSI: {current_rsi:.2f}, Volume Multiplier: {current_volume/avg_volume:.2f}")
self.set_holdings(self.btc.Symbol, 1.0)
self.entry_price = current_price
stop_price = current_price - (self.ATR.Current.Value * self.atr_multiplier)
self.stop_loss_ticket = self.stop_market_order(self.btc.Symbol, -self.portfolio[self.btc.Symbol].quantity, stop_price)
self.log(f"Long position entered at {current_price:.2f}, Stop-loss at {stop_price:.2f}")
elif self.portfolio.invested:
should_exit = False
exit_reason = ""
if is_bearish_crossover:
should_exit = True
exit_reason = "MACD Bearish Crossover"
elif current_rsi > self.rsi_overbought and macd_histogram < 0:
should_exit = True
exit_reason = "RSI Overbought + Weakening MACD"
elif macd_value < 0 and macd_signal < 0 and macd_histogram < -self.min_macd_histogram:
should_exit = True
exit_reason = "MACD in Bearish Territory"
if should_exit:
self.log(f"Exit Signal: {exit_reason}")
self.log(f"MACD={macd_value:.4f}, Signal={macd_signal:.4f}, Histogram={macd_histogram:.4f}")
self.liquidate(self.btc.Symbol)
if self.stop_loss_ticket:
self.stop_loss_ticket.Cancel()
self.stop_loss_ticket = None
if self.entry_price > 0:
pnl_percent = ((current_price - self.entry_price) / self.entry_price) * 100
self.log(f"Trade closed: Entry={self.entry_price:.2f}, Exit={current_price:.2f}, P&L={pnl_percent:.2f}%")
elif self.portfolio.invested and self.stop_loss_ticket:
current_profit_percent = ((current_price - self.entry_price) / self.entry_price) * 100
if current_profit_percent > 5:
new_stop_price = current_price - (self.ATR.Current.Value * self.atr_multiplier * 0.75)
try:
current_stop_price = self.stop_loss_ticket.Get(OrderField.StopPrice)
if new_stop_price > current_stop_price:
self.stop_loss_ticket.Cancel()
self.stop_loss_ticket = self.stop_market_order(self.btc.Symbol, -self.portfolio[self.btc.Symbol].quantity, new_stop_price)
self.log(f"Trailing stop updated to {new_stop_price:.2f} (Profit: {current_profit_percent:.2f}%)")
except:
self.stop_loss_ticket = self.stop_market_order(self.btc.Symbol, -self.portfolio[self.btc.Symbol].quantity, new_stop_price)
self.log(f"New trailing stop created at {new_stop_price:.2f} (Profit: {current_profit_percent:.2f}%)")
self.previous_macd_signal = current_macd_signal
def OnOrderEvent(self, orderEvent):
if orderEvent.Status == OrderStatus.FILLED:
self.log(f"Order Filled: {orderEvent.Symbol} - Qty: {orderEvent.FillQuantity} @ ${orderEvent.FillPrice:.2f}")
if self.stop_loss_ticket and self.stop_loss_ticket.OrderId == orderEvent.OrderId:
self.log(f"Stop-loss executed for {orderEvent.Symbol} at ${orderEvent.FillPrice:.2f}")
self.stop_loss_ticket = None
if self.entry_price > 0:
pnl_percent = ((orderEvent.FillPrice - self.entry_price) / self.entry_price) * 100
self.log(f"Stop-loss trade: Entry=${self.entry_price:.2f}, Exit=${orderEvent.FillPrice:.2f}, P&L={pnl_percent:.2f}%")
if orderEvent.Status == OrderStatus.CANCELED:
if self.stop_loss_ticket and self.stop_loss_ticket.OrderId == orderEvent.OrderId:
self.stop_loss_ticket = None