Overall Statistics
Total Orders
575
Average Win
5.66%
Average Loss
-2.00%
Compounding Annual Return
36.561%
Drawdown
49.600%
Expectancy
0.670
Start Equity
1000000
End Equity
16095150.52
Net Profit
1509.515%
Sharpe Ratio
1.016
Sortino Ratio
1.039
Probabilistic Sharpe Ratio
46.085%
Loss Rate
56%
Win Rate
44%
Profit-Loss Ratio
2.83
Alpha
0.193
Beta
0.783
Annual Standard Deviation
0.255
Annual Variance
0.065
Information Ratio
0.76
Tracking Error
0.229
Treynor Ratio
0.331
Total Fees
$28762.91
Estimated Strategy Capacity
$8000000.00
Lowest Capacity Asset
WMT R735QTJ8XC9X
Portfolio Turnover
1.78%
from AlgorithmImports import *
from sklearn.linear_model import LinearRegression

class ScikitLearnLongShortAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2015, 10, 7)  # Set Start Date
        self.set_end_date(2024, 10, 8)    # Set End Date

        self.lookback = 28  # number of previous days for training

        self.set_cash(1000000)  # Set Strategy Cash

        # Add symbols to the algorithm
        symbols = ["SPY", "AAPL", "NFLX", "ADBE", "META", "LLY", "WMT", "NVDA", "BA", "MSFT", "AMZN", "GOOG", "JPM", "HD", "NKE", "TSLA"]
        self.symbols = [self.add_equity(symbol, Resolution.MINUTE).symbol for symbol in symbols]

        # Schedule regression and trade functions
        self.schedule.on(self.date_rules.every_day(), 
                         self.time_rules.after_market_open("SPY", 28), 
                         self.regression)
        self.schedule.on(self.date_rules.every_day(), 
                         self.time_rules.after_market_open("SPY", 30), 
                         self.trade)

    def regression(self):
        # Daily historical data is used to train the machine learning model
        history = self.history(self.symbols, self.lookback, Resolution.DAILY)

        # price dictionary: key: symbol; value: historical price
        self.prices = {}
        # slope dictionary: key: symbol; value: slope
        self.slopes = {}

        for symbol in self.symbols:
            if not history.empty:
                # get historical open price
                self.prices[symbol] = list(history.loc[symbol.value]['open'])

        # A is the design matrix (time series)
        A = range(self.lookback + 1)

        for symbol in self.symbols:
            if symbol in self.prices:
                # response
                Y = self.prices[symbol]
                # features
                X = np.column_stack([np.ones(len(A)), A])

                # data preparation
                length = min(len(X), len(Y))
                X = X[-length:]
                Y = Y[-length:]
                A = A[-length:]

                # fit the linear regression
                reg = LinearRegression().fit(X, Y)

                # run linear regression y = ax + b
                b = reg.intercept_
                a = reg.coef_[1]

                # store slopes for symbols
                self.slopes[symbol] = a / b

    def trade(self):
        # if there is no open price
        if not self.prices:
            return

        thod_buy = 0.003  # threshold of slope to buy
        thod_sell = -0.006  # threshold of slope to sell (short)
        thod_liquidate = -0.005  # threshold of slope to liquidate long position
        thod_cover = 0.001  # threshold of slope to cover short position

        # Allocate only 90% of the portfolio
        allocation = 2 / len(self.symbols)

        for holding in self.portfolio.Values:
            slope = self.slopes[holding.symbol]
            # Liquidate long positions if slope is smaller than thod_liquidate
            if holding.invested and holding.is_long and slope < thod_liquidate:
                self.liquidate(holding.symbol)
            # Cover short positions if slope is greater than thod_cover
            if holding.invested and holding.is_short and slope > thod_cover:
                self.liquidate(holding.symbol)

        for symbol in self.symbols:
            slope = self.slopes[symbol]
            # Buy when slope is larger than thod_buy
            if slope > thod_buy and not self.portfolio[symbol].invested:
                self.set_holdings(symbol, allocation)
            # Short sell when slope is smaller than thod_sell
            elif slope < thod_sell and not self.portfolio[symbol].invested:
                self.set_holdings(symbol, -allocation)