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