I have played with the examples in the documentation and I see how to get IV values of a contract within the OnData function. But in this current test project of allocating portfolio based on iv each month, I'm getting 0 returned for every contract. For some reason it is not calculating the IV. The indicator is either None or not Ready, but I don't know why. And neither does Mia.
 

from AlgorithmImports import *

class MonthlyOptionsAllocation(QCAlgorithm):
    def Initialize(self):
        # Set start and end dates for backtesting
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 1)
        self.SetCash(100000)
        self.SetWarmUp(timedelta(days=30)) # for IV indicator calculation
        

        # List of stock tickers
        self.tickers = [
            "AAPL", "MSFT", "GOOG", "AMZN", "TSLA", "META", 
            "NFLX", "NVDA", "BABA", "AMD", "SPY", "QQQ", 
            "INTC", "XOM", "BA", "JPM", "V", "MA", "DIS", "COST"
        ]

        # Add equities and their options
        self.symbols = []
        for ticker in self.tickers:
            equity = self.add_equity(ticker, Resolution.DAILY).Symbol
            option = self.add_option(ticker, Resolution.MINUTE).Symbol
            self.symbols.append((equity, option))
            self._iv = self.iv(self.option)

        # Schedule monthly portfolio rebalancing
        self.schedule.on(
            self.date_rules.month_start(self.tickers[0]), 
            self.time_rules.at(10, 0), 
            self.rebalance_portfolio
        )

        # Keep track of performance metrics
        self.sharpe_ratios = {}

    def rebalance_portfolio(self):
        selected_symbols = []
        for equity, option in self.symbols:
            history = self.history(equity, 252, Resolution.DAILY)
            if len(history) < 252:
                continue
            daily_returns = history['close'].pct_change().dropna()
            if daily_returns.std() == 0:
                sharpe_ratio = 0
            else:
                sharpe_ratio = (252 ** 0.5) * daily_returns.mean() / daily_returns.std()
            self.sharpe_ratios[equity] = sharpe_ratio
            if sharpe_ratio > 0:
                selected_symbols.append(option)
        
        # Allocate capital based on IV
        self.set_holdings_based_on_iv(selected_symbols)
 

    def set_holdings_based_on_iv(self, selected_symbols):
        """Allocate weights to selected tickers based on IV."""
        total_iv = 0
        ivs = {}

        for option_symbol in selected_symbols:
            chain = self.option_chain_provider.get_option_contract_list(option_symbol, self.Time)
            if not chain:
                self.debug(f"No chain found for: {option_symbol}")
                continue

            # Filter for monthly contracts expiring in the current month
            monthly_contracts = [
                c for c in chain 
                if c.ID.Date.month == self.Time.month and c.ID.Date.year == self.Time.year
            ]
            if not monthly_contracts:
                self.debug(f"No monthly contracts: {option_symbol}")
                continue

            # Select the ATM contract
            underlying_price = self.Securities[option_symbol.Underlying].Price
            atm_contract = min(
                monthly_contracts, 
                key=lambda x: abs(x.ID.StrikePrice - underlying_price), 
                default=None
            )

            if atm_contract is None:
                self.debug(f"No ATM contract found for: {option_symbol}")
                continue

            # Add the contract and calculate IV
            self.AddOptionContract(atm_contract, Resolution.Minute)
            iv_indicator = self.iv(atm_contract, option_model=OptionPricingModelType.BLACK_SCHOLES)
            if iv_indicator is None or not iv_indicator.IsReady:
                self.debug(f"IV indicator not ready for contract: {atm_contract}")
                continue

            iv = iv_indicator.Current.Value
            if iv <= 0:
                self.debug(f"Invalid IV value for contract: {atm_contract}")
                continue

            self.debug(f"Contract {atm_contract} IV: {iv}")
            ivs[option_symbol] = iv
            total_iv += iv

        # Allocate capital proportionally based on IV
        if total_iv > 0:
            for option_symbol, iv in ivs.items():
                weight = iv / total_iv
                self.SetHoldings(option_symbol.Underlying, weight)
        else:
            self.debug("Total implied volatility is zero, no holdings set.")


    def OnData(self, data):
        """Handles incoming data."""
        pass