| 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()