Overall Statistics
Total Orders
108
Average Win
25.12%
Average Loss
-12.59%
Compounding Annual Return
92.487%
Drawdown
56.900%
Expectancy
0.387
Start Equity
100000
End Equity
181421
Net Profit
81.421%
Sharpe Ratio
1.753
Sortino Ratio
1.959
Probabilistic Sharpe Ratio
46.153%
Loss Rate
54%
Win Rate
46%
Profit-Loss Ratio
2.00
Alpha
0
Beta
0
Annual Standard Deviation
1.316
Annual Variance
1.732
Information Ratio
1.795
Tracking Error
1.316
Treynor Ratio
0
Total Fees
$7111.00
Estimated Strategy Capacity
$1000.00
Lowest Capacity Asset
SPY 32N73JUPZ2XD2|SPY R735QTJ8XC9X
Portfolio Turnover
13.57%
# region imports
from AlgorithmImports import *
# endregion

class FatRedOrangeBarracuda(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)
        option.set_slippage_model(VolumeShareSlippageModel())
        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().strangle(30, 5, -10))

        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 chain:
                self.log(str(self.time) + str(' ') + str('No chanin available'))
                return
            
            if (not self.portfolio.invested and received_signal == 1):    
                # Find options with the farthest expiry
                expiry = max([x.expiry for x in chain])
                contracts = [contract for contract in chain if contract.expiry == expiry]
                
                # Order the OTM calls by strike to find the nearest to ATM
                call_contracts = sorted([contract for contract in contracts
                    if contract.right == OptionRight.CALL and
                        contract.strike > chain.underlying.price],
                    key=lambda x: x.Strike)
                if not call_contracts:
                    return
                    
                # Order the OTM puts by strike to find the nearest to ATM
                put_contracts = sorted([contract for contract in contracts
                    if contract.right == OptionRight.PUT and
                    contract.strike < chain.underlying.price],
                    key=lambda x: x.Strike, reverse=True)
                if not put_contracts:
                    return

                call = call_contracts[0]
                put = put_contracts[0]

                quantity_call = self.portfolio.total_portfolio_value*0.5 / call.ask_price
                quantity_call = int(quantity_call/100)

                quantity_put = self.portfolio.total_portfolio_value*0.5 / put.ask_price
                quantity_put = int(quantity_put/100)

                quantity = min(quantity_call, quantity_put)

                long_strangle = OptionStrategies.strangle(self.symbol, call.strike, put.strike, expiry)
                self.buy(long_strangle, quantity)
            
            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"
        source = "https://www.dropbox.com/scl/fi/qofw9or2nmn1d64pgtm51/ml_output_update_mom.csv?rlkey=36thclybj9d1b4gmx0uxp2j95&e=1&st=oxb6ejtw&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