Overall Statistics
Total Orders
14
Average Win
7.90%
Average Loss
-2.07%
Compounding Annual Return
20.609%
Drawdown
9.700%
Expectancy
1.754
Start Equity
100000
End Equity
126704.3
Net Profit
26.704%
Sharpe Ratio
0.715
Sortino Ratio
0.571
Probabilistic Sharpe Ratio
53.219%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
3.82
Alpha
0.035
Beta
0.427
Annual Standard Deviation
0.13
Annual Variance
0.017
Information Ratio
-0.312
Tracking Error
0.136
Treynor Ratio
0.218
Total Fees
$50.70
Estimated Strategy Capacity
$0
Lowest Capacity Asset
MSFT YMQUHT7C9AHY|MSFT R735QTJ8XC9X
Portfolio Turnover
0.18%
# Strategy is Buy 1 month ATM call option if you there is a price break out
# and close shortly before expiration


# region imports
from AlgorithmImports import *
# endregion

class HyperActiveYellowGreenLeopard(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 8, 6)
        self.set_end_date(2025, 1, 31)
        self.set_cash(100000)
        equity = self.add_equity("MSFT", Resolution.MINUTE)
        equity.set_data_normalization_mode(DataNormalizationMode.RAW)
        self.equity = equity.symbol
        option = self.add_option("MSFT", Resolution.DAILY)

        #set filter on option data needed
        #first two arguments tell how much below and above the current asset price we want to consider
        # for strike rice, as for the below we want ATM options so we keep compact strike price range
        # next we set dats to expiration between 20 and 40 days
        option.set_filter(-3,3, timedelta(20), timedelta(40))

        #indicator to keep track of high price of underlying over the past month
        #inbuit max helper indicator to used and we specify equity as data source 
        #here we use daily resolution as smaller intra day variations are not that important
        #since we want to track high prices we mention that as the last argument
        self.high = self.max(self.equity, 21, Resolution.DAILY, Field.High)




    def on_data(self, data: Slice):
        if not self.high.is_ready:
            return
        
        #we use portfolio object to create a list of open option positions
        #we save those that are of the type option and invested

        option_invested = [x.key for x in self.portfolio if x.value.invested and x.value.type==SecurityType.OPTION ]
        
        #if option_invested is not empty it means we have an open option position
        # so we check if its about to expire since we dont want to exercise the option
        if option_invested:
            if self.time + timedelta(4) >= option_invested[0].id.date:
                self.liquidate(option_invested[0], "Too close to expiration") 
            return #return if we are already invested and there is more time left
        
        if self.securities[self.equity].price > self.high.current.value: #self.high is a max indicator
            for i in data.option_chains: #we check all available option chain and pass to method BuyCall
                chains = i.value
                self.BuyCall(chains) #BuyCall is a udf
    
    #finds the right call option and buys it
    def BuyCall(self, chains):

        #we first filter by expiration dates and take the futhest expiration date available
        expiry = sorted(chains, key = lambda x: x.Expiry, reverse = True)[0].Expiry
        #we are not taking any put option
        calls = [i for i in chains if i.Expiry == expiry and i.right== OptionRight.CALL]
        #we choose the option closest to the underlying price
        calls_contracts = sorted(calls, key = lambda x: abs(x.strike - x.underlying_last_price))

        if len(calls_contracts)==0:
            return
        self.call = calls_contracts[0]
        
        if self.call.ask_price==0:
            return
        quantity = self.portfolio.total_portfolio_value / self.call.ask_price
        quantity = int(0.05 * quantity/100)

        self.buy(self.call.symbol, quantity)

    #if you let an option to expire , quantconnect automatically exercises
    #since we dont want option to exercise because in the strategy we close them early enough
    
    #below is an example if you want to handle order exercise differently
    #you can do so in on_order_event
    # def on_order_event(self, order_event):
    #     order = self.transactions.get_order_by_id(order_event.order_id)
    #     #to check if order event is an exercise , check order type
    #     #in case you don't want any share you can liquidate
    #     if order.type == order_type.option_exercise:
    #         self.liquidate()