Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-1.681
Tracking Error
0.109
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
'''
Random Forest classifier to predict the direction of SPY's price movement and trades options accordingly.
'''

from AlgorithmImports import *
# Import necessary libraries for machine learning
from sklearn.ensemble import RandomForestClassifier
import numpy as np

class SPYOptionMLTradingAlgorithm(QCAlgorithm):
    '''
    Initializes the algorithm with necessary parameters and settings.
    '''
    def Initialize(self):
        self.SetStartDate(2023, 1, 2)
        self.SetEndDate(2023, 12, 31)
        self.SetCash(100_000)

        self.spy = self.AddEquity("SPY", Resolution.Minute)
        self.lookback = 30
        self.training_data = []
        self.model = RandomForestClassifier(n_estimators=10)

        # Schedules the model training to occur at the start of each month.
        self.schedule.on(self.date_rules.month_start("SPY"), self.time_rules.after_market_open("SPY", 30), self.TrainModel)
        # Schedule trading logic to run before market close every day
        self.Schedule.On(self.date_rules.every_day("SPY"), self.time_rules.before_market_close("SPY", 10), self.TradeOptions)
        # Schedule liquidation of all holdings at 3:55 PM
        self.Schedule.On(self.date_rules.every_day(), self.time_rules.at(15, 55), lambda: self.Liquidate())

    '''
    Trains the Random Forest model using historical SPY price data.
    '''
    def TrainModel(self) -> None:
        history = self.History(self.spy.Symbol, self.lookback + 1, Resolution.Daily)
        if history.empty or history.isnull().values.any():
            return

        # Calculate features and labels for the model
        features = np.diff(history['close'])
        labels = np.where(features >= 0, 1, 0)[:-1]
        features = features.reshape(-1, 1)[:-1]

        # Fit the model to the training data
        self.model.fit(features, labels)

    '''
    Predicts the direction of SPY's price movement and trades options accordingly.
    '''
    def TradeOptions(self):
        # Get the current SPY price and calculate the feature for prediction
        today_features = np.array([self.spy.Price - self.spy.Close])

        # Make a prediction using the trained model
        prediction = self.model.predict(today_features.reshape(1, -1))

        # Buy a call option if the prediction is positive, otherwise buy a put option
        if prediction[0] == 1:
            option = self.BuyCallOption()
        else:
            option = self.BuyPutOption()

        # If an option contract is found, place a market order to buy it
        if option is not None:
            # Check if the option contract exists in the security list before placing an order
            if self.Securities.ContainsKey(option):
                self.MarketOrder(option, 1)
            else:
                self.Log("Option contract not found in security list: " + str(option))

    '''
    Returns a call option contract with the closest expiration date.
    '''
    def BuyCallOption(self):
        return self.GetOptionContract(True)

    '''
    Returns a put option contract with the closest expiration date.
    '''
    def BuyPutOption(self):
        return self.GetOptionContract(False)

    '''
    Returns an option contract based on the specified option type (call or put) and closest expiration date.
    '''
    def GetOptionContract(self, is_call):
        contracts = self.option_chain_provider.get_option_contract_list(self.spy.Symbol, self.Time)
        if is_call:
            contracts = [i for i in contracts if i.ID.OptionRight == OptionRight.Call]
        else:
            contracts = [i for i in contracts if i.ID.OptionRight == OptionRight.Put]

        # Sort contracts by expiration date and select the closest one
        contracts = sorted(contracts, key=lambda x: abs((x.ID.Date - self.Time).days))
        zero_dte_contracts = [i for i in contracts if (i.ID.Date - self.Time).days == 0]
        if zero_dte_contracts:
            return zero_dte_contracts[0]
        else:
            return None