Overall Statistics
Total Orders
120
Average Win
11.59%
Average Loss
-9.42%
Compounding Annual Return
35.149%
Drawdown
64.900%
Expectancy
0.161
Start Equity
100000
End Equity
131518.6
Net Profit
31.519%
Sharpe Ratio
0.747
Sortino Ratio
0.69
Probabilistic Sharpe Ratio
33.896%
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
1.23
Alpha
0
Beta
0
Annual Standard Deviation
0.733
Annual Variance
0.537
Information Ratio
0.822
Tracking Error
0.733
Treynor Ratio
0
Total Fees
$3702.40
Estimated Strategy Capacity
$2000.00
Lowest Capacity Asset
SPY YO33EEUKQT46|SPY R735QTJ8XC9X
Portfolio Turnover
11.26%
# region imports
from AlgorithmImports import *
# endregion

class GeekyTanDolphin(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2024, 1, 1)
        self.set_end_date(2024, 12, 31)
        self.set_cash(100000)        
        
        self.universe_settings.asynchronous = True
        option = self.add_option("SPY", Resolution.MINUTE)
        self.symbol = option.symbol
        self.indicator = self.add_data(Signal, "CUSTOM1", Resolution.MINUTE).symbol

        #call_spread(min_days_till_expiry: int, higher_strike_from_atm: float, lower_strike_from_atm: float)
        #Selects two call contracts to form Bull Call Spread or Bear Call Spread Option strategies.
        
        option.set_filter(lambda universe: universe.include_weeklys().call_spread(30, 5))

        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.CASH)

    def on_data(self, data: Slice):
        # Get the OptionChain
        chain = data.option_chains.get(self.symbol, None)

        if self.indicator in data:            
            received_signal = data[self.indicator].value
            
            if (not self.portfolio.invested and received_signal == 1):    
                

                #self.log(str(self.time) + str(' ') + str(received_signal) +str(' ')+ str(len(chain)))
                
                if not chain:
                    self.log(str(self.time) + str(' ') + str('No chanin available'))
                    return
                
                # Select the call Option contracts with the furthest expiry
                expiry = max([x.expiry for x in chain])
                calls = [i for i in chain if i.expiry == expiry and i.right == OptionRight.CALL]
                if calls == 0:
                    self.log(str(self.time) + str(' ') + str('No chanin available'))
                    return
            
                # Select the ITM and OTM contract strike prices from the remaining contracts
                strikes = [x.strike for x in calls]
                otm_strike = max(strikes)
                itm_strike = min(strikes)       
                
                
                self.log("Buy Order" + str(self.time) + str('  ') + str(received_signal))

                itm_call = [x for x in calls if x.strike == itm_strike][0]
                otm_call = [x for x in calls if x.strike == otm_strike][0]

                quantity_itm = self.portfolio.total_portfolio_value*0.5 / itm_call.ask_price
                quantity_itm = int(quantity_itm/100)

                quantity_otm = self.portfolio.total_portfolio_value*0.5 / otm_call.ask_price
                quantity_otm = int(quantity_otm/100)

                quantity = min(quantity_otm, quantity_itm)

                option_strategy = OptionStrategies.bull_call_spread(self.symbol, itm_strike, otm_strike, expiry)
                
                self.buy(option_strategy, quantity)



                # legs = [
                #         Leg.create(itm_call.symbol, quantity_itm),
                #         Leg.create(otm_call.symbol, -quantity_otm)
                # ]
                # self.combo_market_order(legs, 1)

            
            elif self.portfolio.invested and received_signal == 0:
                option_invested = [x.key for x in self.portfolio if x.value.invested and x.value.type==SecurityType.OPTION ]
                
                self.liquidate()
                self.log("Sell Order" + str(self.time) + str('  ') + str(received_signal))
            
            else:
                # self.set_holdings(self.spy, 0)
                self.log("Do Nothing " + str(self.time) + str('  ') + str(received_signal))

class Signal(PythonData):   

    def get_source(self, config, date, isLive):
        source = "https://www.dropbox.com/scl/fi/mynxp0hpeexq2dfjeiset/ml_output_old.csv?rlkey=fd2jghurd7pgzraw8zpnw4fyf&st=yqtpi1fd&dl=1"
        return SubscriptionDataSource(source, SubscriptionTransportMedium.REMOTE_FILE)
    
    def reader(self, config, line, date, isLive):        
        if not (line.strip() and line[0].isdigit()):                
            return None
         
        csv = line.split(',')        
        xyz = Signal()        
        
        xyz.symbol = config.symbol
        xyz.time = datetime.strptime(csv[0], '%Y-%m-%d %H:%M:%S') 
                  
        
        xyz.value = int(csv[1])   
        return xyz