Overall Statistics
Total Orders
376
Average Win
2.90%
Average Loss
-2.54%
Compounding Annual Return
17.316%
Drawdown
35.500%
Expectancy
0.267
Start Equity
10000
End Equity
37379.52
Net Profit
273.795%
Sharpe Ratio
0.591
Sortino Ratio
0.679
Probabilistic Sharpe Ratio
11.325%
Loss Rate
41%
Win Rate
59%
Profit-Loss Ratio
1.14
Alpha
0.041
Beta
0.894
Annual Standard Deviation
0.2
Annual Variance
0.04
Information Ratio
0.212
Tracking Error
0.149
Treynor Ratio
0.132
Total Fees
$403.52
Estimated Strategy Capacity
$2300000.00
Lowest Capacity Asset
DIS R735QTJ8XC9X
Portfolio Turnover
5.86%
#region imports
from AlgorithmImports import *

import numpy as np
#endregion


class BetaAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2016, 1, 1)   # Set Start Date
        self.set_end_date(2024, 4, 1)     # Set End Date
        self.set_cash(10000)             # Set Strategy Cash
        self.set_security_initializer(
            BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))
        )
        
        # Dow 30 companies. 
        self._symbols = [self.add_equity(ticker).symbol
            for ticker in ['AAPL', 'AXP', 'BA', 'CAT', 'CSCO', 'CVX', 'DD',
                           'DIS', 'GE', 'GS', 'HD', 'IBM', 'INTC', 'JPM',
                           'KO', 'MCD', 'MMM', 'MRK', 'MSFT', 'NKE', 'PFE',
                           'PG', 'TRV', 'UNH', 'UTX', 'V', 'VZ', 'WMT', 'XOM'] ]  

        # Benchmark
        self._benchmark = Symbol.create('SPY', SecurityType.EQUITY, Market.USA)

        # Set number days to trace back
        self._lookback = 21

        # Schedule Event: trigger the event at the begining of each month.
        self.schedule.on(self.date_rules.month_start(self._symbols[0]),
                         self.time_rules.after_market_open(self._symbols[0]),
                         self._rebalance)

    def _rebalance(self):

        # Fetch the historical data to perform the linear regression
        history = self.history(
            self._symbols + [self._benchmark], 
            self._lookback,
            Resolution.DAILY).close.unstack(level=0)
            
        symbols = self._select_symbols(history)

        # Liquidate positions that are not held by selected symbols
        for symbol, holdings in self.portfolio.items():
            if symbol not in symbols and holdings.invested:
                self.liquidate(symbol)

        # Invest 100% in the selected symbols
        for symbol in symbols:
            self.set_holdings(symbol, 0.5)

    def _select_symbols(self, history):
        alphas = dict()

        # Get the benchmark returns
        benchmark = history[self._benchmark].pct_change().dropna()

        # Conducts linear regression for each symbol and save the intercept/alpha
        for symbol in self._symbols:
            
            # Get the security returns
            returns = history[symbol].pct_change().dropna()
            bla = np.vstack([benchmark, np.ones(len(returns))]).T

            # Simple linear regression function in Numpy
            result = np.linalg.lstsq(bla , returns)
            alphas[symbol] = result[0][1]

        # Select symbols with the highest intercept/alpha to the benchmark
        selected = sorted(alphas.items(), key=lambda x: x[1], reverse=True)[:2]
        return [x[0] for x in selected]