Custom sizing for rolling futures

This page shows new SigTech users how to interpret and verify:

  • Futures Rolling Strategy Building Block history output

  • Strategy size

  • Mark-to-market P&L calculation

Overview

  1. Initial cash sizing Strategy

  2. Strategy output reconciliation

  3. Fixed number of Future Contracts Strategy

  4. Generate the performance reports

Environment

Learn more: Environment setup

import sigtech.framework as sig
from sigtech.framework.infra.objects.dtypes import FloatType

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

Initial cash sizing

In this example the strategy will buy initially as many future contracts as possible as set by the initial_cash . We will start with a simple example to buy 100 contracts of Crude Oil Futures.

Note: if no initial_cash is set the strategy assumes that 1000 currency units are available.

# start date of the strategy
start_date = dtm.date(2017, 3, 2)
# contract to enter into (front)
co = sig.obj.get('COK17 COMDTY')
# number of contracts
n_contracts = 100
# price of the future at inception 
price_t = co.history().loc['2017-03-02']
# initial_cash required for this strategy
initial_cash = co.contract_size * n_contracts * price_t
print(initial_cash)

We want to purely look at the mark-to-market P&L isolating trading costs and cash interest accrual.

co_strat = sig.RollingFutureStrategy(
    currency='USD',
    start_date=start_date,
    contract_code='CO',
    contract_sector='COMDTY',
    rolling_rule='front',
    front_offset='-5:-4',
    include_trading_costs=False,  # don't include trading costs
    total_return=False,  # don't include cash accrual
    set_weight_from_initial_cash=True,
   initial_cash=initial_cash 
)
co_strat.history().plot();

The history method returns the Mark-to-Market (MtM) P&L. This can easily be reconciled using price_series() method and calculating the MtM as:

PnL=ContractSize×NContracts×Pricechng\mathrm{PnL} = \mathrm{ContractSize} \times \mathrm{NContracts} \times \mathrm{Price}_{chng}

where:

ContractSize\mathrm{ContractSize} is the notional of the future contract

NContracts\mathrm{NContracts} is the number of contracts held

Pricechng\mathrm{Price}_{chng} is the price difference from t to t+1

price = co_strat.price_series()
print(price.head())
price.plot();
print('MtM\n', initial_cash + (price.diff().head(8) * co.contract_size * n_contracts))
print('----')
print('history\n', co_strat.history()[2:9])

To verify your position and size, use Strategy.plot.portfolio_table.

Note: as we are using previous day price to calculate initial_cash there could be some differences from our calculation and final allocation as the usage is relative to the execution day price. The output below shows 100.785% exposure of our AUM equivalent to 100 contracts.

Since the size is relative to the cash available and the futures price, in the next roll our 100 contracts initial exposure could vary up or down. This is illustrated in the output below. From the available cash the strategy rolls from 100 to 101 trade units, or contracts:

co_strat.plot.portfolio_table(
    'TOP_ORDER_PTS',
    start_dt=start_date, 
    end_dt=start_date+dtm.timedelta(days=30),
    unit_type='TRADE') # shows the portfolio in number of contracts

Fixed number of contracts sizing

The default sizing explained above can be overwritten so that a fixed number of contracts is always traded independent of the initial_cash amount.

The argument fixed_contracts in the RollingFuturesStrategy allows to modify the roll behaviour without affecting any other property.

Comparing the two strategies we can quantify the impact of sizing in our strategies.

co_fixed_sizing = sig.RollingFutureStrategy(
    currency='USD',
    start_date=start_date,
    contract_code='CO',
    contract_sector='COMDTY',
    rolling_rule='front',
    front_offset='-5:-4',
    include_trading_costs=False,   # don't include trading costs
    total_return=False, # don't include cash accrual   
    set_weight_from_initial_cash=True,
    initial_cash=initial_cash,
    fixed_contracts=100.0, # fixed sizing
    
)                                          
pd.DataFrame({
    'CO_fixed': co_fixed_sizing.history(),
    'CO_AUM': co_strat.history()
}).plot(legend=True);

Ensuring the strategy is rolling 100 contracts:

co_fixed_sizing.plot.portfolio_table(
    'TOP_ORDER_PTS',
    start_dt=start_date, 
    end_dt=start_date+dtm.timedelta(days=30),
    unit_type='TRADE'
)
sig.PerformanceReport([co_fixed_sizing, co_strat]).report()

Summary

  • RollingFuturesStrategies can be sized by AUM or a fixed number of contracts.

  • The initial sizing is calculated as ContractSize×NContracts×FuturePrice\mathrm{ContractSize} \times \mathrm{NContracts} \times \mathrm{FuturePrice}.

  • Verifying the MtM is possible and easy.

  • Strategy.plot.portfolio_table can return the portfolio using AUM or contracts as the unit type.

Next steps

Learn more: Futures modelling

Last updated

© 2023 SIG Technologies Limited