Overall Statistics
Total Orders
18
Average Win
5.10%
Average Loss
-0.71%
Compounding Annual Return
30.783%
Drawdown
12.400%
Expectancy
3.110
Start Equity
1000000
End Equity
1389110.4
Net Profit
38.911%
Sharpe Ratio
0.799
Sortino Ratio
0.382
Probabilistic Sharpe Ratio
46.326%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
7.22
Alpha
0.138
Beta
0.391
Annual Standard Deviation
0.223
Annual Variance
0.05
Information Ratio
0.33
Tracking Error
0.228
Treynor Ratio
0.455
Total Fees
$544.60
Estimated Strategy Capacity
$5000000.00
Lowest Capacity Asset
ES YLZ9Z7LFSJOK|ES YLZ9Z50BJE2P
Portfolio Turnover
20.89%
from AlgorithmImports import *

class FutureOptionExampleAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2023, 7, 1)
        self.set_cash(1000000)
        # Subscribe the underlying since the updated price is needed for filtering
        self.underlying = self.add_future(Futures.Indices.SP_500_E_MINI,
            extended_market_hours=True,
            data_mapping_mode=DataMappingMode.OPEN_INTEREST,
            data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO,
            contract_depth_offset=0)
        # Filter the underlying continuous Futures to narrow the FOP spectrum
        self.underlying.set_filter(0, 182)
        # Filter for the current-week-expiring calls to formulate a covered call that expires at the end of week
        self.add_future_option(self.underlying.symbol, lambda u: u.include_weeklys().calls_only().expiration(0, 5))

    def on_data(self, slice: Slice) -> None:
        # Create canonical symbol for the mapped future contract, since option chains are mapped by canonical symbol
        symbol = Symbol.create_canonical_option(self.underlying.mapped)

        # Get option chain data for the mapped future, as both the underlying and FOP have the highest liquidity among all other contracts
        chain = slice.option_chains.get(symbol)
        if not self.portfolio.invested and chain:
            # Obtain the ATM call that expires at the end of week, such that both underlying and the FOP expires the same time
            expiry = max(x.expiry for x in chain)
            atm_call = sorted([x for x in chain if x.expiry == expiry],
                key=lambda x: abs(x.strike - x.underlying_last_price))[0]

            # Use abstraction method to order a covered call to avoid manual error
            option_strategy = OptionStrategies.covered_call(symbol, atm_call.strike,expiry)
            self.buy(option_strategy, 1)
        
    def on_securities_changed(self, changes: SecurityChanges) -> None:
        for security in changes.added_securities:
            if security.type == SecurityType.FUTURE_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}")