Overall Statistics
Total Orders
19
Average Win
0.83%
Average Loss
-0.48%
Compounding Annual Return
11.058%
Drawdown
3.300%
Expectancy
1.123
Start Equity
2000000
End Equity
2089665.73
Net Profit
4.483%
Sharpe Ratio
0.392
Sortino Ratio
0.592
Probabilistic Sharpe Ratio
60.327%
Loss Rate
22%
Win Rate
78%
Profit-Loss Ratio
1.73
Alpha
-0.053
Beta
0.408
Annual Standard Deviation
0.056
Annual Variance
0.003
Information Ratio
-2.358
Tracking Error
0.069
Treynor Ratio
0.054
Total Fees
$181.98
Estimated Strategy Capacity
$420000000.00
Lowest Capacity Asset
MSFT R735QTJ8XC9X
Portfolio Turnover
3.13%
from AlgorithmImports import *

class CombinedTradingAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2023, 12, 20)
        self.SetEndDate(2024, 5, 20)
        
        self.SetCash(2_000_000)
        
        # S1: AMZN (Momentum Strategy)
        self.s1 = self.AddEquity("AMZN", Resolution.Daily).Symbol
        self.sma_s1 = self.SMA(self.s1, 20, Resolution.Daily)
        
        # S2: MSFT (Mean Reversion Strategy)
        self.s2 = self.AddEquity("MSFT", Resolution.Daily).Symbol
        self.sma_s2 = self.SMA(self.s2, 20, Resolution.Daily)
        
        self.weight = 0.25  ## each stock gets 25% allocation

        self.max_daily_loss = 0
        self.max_loss_date = None
        self.previous_value = None  
        self.pnl_on_max_loss_day = {"realized": 0, "unrealized": 0}


    def OnData(self, data):
        if not self.sma_s1.IsReady or not self.sma_s2.IsReady:
            return 
        
        #get latest price & Simple Moving Average (SMA) values
        if self.s1 in data and data[self.s1] is not None:
            price_s1 = data[self.s1].Close
            sma_value_s1 = self.sma_s1.Current.Value

            # Momentum Strategy (AMZN)
            if price_s1 > sma_value_s1 and not self.Portfolio[self.s1].Invested:
                self.SetHoldings(self.s1, self.weight)  # Buy 25% of portfolio
            elif price_s1 <= sma_value_s1 and self.Portfolio[self.s1].Invested:
                self.Liquidate(self.s1) 

        if self.s2 in data and data[self.s2] is not None:
            price_s2 = data[self.s2].Close
            sma_value_s2 = self.sma_s2.Current.Value

            # Mean Reversion Strategy (MSFT)
            if price_s2 < sma_value_s2 and not self.Portfolio[self.s2].Invested:
                self.SetHoldings(self.s2, self.weight)  # Buy 25% of portfolio
            elif price_s2 >= sma_value_s2 and self.Portfolio[self.s2].Invested:
                self.Liquidate(self.s2) 

    def OnEndOfDay(self):
        if self.previous_value is not None:
            daily_loss = (self.Portfolio.TotalPortfolioValue / self.previous_value) - 1

            if daily_loss < self.max_daily_loss:  # Track the worst loss day
                self.max_daily_loss = daily_loss
                self.max_loss_date = self.Time.date()
                self.pnl_on_max_loss_day["realized"] = self.Portfolio.TotalProfit  # Realized PnL
                self.pnl_on_max_loss_day["unrealized"] = (
                    self.Portfolio.TotalPortfolioValue - self.Portfolio.Cash - self.Portfolio.TotalProfit
                )  # Unrealized PnL

        self.previous_value = self.Portfolio.TotalPortfolioValue

    def OnEndOfAlgorithm(self):
        self.Debug(f"Max Daily Loss: {self.max_daily_loss:.4%} on {self.max_loss_date}")
        self.Debug(f"Realized PnL on Max Loss Day: ${self.pnl_on_max_loss_day['realized']:,.2f}")
        self.Debug(f"Unrealized PnL on Max Loss Day: ${self.pnl_on_max_loss_day['unrealized']:,.2f}")