Continuous Futures Template with Moving Average Crossover Trading
Summary
This example shows a basic algorithm for trading a continuous E-mini S&P 500 futures series. It configures a date range, uses backward-ratio price normalization and last-trading-day contract mapping, and calculates daily fast and slow simple moving averages. The algorithm buys one unit of the currently mapped contract when the fast average exceeds the slow average by a small threshold, and liquidates when the reverse gap appears.
Because a continuous future maps across expiring contracts, the example tracks the active mapped contract and rolls an open position when the mapping changes during exchange hours. It also logs symbol-change and order events. The example is a template rather than a full evaluation: it specifies a limited historical interval but provides no performance results, risk controls, or discussion of how the crossover behaves across different futures markets.
Key ideas
- The algorithm uses a continuous E-mini S&P 500 future with explicit normalization and mapping settings.
- Daily fast and slow simple moving averages determine long entry and liquidation conditions.
- A minimum gap threshold is used to avoid acting on very small crossover differences.
- Open exposure is rolled to the newly mapped contract when the continuous symbol changes.
- The template gives no performance analysis or broader risk management framework.
Tags
Full text
# BasicTemplateContinuousFutureAlgorithm
# BasicTemplateContinuousFutureAlgorithm
Basic template algorithm simply initializes the date range and cash
Basic Continuous Futures Template Algorithm
## Source (Apache-2.0)
```python
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from AlgorithmImports import *
### <summary>
### Basic Continuous Futures Template Algorithm
### </summary>
class BasicTemplateContinuousFutureAlgorithm(QCAlgorithm):
'''Basic template algorithm simply initializes the date range and cash'''
def initialize(self):
'''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
self.set_start_date(2013, 7, 1)
self.set_end_date(2014, 1, 1)
self._continuous_contract = self.add_future(Futures.Indices.SP_500_E_MINI,
data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
data_mapping_mode = DataMappingMode.LAST_TRADING_DAY,
contract_depth_offset = 0)
self._fast = self.sma(self._continuous_contract.symbol, 4, Resolution.DAILY)
self._slow = self.sma(self._continuous_contract.symbol, 10, Resolution.DAILY)
self._current_contract = None
# Minimum SMA gap required before acting on a cross; see the workaround note in on_data.
self._cross_threshold = 0.001
def on_data(self, data):
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the stock data
'''
for changed_event in data.symbol_changed_events.values():
if changed_event.symbol == self._continuous_contract.symbol:
self.log(f"SymbolChanged event: {changed_event}")
# Workaround so the C# and Python versions take the exact same trades on the limited
# sample data in the repository (decimal vs double rounding can disagree at a cross).
if not self.portfolio.invested:
if self._fast.current.value - self._slow.current.value > self._cross_threshold:
self._current_contract = self.securities[self._continuous_contract.mapped]
self.buy(self._current_contract.symbol, 1)
elif self._slow.current.value - self._fast.current.value > self._cross_threshold:
self.liquidate()
# We check exchange hours because the contract mapping can call OnData outside of regular hours.
if self._current_contract is not None and self._current_contract.symbol != self._continuous_contract.mapped and self._continuous_contract.exchange.exchange_open:
self.log(f"{self.time} - rolling position from {self._current_contract.symbol} to {self._continuous_contract.mapped}")
current_position_size = self._current_contract.holdings.quantity
self.liquidate(self._current_contract.symbol)
self.buy(self._continuous_contract.mapped, current_position_size)
self._current_contract = self.securities[self._continuous_contract.mapped]
def on_order_event(self, order_event):
self.debug("Purchased Stock: {0}".format(order_event.symbol))
def on_securities_changed(self, changes):
self.debug(f"{self.time}-{changes}")
```Shown in full with attribution under the source's licence. Licence: Apache-2.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.