Tutorial: commodity momentum strategy
Beginner tutorial on creating a systematic strategy using futures contracts.
This tutorial shows new SigTech users how to construct a systematic investment strategy where the strategy involves a number of different commodities, and where the investment decision is based on a simple momentum signal.
When creating an investment strategy on the SigTech platform, the workflow tends to follow these steps:
- 1.Set up the environment
- 2.Define the investment universe
- 3.Create the strategy
- 4.Construct the portfolio
- 5.Generate the performance report
Setting up your environment takes three steps:
- Import the relevant internal and external libraries
- Configure the environment parameters
- Initialise the environment
import sigtech.framework as sig
import datetime as dtm
import pandas as pd
import numpy as np
import seaborn as sns
sns.set(rc = {'figure.figsize': (18, 6)})
sig.config.init()
In this step, you will define the futures contracts that will form the universe of the investment strategy. When defining the strategy's futures contracts it's important to use each contract's exact
ContractCode
.Example: the
ContractCode
of corn futures is 'C '
Note: all futures contracts issued by a particular futures exchange, such as CBOT for the same underlying asset, such as corn, will share the same
ContractCode
.# Dictionary of assets and their ContractCode
assets = {
'Corn Futures (C)': 'C ',
'Soybean Futures (S)': 'S ',
'Sugar Futures (SB)': 'SB',
'Cotton Futures (CT)': 'CT',
'Live Cattle Futures (LC)': 'LC',
'Soybean Oil Futures (BO)': 'BO',
'Gas Oil Futures (QS)': 'QS',
'Heating Oil Futures (HO)': 'HO',
}
Define the relevant start and end date for the strategy over which the historical performance will be simulated:
start_date = dtm.date(2000, 1, 8)
end_date = dtm.date(2020, 12, 8)
When the investment universe contains futures contracts, it's of high importance to handle the rolling of futures—since a given futures contract has an expiry date, it's crucial to systematically handle how the strategy trades in and out of futures contracts to maintain an exposure to the underlying asset. The SigTech platform offers the
RollingFutureStrategy
class which allows the user to easily handle the rolling of contracts along a futures strip.In this tutorial we will create eight
RollingFutureStrategy
objects. The function below allows you to easily create these RollingFutureStrategy
objects by just passing in a few parameters.The
RollingFutureStrategy
objects are instantiated using a few different parameters, such as rolling_rule
and contract_sector
. Note: the
RollingFutureStrategy
objects assumes a constant long exposure from start_date
to end_date
.def create_rfs(contract_code, start_date, end_date):
'''Creates RollingFutureStrategy objects and
returns the name of the object.'''
try:
rfs = sig.RollingFutureStrategy(
# The contract_code can be found in the
# Market Data Browser under 'ContractCode'
contract_code=contract_code,
# The contract_sector can be found in the
# Market Data Browser under 'BBGMarketSector'
contract_sector='COMDTY',
# Specify the currency the strategy will be denominated in
currency='USD',
# The behaviour of the rolling between contracts,
# F_0 means adjusted front contract
# See the link specified above for more alternatives and explanations
rolling_rule='F_0',
# Start date of the strategy
start_date=start_date,
# End date of the strategy
end_date=end_date
)
return rfs.name
except:
pass
Using a dictionary comprehension, all of the previously defined instruments will be passed into the function defined above, creating a
RollingFutureStrategy
object for each of the instruments:Input
Output
BASKETS = {
asset: create_rfs(contract_code, start_date, end_date)
for asset, contract_code in assets.items()
}
list(BASKETS.keys())
['Corn Futures (C)',
'Soybean Futures (S)',
'Sugar Futures (SB)',
'Cotton Futures (CT)',
'Live Cattle Futures (LC)',
'Soybean Oil Futures (BO)',
'Gas Oil Futures (QS)',
'Heating Oil Futures (HO)']
The strategy consists of a simple momentum strategy, which in this case will mean that an asset is bought if it has had a positive price movement over a three month period, and be sold if it has had a negative price movement over a three month period.
The function below,
generate_signals_momentum
, will not perform any buying or selling actions. Instead it will generate a pandas DataFrame with a buying or selling signal for each data point with a given timeframe, such as daily, for each RollingFutureStrategy
. The signals will be expressed as +1 or -1 depending on if it is a buying or selling signal.def generate_signals_momentum(assets, days_window=63):
'''Generates a dataframe of signals for a provided list of strategies'''
# List to store signals for all assets
frames = []
# Loop through all provided assets
for asset in assets:
# Get the performance of the RollingFutureStrategy as a time series
ts_data = sig.obj.get(asset).history()
# Convert prices to percentage change
pct_change_ts = ts_data.pct_change()
# Create a rolling mean of the percentage price change over
# a backward looking window of 63 days
pct_change_mean_window_ts = pct_change_ts.rolling(days_window).mean()
# Convert the percentage numbers to +1 or -1 if it's higher or lower
# than zero, i.e. only maintain the sign of the percentage change.
mom_signal = np.sign(pct_change_mean_window_ts)
# Add the signal for this asset to the list of stored signals
frames.append(mom_signal.to_frame(asset))
# Convert the list to a DataFrame and remove all NaN values
all_signals = pd.concat(frames, axis=1).dropna()
return all_signals
In the code block below, all the instruments in the universe get passed in as a list to the function defined above, to convert the performance history of the
RollingFutureStrategy
objects to a pandas DataFrame holding signals. The signals will be +1 or -1 on each date for each asset, which indicates a buying or selling signal in that given asset.signal_mom = generate_signals_momentum(assets=list(BASKETS.values()))
signal_mom.head()

To apply the strategy to the universe and combine all the assets into a portfolio, the
SignalStrategy
class is used. But before we can use the SignalStrategy
class, we need to convert the pandas DataFrame of signals into a signal object:signal_obj = sig.signal_library.from_ts(signal_mom)
Once the signal object is created, a
SignalStrategy
object is created. There are many different parameters and alternatives that the user can make use of, such as different allocation functions between the assets in the strategy. mom_strat = sig.SignalStrategy(
# Start date of strategy
start_date=start_date,
# End date of strategy
end_date=end_date,
# Currency in which the strategy is denominated in
currency='USD',
# Reference to the signal object
signal_name=signal_obj.name,
# The rebalance frequency of the strategy. The strategy will only
# switch positions (buying or selling) of the underlying assets on
# the rebalance dates, e.g. EOM (end-of-month). This means that
# it is only the signals at the end-of-month that is relevant to
# this strategy, and all the signals in between will not have an
# impact on buying/selling of the underlying assets.
rebalance_frequency='EOM',
# Allocation function for the strategy, in this case the allocation function
# normalizes the number of short and long positions, i.e. holds the same number
# of long and short positions at any given time.
allocation_function=sig.signal_library.allocation.normalize_weights,
# The reference name of the strategy
ticker=f'Momentum Strategy'
)
After having created the strategy, the code block below can be used to create a performance report which contains information on:
- Metrics
- Rolling performance charts
- Performance calendar
There are a wide range of different performance views and reports available.
sig.PerformanceReport(mom_strat).report()



Another useful tool is to delve into the different strategy constituents' performance attribution to the overall strategy. The strategy is built up in a layered way, and each layer can be further investigated in terms of performance attribution.
The different layers can be viewed using the
.pnl_breakdown
method, and by changing the parameter levels
. The different levels can be view—
levels=0
is the top level, and levels=1
is the next one.pnl, weight = mom_strat.analytics.pnl_breakdown(levels=1)
pnl.plot();

Last modified 8mo ago