Overall Statistics
Total Orders
21
Average Win
1.44%
Average Loss
0%
Compounding Annual Return
20.529%
Drawdown
3.400%
Expectancy
0
Start Equity
1000000
End Equity
1205697.18
Net Profit
20.570%
Sharpe Ratio
3.343
Sortino Ratio
2.115
Probabilistic Sharpe Ratio
97.494%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0.078
Beta
0.208
Annual Standard Deviation
0.04
Annual Variance
0.002
Information Ratio
-1.176
Tracking Error
0.115
Treynor Ratio
0.642
Total Fees
$271.05
Estimated Strategy Capacity
$510000.00
Lowest Capacity Asset
SPY 31NL3I227KPVQ|SPY R735QTJ8XC9X
Portfolio Turnover
0.04%
# region imports
from AlgorithmImports import *
# endregion


class WheelStrategyAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2020, 6, 1)
        self.set_end_date(2021, 6, 1)
        self.set_cash(1_000_000)
        self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
        self._equity = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.Raw)
        self._otm_threshold = 0.05 # 5%
        daily_interest_rate = (1.04) ** (1 / 365) - 1  # 4% annual interest rate.
        self.schedule.on(self.date_rules.every_day(), self.time_rules.midnight, 
            lambda: self.portfolio.cash_book["USD"].add_amount(self.portfolio.cash_book["USD"].amount * daily_interest_rate))
        
    def _get_target_contract(self, right, target_price):
        contract_symbols = self.option_chain_provider.get_option_contract_list(self._equity.symbol, self.time)
        expiry = min([s.id.date for s in contract_symbols if s.id.date.date() > self.time.date() + timedelta(30)])
        filtered_symbols = [
            s for s in contract_symbols 
            if (s.id.date == expiry and s.id.option_right == right and 
                (s.id.strike_price <= target_price if right == OptionRight.PUT else s.id.strike_price >= target_price))
        ]
        symbol = sorted(filtered_symbols, key=lambda s: s.id.strike_price, reverse=right == OptionRight.PUT)[0]
        self.add_option_contract(symbol)
        return symbol

    def on_data(self, data):
        if not self.portfolio.invested and self.is_market_open(self._equity.symbol):
            symbol = self._get_target_contract(OptionRight.PUT, self._equity.price * (1-self._otm_threshold))
            self.set_holdings(symbol, -0.2)
        elif [self._equity.symbol] == [symbol for symbol, holding in self.portfolio.items() if holding.invested]:
            symbol = self._get_target_contract(OptionRight.CALL, self._equity.price * (1+self._otm_threshold))
            self.market_order(symbol, -self._equity.holdings.quantity / 100)