Overall Statistics |
Total Orders 113 Average Win 0.46% Average Loss -0.22% Compounding Annual Return 1.143% Drawdown 3.400% Expectancy 0.202 Start Equity 1000000 End Equity 1026898.42 Net Profit 2.690% Sharpe Ratio -0.353 Sortino Ratio -0.414 Probabilistic Sharpe Ratio 15.943% Loss Rate 61% Win Rate 39% Profit-Loss Ratio 2.06 Alpha -0.003 Beta -0.019 Annual Standard Deviation 0.017 Annual Variance 0 Information Ratio -0.829 Tracking Error 0.203 Treynor Ratio 0.319 Total Fees $279.11 Estimated Strategy Capacity $9400000000.00 Lowest Capacity Asset CL XON0163K9O75 Portfolio Turnover 0.67% |
from AlgorithmImports import * from QuantConnect.DataSource import * class USFuturesSecurityMasterDataClassicAlgorithm(QCAlgorithm): """ This algorithm demonstrates the use of the US futures security master to trade a continuous futures contract based on its price relative to a simple moving average (SMA) and considers market volatility using ATR. It goes long when the price is above the SMA by a certain threshold and goes short when it is below by the same threshold. The algorithm also handles contract rollovers and logs when these events occur. """ threshold = 0.01 # Define a threshold for price deviation from the SMA (1%) def Initialize(self) -> None: self.SetCash(1000000) # Set starting cash self.SetStartDate(2019, 2, 1) # Set start date for the backtest self.SetEndDate(2021, 6, 1) # Set end date for the backtest # Request continuous future data for Crude Oil WTI with specific data normalization and mapping modes. self.continuous_contract = self.AddFuture(Futures.Energies.CrudeOilWTI, DataNormalizationMode.BackwardsRatio, DataMappingMode.OpenInterest, contractDepthOffset=0) self.symbol = self.continuous_contract.Symbol # Get the continuous contract symbol # Initialize SMA and ATR indicators max_lookback = max(110, 20) # Determine the max lookback period for indicator warming self.sma = self.SMA(self.symbol, 110, Resolution.Daily) self.atr = self.ATR(self.symbol, 20, MovingAverageType.Simple, Resolution.Daily) # Request historical data to warm up the indicators history = self.History(self.symbol, max_lookback, Resolution.Daily) if not history.empty: for time, row in history.iterrows(): self.sma.Update(IndicatorDataPoint(time, row.close)) self.atr.Update(IndicatorDataPoint(time, row.close)) def OnData(self, slice: Slice) -> None: mapped_symbol = self.continuous_contract.Mapped # The currently mapped contract symbol # Ensure there is data and that both indicators are ready if not (slice.Bars.ContainsKey(self.symbol) and self.sma.IsReady and self.atr.IsReady): return # Log indicator values self.Log(f"SMA: {self.sma.Current.Value}, ATR: {self.atr.Current.Value}") # Trading logic current_price = slice.Bars[self.symbol].Price if current_price > self.sma.Current.Value * (1 + self.threshold) and not self.Portfolio[mapped_symbol].IsLong: self.MarketOrder(mapped_symbol, 1) elif current_price < self.sma.Current.Value * (1 - self.threshold) and not self.Portfolio[mapped_symbol].IsShort: self.MarketOrder(mapped_symbol, -1) # Handle contract rollovers for symbol, changed_event in slice.SymbolChangedEvents.items(): self.HandleRollover(changed_event) def HandleRollover(self, changed_event): old_symbol = changed_event.OldSymbol new_symbol = changed_event.NewSymbol tag = f"Rollover - Symbol changed at {self.Time}: {old_symbol} -> {new_symbol}" quantity = self.Portfolio[old_symbol].Quantity self.Liquidate(old_symbol, tag=tag) if quantity != 0: self.MarketOrder(new_symbol, quantity, tag=tag) self.Log(tag)
from AlgorithmImports import * from QuantConnect.DataSource import * class USFuturesSecurityMasterDataClassicAlgorithm (QCAlgorithm): """ This algorithm demonstrates the use of the US futures security master to trade a continuous futures contract based on its price relative to a simple moving average (SMA). It goes long when the price is above the SMA by a certain threshold and goes short when the price is below the SMA by the same threshold. The algorithm also handles contract rollovers and logs when these events occur. """ threshold = 0.01 # Define a threshold for price deviation from the SMA (1%) def Initialize(self) -> None: """ Initialize the algorithm settings, add the future contract, and set up the indicators. """ self.SetCash(1000000) # Set starting cash self.SetStartDate(2019, 2, 1) # Set start date for the backtest self.SetEndDate(2021, 6, 1) # Set end date for the backtest # Request continuous future data for Crude Oil WTI with specific data normalization and mapping modes. self.continuous_contract = self.AddFuture(Futures.Energies.CrudeOilWTI, resolution= Resolution.DAILY, dataNormalizationMode=DataNormalizationMode.BackwardsRatio, dataMappingMode=DataMappingMode.OpenInterest, contractDepthOffset=0) self.symbol = self.continuous_contract.Symbol # Get the continuous contract symbol # Request historical data to warm up the SMA indicator history = self.History(self.symbol, 40, Resolution.DAILY) self.Debug(f"We got {len(history)} items from our history request") # Initialize a 10-period SMA indicator with daily resolution self.sma = self.SMA(self.symbol, 20, Resolution.Daily) # Warm up the SMA with historical data if not history.empty: for time, row in history.droplevel(0).loc[self.symbol].iterrows(): self.sma.Update(IndicatorDataPoint(time, row.close)) # Initialize a 20-period ATR Indicator with daily resolution self.atr = self.atr(self.symbol, 20, Resolution.DAILY) self.dch = self.dch(self.symbol, 20, 10) def OnData(self, slice: Slice) -> None: """ Event handler for new data. Checks for rollover events and trading signals based on the SMA. """ # Check for contract rollover events and handle them for symbol, changed_event in slice.SymbolChangedEvents.items(): old_symbol = changed_event.OldSymbol # The symbol of the old contract new_symbol = changed_event.NewSymbol # The symbol of the new contract tag = f"Rollover - Symbol changed at {self.Time}: {old_symbol} -> {new_symbol}" quantity = self.Portfolio[old_symbol].Quantity # Quantity held of the old contract # Liquidate the old contract position and open a new position in the new contract if necessary self.Liquidate(old_symbol, tag=tag) if quantity != 0: self.MarketOrder(new_symbol, quantity, tag=tag) self.Log(tag) mapped_symbol = self.continuous_contract.Mapped # The currently mapped contract symbol # Check if there is a price update for the continuous contract and the SMA is ready if not (slice.Bars.ContainsKey(self.symbol) and self.sma.IsReady and self.atr.IsReady and self.dch and mapped_symbol): return # Update ATR with the new data # bar = slice.Bars[self.symbol] # self.atr.Update(bar) # self.dch.Update(bar) current_price = slice.Bars[self.symbol].Price # The current price of the continuous contract sma_value = self.sma.Current.Value atr_value = self.atr.Current.Value dch_upper_value = self.dch.UpperBand.Current.Value dch_lower_value = self.dch.LowerBand.Current.Value self.log(f"PRICE: {current_price}") self.log(f"SMA VALUE: {sma_value}") self.log(f"ATR VALUE: {atr_value}") self.log(f"DCH UPPER VALUE: {dch_upper_value}") self.log(f"DCH LOWER VALUE: {dch_lower_value}") # Trading logic based on the relationship between the current price and the SMA current_price = slice.Bars[self.symbol].Price # The current price of the continuous contract sma_value = self.sma.Current.Value # The current value of the SMA # If the current price is significantly above the SMA, and we're not already long, go long if current_price > sma_value * (1 + self.threshold) and not self.Portfolio[mapped_symbol].IsLong: self.MarketOrder(mapped_symbol, 1) # If the current price is significantly below the SMA, and we're not already short, go short elif current_price < sma_value * (1 - self.threshold) and not self.Portfolio[mapped_symbol].IsShort: self.MarketOrder(mapped_symbol, -1)
from AlgorithmImports import * class BasicTemplateFutureRolloverAlgorithm(QCAlgorithm): """ An example algorithm demonstrating trading with continuous futures contracts and handling contract rollovers. This class Serves as the core algorithm class that initializes the trading environment, subscribes to futures data, and handles incoming data points for decision making. """ def initialize(self): """ Set up the initial conditions of the algorithm including start/end dates, cash balance, and the futures contracts to trade. Map futures symbols to SymbolData objects for managing trading data. """ self.set_start_date(2013, 1, 1) # Start date of the algorithm self.set_end_date(2015, 1, 1) # End date of the algorithm self.set_cash(1000000) # Starting cash self._symbol_data_by_symbol = {} # Define futures to be traded futures = [Futures.Indices.SP_500_E_MINI ] for future in futures: # Add futures contract with specific settings for resolution and market hours continuous_contract = self.add_future( future, resolution=Resolution.DAILY, extended_market_hours=False, data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO, data_mapping_mode=DataMappingMode.OPEN_INTEREST, contract_depth_offset=0 ) symbol_data = SymbolData(self, continuous_contract) self._symbol_data_by_symbol[continuous_contract.symbol] = symbol_data def on_data(self, slice): """ The primary event handler for incoming market data. This method that is called with every new data point (slice) It processes each symbol's data using SymbolData methods. In this particular alorithm, it makes trading decisions based on the exponential moving average (EMA) and current price. Args: slice: A Slice object containing the current market data. """ for symbol, symbol_data in self._symbol_data_by_symbol.items(): # Update the symbol data with the latest market data slice symbol_data.update(slice) # Skip processing if the symbol data isn't ready or if there's no new data for this symbol if not symbol_data.is_ready or not slice.bars.contains_key(symbol): continue # Get the current value of the exponential moving average (EMA) for this symbol ema_current_value = symbol_data.EMA.current.value # If the EMA is below the current price and we are not already in a long position, # place a buy order to go long on the symbol. if ema_current_value < symbol_data.price and not symbol_data.is_long: self.market_order(symbol_data.mapped, 1) # Conversely, if the EMA is above the current price and we are not already in a short position, # place a sell order to go short on the symbol. elif ema_current_value > symbol_data.price and not symbol_data.is_short: self.market_order(symbol_data.mapped, -1) class SymbolData: """ Handles and encapsulates all necessary data and operations for each single futures symbol within a multi-security algorithm, facilitating the management of state and indicators. """ def __init__(self, algorithm, future): """ Initializes a new instance of SymbolData to manage a specific future. Instantiates a new SymbolData object with a reference to the algorithm and the associated future. Args: algorithm: The instance of the algorithm using this SymbolData. future: The future contract associated with this SymbolData. """ self._algorithm = algorithm self._future = future self.EMA = algorithm.ema(future.symbol, 100, Resolution.DAILY) self.price = 0 self.is_long = False self.is_short = False self.reset() @property def symbol(self): """Returns the symbol of the future.""" return self._future.symbol @property def mapped(self): """Returns the mapped symbol of the future for current trading.""" return self._future.mapped @property def is_ready(self): """Check if the symbol and EMA are ready for trading.""" return self.mapped is not None and self.EMA.is_ready def update(self, slice): """ Updates symbol data with new market data and handles symbol changes due to contract rollovers. Adjusts positions based on the new symbol mapping. Args: slice: A slice of new market data. """ if slice.symbol_changed_events.contains_key(self.symbol): # Check if there is a rollover event for the current symbol in the data slice changed_event = slice.symbol_changed_events[self.symbol] old_symbol = changed_event.old_symbol # Extract the old symbol from the rollover event new_symbol = changed_event.new_symbol # Extract the new symbol to which we are rolling over # Create a tag for logging that includes the time of rollover and the symbols involved tag = f"Rollover - Symbol changed at {self._algorithm.time}: {old_symbol} -> {new_symbol}" # Get the current position quantity for the old symbol to replicate in the new symbol quantity = self._algorithm.portfolio[old_symbol].quantity # Liquidate the existing position in the old symbol self._algorithm.liquidate(old_symbol, tag=tag) # Create a new position in the new symbol with the same quantity as the old symbol self._algorithm.market_order(new_symbol, quantity, tag=tag) # Reset the symbol data to clear any state specific to the old contract self.reset() # Update the current price with the latest price from the slice, or keep the old if no new data self.price = slice.bars[self.symbol].price if slice.bars.contains_key(self.symbol) else self.price # Update the long and short position flags based on the current portfolio state self.is_long = self._algorithm.portfolio[self.mapped].is_long # Check if the current position is long self.is_short = self._algorithm.portfolio[self.mapped].is_short # Check if the current position is short def reset(self): """Resets and warms up indicators for the newly mapped contracts.""" self.EMA.reset() self._algorithm.warm_up_indicator(self.symbol, self.EMA, Resolution.DAILY) def dispose(self): """Frees up resources by resetting the EMA and other indicators.""" self.EMA.reset()