Overall Statistics
Total Orders
550
Average Win
0.70%
Average Loss
-0.19%
Compounding Annual Return
11.758%
Drawdown
24.300%
Expectancy
0.959
Start Equity
10000
End Equity
16028.10
Net Profit
60.281%
Sharpe Ratio
0.566
Sortino Ratio
0.385
Probabilistic Sharpe Ratio
25.147%
Loss Rate
58%
Win Rate
42%
Profit-Loss Ratio
3.71
Alpha
0.044
Beta
0.228
Annual Standard Deviation
0.115
Annual Variance
0.013
Information Ratio
-0.144
Tracking Error
0.178
Treynor Ratio
0.286
Total Fees
$549.00
Estimated Strategy Capacity
$5600000.00
Lowest Capacity Asset
IJS RWQR2INKP0TH
Portfolio Turnover
3.28%
#region imports
from AlgorithmImports import *
#endregion
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from datetime import timedelta

import numpy as np

class ScheduledCAPM(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020,1,1)
        self.SetEndDate(2024,3,31)  
        self._cash = 10000
        self.SetCash(self._cash)                    
        
        self._tickers =  ['IJR', 'IWM', 'IWF', 'IJH', 'IWD', 'ITOT', 'IVW',
                          'IWR', 'IWB', 'IVE', 'IWN' ,'IWP', 'IWS', 'IWO',
                          'IWV', 'IUSG','IBB', 'IUSV','IHI', 'IJS', 'IJJ',
                          'IJK', 'IYW', 'OEF']
         
       
        self.symbols = [self.AddEquity(ticker).Symbol for ticker in self._tickers ] 
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.ivv = self.AddEquity("IVV", Resolution.Daily).Symbol
        self._bench = self.AddEquity("IVV", Resolution.Daily).Symbol
        
        self.lookback = 30
        self.SetWarmup(31)
        self.counter = 0
        self._counter=0
        
        self.reference = self.History(self.ivv, 5, Resolution.Daily)['close']
        self._initialValue = self.reference.iloc[0]
        
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen(self.spy, 10), self.Rebalance)    
        self._portfolioValue =[self._cash]
        
        self._stopLoss=float(self.GetParameter("stop_loss"))
        self._num_alpha=int(self.GetParameter("number_of_stocks"))
        self._num_beta = int(self.GetParameter("number_of_stocks_beta"))
        self._numStocks= self._num_alpha + self._num_beta
        self._weight = (1/(2*self._numStocks))
        
    def Rebalance(self):
        self.counter += 1
        self.Debug(f"Counter : {self.counter}")
    
        if self.IsWarmingUp: 
            return
        
        if self.counter % 3 == 0:
            
            history = self.History(
            self.symbols + [self._bench], 
            self.lookback,
            Resolution.Daily).close.unstack(level=0)
            
            self.symbols_alpha = self.SelectSymbols_alphas(history)
            self.symbols_beta = self.SelectSymbols_betas(history)
            
            for holdings in self.Portfolio.Values:
                symbol = holdings.Symbol
            
            if symbol not in self.symbols_alpha and symbol not in self.symbols_beta and holdings.Invested:
                self.Liquidate(symbol)
            for symbol in self.symbols_alpha:
                self.SetHoldings(symbol, self._weight)
            for symbol in self.symbols_beta:
                self.SetHoldings(symbol, self._weight)
            self.SetHoldings("SPY", 1-self._weight)
       
    def OnData(self,data):
        
        self._portfolioValue.append(self.Portfolio.TotalPortfolioValue)
        if self.Portfolio.TotalPortfolioValue - self._portfolioValue[self._counter] < -self._cash*self._stopLoss:
            self.Liquidate()
        self._counter += 1
        
   
    def SelectSymbols_alphas(self, history):
        alphas = dict()
        _bench = history[self._bench].pct_change().dropna()

        for symbol in self.symbols:
            returns = history[symbol].pct_change().dropna()
            bla = np.vstack([_bench, np.ones(len(returns))]).T
            result = np.linalg.lstsq(bla , returns)
            alphas[symbol] = result[0][1]
            

        selected_alphas = sorted(alphas.items(), key=lambda x: x[1], reverse=True)[:self._numStocks-1]
        return [x[0] for x in selected_alphas]
        
    def SelectSymbols_betas(self, history):
        betas = dict()
        _bench = history[self._bench].pct_change().dropna()

        for symbol in self.symbols:
            returns = history[symbol].pct_change().dropna()
            bla = np.vstack([_bench, np.ones(len(returns))]).T
            result = np.linalg.lstsq(bla , returns)
            betas[symbol]= result[0][0]

        selected_betas = sorted(betas.items(), key=lambda x: x[1], reverse=True)[:self._numStocks-1]
        return [x[0] for x in selected_betas]