Overall Statistics
Total Orders
11
Average Win
0.15%
Average Loss
0%
Compounding Annual Return
16.665%
Drawdown
2.800%
Expectancy
0
Start Equity
100000
End Equity
108017.84
Net Profit
8.018%
Sharpe Ratio
1.324
Sortino Ratio
1.616
Probabilistic Sharpe Ratio
88.489%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
-0.026
Beta
0.498
Annual Standard Deviation
0.044
Annual Variance
0.002
Information Ratio
-2.485
Tracking Error
0.045
Treynor Ratio
0.118
Total Fees
$1.00
Estimated Strategy Capacity
$110000000.00
Lowest Capacity Asset
SPXW 32JQ5BIPGEQ7I|SPX 31
Portfolio Turnover
0.26%
# region imports
from AlgorithmImports import *
# endregion

class StrategyTwoSellPutAndHedge(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2024, 1, 1)
        self.set_end_date(2024, 7, 1)
        self.set_cash(100000)

        self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))

        # Adds the SPXW options. 
        # The (-10,10, 30, 30) filter means we will get 10 strikes below and above ATM
        # which should include the 10-delta put options, 30 days out.
        spx = self.add_index("SPX").symbol
        self.index_option_symbol = Symbol.create_canonical_option(spx, "SPXW", Market.USA, "?SPXW")


        # We will hedge the short puts with SPY
        self.hedge = self.add_equity("SPY").symbol

        # We will check for trades every day, so we open new trades after the option expires
        self.schedule.on(self.date_rules.month_start(spx), self.time_rules.after_market_open(spx, 1), self.trade)
    
    def trade(self):
        # Get all the contracts available for the day
        option_contract_list = self.option_chain_provider.get_option_contract_list(self.index_option_symbol, self.time)

        # Filter the OTM puts with expiry to one month
        expiry = min(option_contract_list, key=lambda symbol: abs((symbol.id.date - self.time).days - 30)).id.date
        underlying_price = self.securities[self.index_option_symbol.underlying].price
        puts = filter(lambda symbol: symbol.id.option_right == OptionRight.PUT and symbol.id.date == expiry and symbol.id.strike_price < underlying_price, option_contract_list)

        deltas = {}
        risk_free_rate = self.risk_free_interest_rate_model.GetInterestRate(self.time)

        for put in puts:
            call = Symbol.create_option(put.underlying, Market.USA, put.id.option_style, OptionRight.CALL, put.id.strike_price, expiry)
            delta = Delta(put, risk_free_rate, 0, call)
            history = self.history[TradeBar]([put, call], 10, Resolution.DAILY)
            for bars in history:
                for symbol, bar in bars.items():
                    delta.update(IndicatorDataPoint(symbol, bar.end_time, bar.close))
            deltas[put] = delta.current.value

        # Get the put closest to 10
        put = sorted(deltas.items(), key=lambda x: abs(x[1]+.10))[0][0]

        # Sell the put and buy the hedge if we don' have it
        self.add_option_contract(put)
        self.market_order(put, -1, tag=f'Delta={deltas[put]:.3f}')
        if not self.portfolio[self.hedge].invested:
            self.market_order(self.hedge, 100)

        # Remove the subcriptions we don«'t need
        for symbol, _ in deltas.items():
            if symbol == put:
                continue
            self.remove_option_contract(symbol)