Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-5.908
Tracking Error
0.136
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
# from datetime import timedelta
# from QuantConnect import *
# from QuantConnect.Algorithm.Framework.Selection import FutureUniverseSelectionModel
# from QuantConnect.Data.UniverseSelection import *
# from QuantConnect.Securities.Future import FutureFilterUniverse
# from QuantConnect.Securities import MarketHoursDatabase

from AlgorithmImports import * 

class OpenInterestFutureUniverseSelectionModel(FutureUniverseSelectionModel):
    """
    This model selects futures contracts based on open interest. It extends the FutureUniverseSelectionModel
    and applies an additional filter to sort the contracts by open interest.

    Attributes:
        algorithm (QCAlgorithm): The algorithm instance using this selection model.
        future_chain_symbol_selector (Callable[[DateTime], List[Symbol]]): Function to select futures symbols.
        _chain_contracts_lookup_limit (int): The maximum number of contracts to inspect for open interest.
        _results_limit (int): The maximum number of contracts to return in the universe.
        _market_hours_database (MarketHoursDatabase): Provides exchange hours needed for history requests.
    """

    def __init__(self, algorithm, future_chain_symbol_selector, chain_contracts_lookup_limit=6, results_limit=1):
        """
        Creates a new instance of the model.

        Args:
            algorithm (QCAlgorithm): The algorithm instance.
            future_chain_symbol_selector (Callable[[DateTime], List[Symbol]]): Selector function for futures symbols.
            chain_contracts_lookup_limit (int): Maximum number of contracts to query for open interest.
            results_limit (int): Maximum number of contracts that will be part of the universe.
        """
        super().__init__(timedelta(days=1), future_chain_symbol_selector)
        self.algorithm = algorithm
        self._chain_contracts_lookup_limit = chain_contracts_lookup_limit
        self._results_limit = results_limit
        self._market_hours_database = MarketHoursDatabase.FromDataFolder()

    def Filter(self, filter: FutureFilterUniverse) -> FutureFilterUniverse:
        """
        Overrides the base method to filter futures contracts based on open interest.

        Args:
            filter (FutureFilterUniverse): The future filter universe to apply the filtering.

        Returns:
            FutureFilterUniverse: The filtered universe of futures contracts.
        """
        contracts = filter.ToDictionary(lambda x: x.Symbol, lambda x: self._market_hours_database.GetEntry(x.Symbol.ID.Market, x.Symbol, x.Symbol.ID.SecurityType))
        return filter.Contracts(self.FilterByOpenInterest(contracts))

    def FilterByOpenInterest(self, contracts):
        """
        Filters the provided contracts by open interest.

        Args:
            contracts (Dictionary[Symbol, MarketHoursDatabase.Entry]): Contracts to filter.

        Returns:
            List[Symbol]: The list of symbols sorted by open interest and date.
        """
        sorted_contracts = sorted(contracts.keys(), key=lambda x: x.ID.Date)
        if self._chain_contracts_lookup_limit is not None:
            sorted_contracts = sorted_contracts[:self._chain_contracts_lookup_limit]
        
        open_interest_data = self.GetOpenInterest(contracts, sorted_contracts)
        filtered_contracts = sorted(open_interest_data.items(), key=lambda x: (-x[1], x[0].ID.Date))

        if self._results_limit is not None:
            filtered_contracts = filtered_contracts[:self._results_limit]
        
        return [symbol for symbol, _ in filtered_contracts]

    def GetOpenInterest(self, contracts, symbols):
        """
        Retrieves and processes historical open interest data for the given symbols.

        Args:
            contracts (Dictionary[Symbol, MarketHoursDatabase.Entry]): Contracts for which to retrieve open interest.
            symbols (List[Symbol]): The list of symbols to get historical data for.

        Returns:
            Dictionary[Symbol, float]: A dictionary mapping each symbol to its latest open interest value.
        """
        current_utc_time = self.algorithm.UtcTime
        start_time = current_utc_time - timedelta(days=1)
        
        # Request historical open interest data
        history = self.algorithm.History(symbols, start_time, current_utc_time, Resolution.Daily)
        open_interest_data = {}

        for symbol in symbols:
            try:
                # Access the open interest data for the symbol
                symbol_data = history.get(symbol)
                if symbol_data.empty:
                    continue
                open_interest_data[symbol] = symbol_data['openinterest'][-1]
            except KeyError:
                # If data is missing, log a message and skip the symbol
                self.algorithm.Debug(f'No open interest data for {symbol.Value}')
        
        return open_interest_data
from AlgorithmImports import *

# Define a custom universe selection model that inherits from OpenInterestFutureUniverseSelectionModel.
# This model will select futures contracts based on open interest.
class CustomFutureUniverseSelectionModel(OpenInterestFutureUniverseSelectionModel):
    """
    A universe selection model that selects futures contracts based on open interest
    and inherits the behavior of the OpenInterestFutureUniverseSelectionModel.
    """
    def __init__(self, algorithm, chainContractsLookupLimit=6, resultsLimit=1):
        """
        Initialize the custom universe selection model with the algorithm instance and limits for contracts lookup.

        :param algorithm: The algorithm instance.
        :param chainContractsLookupLimit: The maximum number of contracts to consider based on open interest.
        :param resultsLimit: The maximum number of contracts to include in the universe.
        """
        super().__init__(algorithm, self.select_future_chain_symbols, chainContractsLookupLimit, resultsLimit)

    def select_future_chain_symbols(self, utcTime: datetime) -> List[Symbol]:
        """
        Selects the future symbols to be included in the universe.

        :param utcTime: The current UTC time as a datetime object.
        :return: A list of Symbols representing the selected futures contracts.
        """
        return [
            Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME),
            Symbol.Create(Futures.Metals.GOLD, SecurityType.Future, Market.COMEX)
        ]


from AlgorithmImports import * 
from Universe import CustomFutureUniverseSelectionModel

# Main algorithm class that uses the CustomFutureUniverseSelectionModel for universe selection.
class OpenInterestFutureUniverseSelectionAlgorithm(QCAlgorithm):
    """
    This algorithm uses a custom future universe selection model to select futures contracts based on open interest.
    It logs the contract data and rollover events for analysis.
    """
    def Initialize(self):
        """
        Initialize the algorithm settings, including start date, cash, universe settings, and universe selection model.
        """
        self.SetStartDate(2023, 1, 1)
        self.SetEndDate(2023,2,1)  # Start date for the backtest
        self.SetCash(100000)           # Starting cash for the algorithm

        # Set properties of the universe settings
        self.UniverseSettings.Resolution = Resolution.MINUTE
        self.UniverseSettings.Leverage = 1.0
        self.UniverseSettings.FillForward = True
        self.UniverseSettings.ExtendedMarketHours = True
        self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw

        # Add the custom universe selection model to the algorithm
        self.AddUniverseSelection(CustomFutureUniverseSelectionModel(self))

        # Keep track of active contracts to detect rollovers
        self.previous_active_contracts = set()

    def OnData(self, slice: Slice):
        """
        Event handler for new data.
        
        :param slice: Slice object containing the data for the current time step.
        """
        # Log the current time slice
        self.Log(f"OnData for time slice: {self.Time}")

        # Process data for each futures chain in the slice
        if slice.FutureChains:
            for chain in slice.FutureChains:
                # Log the underlying Symbol for the current futures chain
                self.Log(f"Processing future chain for underlying: {chain.Key.Value}")

                # Iterate through contracts in the current chain
                for contract in chain.Value:
                    # Log contract details including price and open interest
                    self.Log(f"Contract: {contract.Symbol.Value} Price: {contract.LastPrice} OpenInterest: {contract.OpenInterest}")

                    # Detect and log new active contracts based on open interest
                    if contract.Symbol not in self.previous_active_contracts and contract.OpenInterest > 0:
                        self.Log(f"New active contract detected based on open interest: {contract.Symbol.Value}")
                        # Update the active contracts list
                        self.previous_active_contracts.clear()
                        self.previous_active_contracts.add(contract.Symbol)

    def OnSecuritiesChanged(self, changes: SecurityChanges):
        """
        Event handler for changes in the securities in the universe, such as additions and removals.
        
        :param changes: SecurityChanges object containing lists of added and removed securities.
        """
        # Log securities that have been added to the universe
        for security in changes.AddedSecurities:
            self.Log(f"Added security: {security.Symbol.Value}")

        # Log securities that have been removed from the universe
        for security in changes.RemovedSecurities:
            self.Log(f"Removed security: {security.Symbol.Value}")
from AlgorithmImports import *

class OpenInterestFutureUniverseSelectionAlgorithm(QCAlgorithm):
    """
    Algorithm using the OpenInterestFutureUniverseSelectionModel to select futures contracts based on open interest
    and prints out data and rollover events.
    """
    def Initialize(self):
        self.SetStartDate(2023, 1, 1)  # Set the start date
        # self.SetEndDate(2023, 6, 1)    # Set the end date
        self.SetCash(100000)           # Set the starting cash

        # Set UniverseSettings properties as required
        self.UniverseSettings.Resolution = Resolution.Daily 
        self.UniverseSettings.Leverage = 1.0
        self.UniverseSettings.FillForward = True
        self.UniverseSettings.ExtendedMarketHours = True
        self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw

        # Add OpenInterestFutureUniverseSelectionModel with the selector function for the futures chains
        self.AddUniverseSelection(
            OpenInterestFutureUniverseSelectionModel(self, self.FutureChainSymbolSelector, chainContractsLookupLimit=6, resultsLimit=1)
        )

        # Keep track of the active contracts and rollover
        self.previous_active_contracts = set()

    def FutureChainSymbolSelector(self, utc_time: datetime) -> List[Symbol]:
        # This function returns the future symbols to be included in the universe
        return [
            Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME),
            Symbol.Create(Futures.Metals.GOLD, SecurityType.Future, Market.COMEX)
        ]

    def OnData(self, slice: Slice):
        # Log the time of the data slice
        self.Log(f"OnData for time slice: {self.Time}")

        if slice.FutureChains:
            for chain in slice.FutureChains:
                # Use 'chain.Key' to get the underlying Symbol for the futures chain
                self.Log(f"Processing future chain for {chain.Key}")

                for contract in chain.Value:
                    # Log the contract Symbol, Price, and OpenInterest for each contract in the chain
                    self.Log(f"Contract: {contract.Symbol.Value} Price: {contract.LastPrice} OpenInterest: {contract.OpenInterest}")

                    # Check if this is a newly active contract based on open interest
                    if contract.Symbol not in self.previous_active_contracts and contract.OpenInterest > 0:
                        # Log the new active contract
                        self.Log(f"New active contract detected based on open interest: {contract.Symbol.Value}")
                        # Clear the previous contracts and add this contract as the currently active one
                        self.previous_active_contracts.clear()
                        self.previous_active_contracts.add(contract.Symbol)

    def OnSecuritiesChanged(self, changes: SecurityChanges):
        # This method will be called when the active future contract changes, i.e., a rollover event
        for security in changes.AddedSecurities:
            self.Log(f"Added security: {security.Symbol.Value}")

        for security in changes.RemovedSecurities:
            self.Log(f"Removed security: {security.Symbol.Value}")