Overall Statistics
Total Orders
10
Average Win
7.44%
Average Loss
-7.20%
Compounding Annual Return
0.491%
Drawdown
0.300%
Expectancy
0.016
Start Equity
200000
End Equity
200985
Net Profit
0.492%
Sharpe Ratio
-0.519
Sortino Ratio
-1.007
Probabilistic Sharpe Ratio
25.593%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
1.03
Alpha
-0.003
Beta
0.003
Annual Standard Deviation
0.006
Annual Variance
0
Information Ratio
-0.592
Tracking Error
0.277
Treynor Ratio
-0.864
Total Fees
$0.00
Estimated Strategy Capacity
$18000000.00
Lowest Capacity Asset
SPX XL80P4UFCXOU|SPX 31
Portfolio Turnover
0.14%
from AlgorithmImports import *

class IndexOptionsDataAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2021, 1, 1)
        self.set_cash(200000)
        # Asynchronous can use computational resources efficiently
        self.universe_settings.asynchronous = True
        # Filter to get ATM calls expiring in 180 days to form the Bull Call Spread
        option = self.add_index_option("SPX")
        option.set_filter(lambda u: u.calls_only().strikes(-2, +2).expiration(0, 180))
        self.option_symbol = option.symbol

    def on_data(self, slice: Slice) -> None:
        if not self.portfolio.invested and self.is_market_open(self.option_symbol):
            # Make sure getting the updated VIX option chain
            chain = slice.option_chains.get(self.option_symbol)
            if chain:
                expiry = max([c.expiry for c in chain])
                call_contracts = sorted([c for c in chain if c.expiry == expiry],
                    key=lambda c: c.strike)
                
                # Need 2 contracts to form a call spread
                if len(call_contracts) < 2:
                    return
                
                # Obtain 2 call contracts with different strike price to form the call spread
                longCall, shortCall = call_contracts[0:2]
                
                # Use all the buying power, but need to ensure the order size of the long and short call are the same
                quantity = min([
                    abs(self.calculate_order_quantity(shortCall.symbol, -0.5)),
                    abs(self.calculate_order_quantity(longCall.symbol, 0.5))])
                
                self.market_order(shortCall.symbol, -quantity)
                self.market_order(longCall.symbol, quantity)
                
                expected_margin_usage = max((longCall.strike - shortCall.strike) * self.securities[longCall.symbol].symbol_properties.contract_multiplier * quantity, 0)
                if expected_margin_usage != self.portfolio.total_margin_used:
                    raise Exception("Unexpect margin used!")


    def on_securities_changed(self, changes: SecurityChanges) -> None:
        for security in changes.added_securities:
            if security.type == SecurityType.INDEX_OPTION:
                # Historical data
                history = self.history(security.symbol, 10, Resolution.MINUTE)
                self.debug(f"We got {len(history)} from our history request for {security.symbol}")