Overall Statistics
Total Trades
688
Average Win
22.79%
Average Loss
-0.16%
Compounding Annual Return
15.420%
Drawdown
22.800%
Expectancy
8.238
Net Profit
765.726%
Sharpe Ratio
0.296
Probabilistic Sharpe Ratio
0.000%
Loss Rate
94%
Win Rate
6%
Profit-Loss Ratio
143.03
Alpha
0.239
Beta
-0.06
Annual Standard Deviation
0.793
Annual Variance
0.629
Information Ratio
0.191
Tracking Error
0.813
Treynor Ratio
-3.897
Total Fees
$69941.00
Estimated Strategy Capacity
$55000000.00
Lowest Capacity Asset
SPY Y5PEZIKI6JYE|SPY R735QTJ8XC9X
# region imports
from AlgorithmImports import *
# endregion
from hmmlearn import hmm
from statsmodels.tsa.regime_switching.markov_regression import MarkovRegression
from scipy.stats import multivariate_normal
import numpy as np



class HMMandManualEqFutureOptionPortfolio(QCAlgorithm):

    def Initialize(self):
        # Option contracts in the portfolio, SPY in this case:
        self.option_eq = self.AddEquity("SPY", Resolution.Minute)
        self.option_eq.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.option_symbol = self.option_eq.Symbol

        self.put_contract = None
        self.call_contract = None

      
        self.init_money = 100000
        self.SetCash(self.init_money)
        
        self.timetoexpire = 3
        # Options take profit
        self.tp = 0.2
        # backtest period
        self.SetStartDate(2008, 1, 12)

        self.SetBenchmark("SPY")
        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)

        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("SPY", 60), self.portfolioOptimization)

        # checking options
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=60)), self.checkOptions)


    def OnData(self, data):
        pass
        

    def portfolioOptimization(self):

        w_put = 0.002
        w_call = 0.002


        portfolio = []

        if w_put != 0:
            if self.put_contract is None or not self.Portfolio[self.put_contract].Invested:
                self.put_contract = self.GetContractPut(self.option_symbol, 0.8, 30, 45)
                if self.put_contract is not None:
                    portfolio.append(PortfolioTarget(self.put_contract, w_put))

        if w_call != 0:
            if self.call_contract is None or not self.Portfolio[self.call_contract].Invested:
                self.call_contract = self.GetContractCall(self.option_symbol, 1.2, 30, 45)
                if self.call_contract is not None:
                    portfolio.append(PortfolioTarget(self.call_contract, w_call))


        self.SetHoldings(portfolio)


    def GetContractPut(self, symbol, percentage=0.8, minExpiry=30, maxExpiry=60):
        targetStrike = self.Securities[symbol].Price * percentage
        error = self.Securities[symbol].Price * 0.05
        contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)

        if not contracts:
            return None
        puts = [x for x in contracts if x.ID.OptionRight == OptionRight.Put]
        puts = sorted(puts, key=lambda x: x.ID.Date)
        puts = [x for x in puts if abs(x.ID.StrikePrice - targetStrike) <= error]

        puts = [x for x in puts if minExpiry < (x.ID.Date - self.Time).days <= maxExpiry]

        if len(puts) == 0:
            return None
        self.AddOptionContract(puts[0], Resolution.Minute, fillDataForward=False, extendedMarketHours=False)
        self.Error(str(self.Time) + " " + str(puts[0].ID.StrikePrice / self.Securities[symbol].Price) + " " + str(
            (puts[0].ID.Date - self.Time).days))
        return puts[0]

    def GetContractCall(self, symbol, percentage=1.2, minExpiry=30, maxExpiry=60):
        targetStrike = self.Securities[symbol].Price * percentage
        error = self.Securities[symbol].Price * 0.05
        contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
        calls = [x for x in contracts if x.ID.OptionRight == OptionRight.Call]
        calls = sorted(calls, key=lambda x: x.ID.Date)

        calls = [x for x in calls if abs(x.ID.StrikePrice - targetStrike) <= error]

        calls = [x for x in calls if minExpiry < (x.ID.Date - self.Time).days <= maxExpiry]
        if len(calls) == 0:
            return None
        self.AddOptionContract(calls[0], Resolution.Minute, fillDataForward=False, extendedMarketHours=False)
        self.Error(str(self.Time) + " " + str(calls[0].ID.StrikePrice / self.Securities[symbol].Price) + " " + str(
            (calls[0].ID.Date - self.Time).days))
        return calls[0]

    def checkOptions(self):
        now = self.Time
        exchange = self.Securities["SPY"].Exchange
        if not exchange.ExchangeOpen:
            return

        slice = self.CurrentSlice

        if self.put_contract is not None and self.Portfolio[self.put_contract].Invested:
            p = self.Portfolio[self.put_contract].TotalCloseProfit()
            q = self.Portfolio[self.put_contract].Quantity
            price = self.Portfolio[self.put_contract].Price
            strike_price = self.put_contract.ID.StrikePrice
            price_underlying = self.Portfolio[self.option_symbol].Price
            init_price = self.Portfolio[self.put_contract].AveragePrice

            if slice.ContainsKey(self.put_contract) and slice[self.put_contract] is not None and not slice[
                self.put_contract].IsFillForward:

                if self.Portfolio[
                    self.put_contract].TotalCloseProfit() / self.Portfolio.TotalPortfolioValue > self.tp:  

                    self.SetHoldings(self.put_contract, 0)

                    self.put_contract = None
                    return

                if ((self.put_contract.ID.Date - now).days <= self.timetoexpire):
                    self.SetHoldings(self.put_contract, 0)

                    self.put_contract = None

        if self.call_contract is not None and self.Portfolio[self.call_contract].Invested:
            strike_price = self.call_contract.ID.StrikePrice
            price_underlying = self.Portfolio[self.option_symbol].Price
            if slice.ContainsKey(self.call_contract) and slice[self.call_contract] is not None and not slice[
                self.call_contract].IsFillForward:

                if self.Portfolio[
                    self.call_contract].TotalCloseProfit() / self.Portfolio.TotalPortfolioValue > self.tp:  

                    self.SetHoldings(self.call_contract, 0)

                    self.call_contract = None
                    return
                if ((self.call_contract.ID.Date - now).days <= self.timetoexpire):
                    self.SetHoldings(self.call_contract, 0)

                    self.call_contract = None