Basket Strategy
A simple building block for creating strategies, whose underlying assets or strategies are rebalanced to fixed weights, on a fixed rebalancing schedule.
This section will import relevant internal and external libraries, as well as setting up the platform environment.
import pandas as pd
import datetime as dtm
import seaborn as sns
sns.set(rc={'figure.figsize': (18, 6)})
import sigtech.framework as sig
end_date = dtm.date(2021, 11, 30)
sig.init(env_date=end_date)
Overview of the BasketStrategy class.
Input
sig.BasketStrategy?
Although it is possible to build custom rebalancing schedules, the majority of common schedules are already supported.
The following
AVAILABLE_REBALANCE_FREQS
object can help with finding the correct string to supply to a BasketStrategy
, and other strategies more generally, to achieve the desired rebalancing behaviour.Input
Output
freqs = sig.BasketStrategy.AVAILABLE_REBALANCE_FREQS
frequencies = [f for f in dir(freqs) if not f.startswith('_')]
frequency_strings = sorted([f for f in frequencies if f == f.upper()])
frequency_strings
['END_OF_MONTH', 'IMM', 'START_OF_MONTH', 'YEARLY']
Any of the attributes above, represented by upper-case strings, yield the correct input string directly:
Input
Output
print(freqs.END_OF_MONTH)
print(freqs.IMM)
print(freqs.START_OF_MONTH)
print(freqs.YEARLY)
EOM
3M-IMM
SOM
Yearly
Input
Output
frequency_methods = sorted([f for f in frequencies if f == f.lower()])
frequency_methods
['business_days',
'day_of_month',
'fridays',
'mondays',
'months',
'thursdays',
'tuesdays',
'wednesdays',
'weeks']
Any of the above attributes are methods that take an input and return the correct string format. The
thursdays
method is an example:Input
Output
freqs.thursdays?
Signature: freqs.thursdays(n=1)
Docstring:
Rebalance every ``n`` weeks on Thursdays.
:param n: Number of weeks, 1 by default.
:return: Rebalance string value.
File: /opt/conda/envs/sig-env/lib/python3.7/site-packages/sigtech/framework/schedules/schedule.py
Type: method
The input
n
is how often, in terms of weeks, to rebalance.So
freqs.thursdays(2)
returns the string to input when a strategy should rebalance every second Thursday.The following cell cycles through some of these methods and shows sample output (the strings to supply as the
rebalancing_frequency
argument):Input
Output
print(freqs.business_days(2))
print(freqs.day_of_month(2))
print(freqs.fridays(2))
print(freqs.thursdays(2))
print(freqs.weeks(2))
2BD
2DOM
2W-FRI
2W-THU
2W
currency = 'USD'
This basket will trade the March 2022 Crude Oil (CL) and Natural Gas (NG) futures contracts.
Fetch objects and combine their histories on a shared calendar.
cl = sig.obj.get('CLH22 COMDTY')
ng = sig.obj.get('NGH22 COMDTY')
prices = pd.DataFrame({'CL': cl.history(), 'NG': ng.history()}).dropna()
prices.head()
.png?alt=media&token=8ef04b45-5994-48d5-86f1-62b3e7a9a711)
The shared history begins in 2016. Examine the volume being traded to identify a realistic start date.
volumes = pd.DataFrame({
'CL': cl.history('Volume'),
'NG': ng.history('Volume')
}).dropna()
volumes.sum(axis=1).plot();

Based on the above, choose a start date from the beginning of 2021.
start_date = dtm.date(2021, 1, 3)
Create a basket that allocates 70% to the CL contract and 40% to the NG contract, rebalancing on Wednesday every three weeks. There is no requirement for weights to sum to 100% and short positions are also possible.
If the scheduled rebalancing day falls on a market holiday, then rebalance on the following business day.
weights = [0.7, 0.4]
# list of names of constituents, not constituent objects
constituent_names = [cl.name, ng.name]
rebalance_frequency = freqs.wednesdays(3) # '3W-WED'
rebalance_bdc = 'FOLLOWING'
futures_basket = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
constituent_names=constituent_names,
weights=weights,
rebalance_frequency=rebalance_frequency,
rebalance_bdc=rebalance_bdc
)
futures_basket.build()
futures_basket.plot.portfolio_table(
dts='TOP_ORDER_PTS',
end_dt=dtm.date(2021, 1, 31)
)

The
BasketStrategy
building block is also able to take strategies as constituents. The following example creates two RollingFutureStrategy
objects trading crude oil, a 'front month' version and a 'back month' version, and then constructs a basket from these two strategies.The relevant information needed to construct a
RollingFutureStrategy
(RFS) is obtained directly from the relevant FuturesContractGroup
. # utilise a longer history
start_date = dtm.date(2018, 1, 3)
cl_group = cl.group()
contract_code = cl_group.contract_code
contract_sector = cl_group.contract_sector
# specifics of how rolling is to be done
rolling_rule = 'front'
front_offset = '-4:-2'
rf_front = sig.RollingFutureStrategy(
currency=currency,
start_date=start_date,
contract_code=contract_code,
contract_sector=contract_sector,
rolling_rule=rolling_rule,
front_offset=front_offset,
# determine which contract to trade
contract_offset=0,
)
rf_front.build()
rf_back = sig.RollingFutureStrategy(
currency=currency,
start_date=start_date,
contract_code=contract_code,
contract_sector=contract_sector,
rolling_rule=rolling_rule,
front_offset=front_offset,
# determine which contract to trade
contract_offset=1,
)
rf_back.build()
Comparison of holdings for the two rolling futures strategies.
As expected, whilst the 'front' version of the strategy is rolling out of Gs and into Hs, the back version is rolling out of Hs and into Js.
rf_front.plot.portfolio_table(
dts='TOP_ORDER_PTS',
end_dt=dtm.date(2018, 1, 31)
)

rf_back.plot.portfolio_table(
dts='TOP_ORDER_PTS',
end_dt=dtm.date(2018, 1, 31)
)

Create a dollar neutral basket that goes long the back month version of the RFS and short the front month version, rebalancing at the end of each month.
The default value of
rebalance_bdc
is taken as no argument is supplied. This results in the preceding business day being used to rebalance if that dictated by the schedule falls on a market holiday.strategy_weights = [-0.5, 0.5]
strategy_constituent_names = [rf_front.name, rf_back.name]
rebalance_frequency = freqs.END_OF_MONTH # 'EOM'
term_basket = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
constituent_names=strategy_constituent_names,
weights=strategy_weights,
rebalance_frequency=rebalance_frequency,
)
term_basket.build()
The history can be fetched and plotted directly by calling
history
or the PlotWrapper
. It can be used to create an interactive performance plot.term_basket.plot.performance()
A basket strategy, like any strategy built within the SigTech platform, is itself a
Strategy,
i.e. it inherits from the sig.Strategy
class. This means it is possible to create a BasketStrategy
that allocates amongst other BasketStrategy
objects.This example creates a basket that allocates between the crude term structure basket built above and a EURO STOXX 50 (VG) Rolling Futures Strategy.
The same rolling rules as above are used with a
contract_offset
of zero (the front quarterly contract is held).group = sig.obj.get('VG INDEX FUTURES GROUP')
rfs = sig.RollingFutureStrategy(
currency=currency,
start_date=start_date,
contract_code=group.contract_code,
contract_sector=group.contract_sector,
rolling_rule=rolling_rule,
front_offset=front_offset,
contract_offset=0,
)
rfs.build()
basket_weights = [0.5, 0.5]
basket_constituent_names = [term_basket.name, rfs.name]
rebalance_frequency = freqs.day_of_month(2) # '2DOM'
The business day calendars of the two strategies do not precisely agree, seen in the symmetric difference of the dates for which they have valuations.
Input
Output
set(rfs.history().index).symmetric_difference(
set(term_basket.history().index))
{Timestamp('2018-05-01 00:00:00'),
Timestamp('2018-05-07 00:00:00'),
Timestamp('2018-05-28 00:00:00'),
Timestamp('2018-08-27 00:00:00'),
Timestamp('2019-05-01 00:00:00'),
Timestamp('2019-05-06 00:00:00'),
Timestamp('2019-05-27 00:00:00'),
Timestamp('2019-08-26 00:00:00'),
Timestamp('2020-05-01 00:00:00'),
Timestamp('2020-05-08 00:00:00'),
Timestamp('2020-05-25 00:00:00'),
Timestamp('2020-08-31 00:00:00'),
Timestamp('2020-12-28 00:00:00'),
Timestamp('2021-05-03 00:00:00'),
Timestamp('2021-05-31 00:00:00'),
Timestamp('2021-08-30 00:00:00')}
The
intersecting_business_days
parameter can be used to ensure that the intersection of the business days used for each strategy is used for the resulting strategy.It is easy to check that this is true for
baskets_basket
.baskets_basket = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
constituent_names=basket_constituent_names,
weights=basket_weights,
rebalance_frequency=rebalance_frequency,
intersecting_business_days=True
)
baskets_basket.build()
Click
Expand All
in the following tree plot to view a visualisation of how the top level basket is trading both a lower level basket and a Rolling Futures Strategy. It also shows the lower level constituents that each of these strategies is holding.baskets_basket.plot.tree(dtm.datetime(2021, 11, 30))
BasketStrategy
admits a parameter called rebalance_dates
, which takes the form of a list of dtm.date
or dtm.datetime
objects and superseded rebalance_frequency
in determining the dates on which the strategy should rebalance.The example below creates a custom schedule using the
SchedulePeriodic
class and then passes this to BasketStrategy
Input
Output
start_date = dtm.date(2018, 7, 2)
_end_date = dtm.date(2019, 12, 31)
weekly_fridays = sig.SchedulePeriodic(
start_date=start_date,
end_date=_end_date,
holidays=cl.exchange().holidays,
frequency=freqs.fridays(1)
).all_data_dates()
schedule = [d for d in weekly_fridays if d.month in [3, 6, 9, 12]]
schedule[:5]
[datetime.date(2018, 9, 7),
datetime.date(2018, 9, 14),
datetime.date(2018, 9, 21),
datetime.date(2018, 9, 28),
datetime.date(2018, 12, 7)]
custom_rebalance_basket = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=_end_date,
weights=[0.9, 0.1],
constituent_names=[rf_front.name, rfs.name],
# provide custom schedule
rebalance_dates=schedule,
# custom schedule supersedes
rebalance_frequency=freqs.END_OF_MONTH
)
custom_rebalance_basket.build()
custom_rebalance_basket.plot.portfolio_table(
dts='TOP_ORDER_PTS',
end_dt=dtm.date(2018, 9, 14),
flatten=True
)
.png?alt=media&token=9033b7e5-1d7d-41c3-b138-2f49f73ea754)
The baskets constructed in the examples above have a list of target weights that they aim to rebalance.
It is also useful to specify the number of units to target, such as shares and contracts.
Note: this is completed with the use of the
unit_type
parameter.Using the futures contracts introduced above, the following example creates a basket that buys one CL contract and sells one NG contract:
futures_basket_units = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
constituent_names=[cl.name, ng.name],
weights=[1, -1],
rebalance_frequency=freqs.START_OF_MONTH,
unit_type='TRADE'
)
futures_basket_units.build()
Note: specifying
unit_type=TRADE,
when viewing the corresponding portfolio_table,
clearly demonstrates the desired behaviour. futures_basket_units.plot.portfolio_table(
dts='TOP_ORDER_PTS',
unit_type='TRADE'
)

In the first futures basket example, the position weights achieved differ slightly from the targets.
Example: the weight accorded to NGH22 COMDTY at 2021/01/05 21:30:00 is 72.001%, whilst the target weight is exactly 70%.
futures_basket.plot.portfolio_table(
dts='TOP_ORDER_PTS',
end_dt=dtm.date(2021, 1, 5)
)

One of the main reasons for this difference is that the number of units to trade, or sizing, is done using the previous day's closing price for both of the futures contracts that underlie the basket. This is to more accurately simulate the experience of a portfolio manager with access to EOD prices.
The
instant_sizing
parameter can be used to execute orders on the same day that they are sized.# basket parameters
weights = [0.7, 0.4]
constituent_names = [cl.name, ng.name]
rebalance_frequency = freqs.wednesdays(3)
rebalance_bdc = 'FOLLOWING'
Useful data on exchange close times and timezones:
Input
Output
cl.exchange().exchange_close
datetime.time(16, 30)
Input
Output
cl.exchange().timezone
'America/New_York'
To achieve the exact weights specified:
- Set
instant_sizing
toTrue
to ensure orders are executed on the day they are generated. - Ignore transaction costs, since the slippage here can alter the resulting weights.
- Set the sizing time and the execution time to agree.
- Although not completely necessary in this example, set this shared action time to be the exchange close.
action_time = dtm.time(16, 30)
timezone = cl.exchange().timezone
futures_basket_instant = sig.BasketStrategy(
currency=currency,
start_date=dtm.date(2021, 1, 3),
end_date=end_date,
constituent_names=constituent_names,
weights=weights,
rebalance_frequency=rebalance_frequency,
rebalance_bdc=rebalance_bdc,
# sizing and trading time parameters
instant_sizing=True,
# remove trading costs to avoid slippage
include_trading_costs=False,
# sizing time and execution time must agree
size_time_input=action_time,
execution_time_input=action_time,
size_timezone_input=timezone,
execution_timezone_input=timezone
)
futures_basket_instant.build()
The precise target weights are now achieved:
futures_basket_instant.plot.portfolio_table(
dts='TOP_ORDER_PTS',
end_dt=dtm.date(2021, 1, 31)
)

The
CashBasketStrategy
is an extension of the BasketStrategy
that enables FX spot to be traded.Input
Output
sig.CashBasketStrategy?
Init signature:
sig.CashBasketStrategy(
raw_data: Dict = None,
identifier: Union[sigtech.framework.infra.data_adapter.identifier.Identifier, NoneType] = None,
cache: bool = True,
db=False,
validate=True,
check_input_parameters=True,
env: Union[sigtech.framework.config.config.ConfiguredEnvironment, NoneType] = None,
**kwargs,
)
Docstring:
Extension of ``BasketStrategy`` that allows trading of FX spot (``xxx CASH`` instruments).
Example of getting equally weighted exposure to GBPEUR and GBPUSD, rebalancing daily:
::
CashBasketStrategy(
currency='GBP',
start_date=datetime.date(2010, 1, 4),
constituent_names=[
'EUR CASH',
'USD CASH',
],
weights=[0.5, 0.5],
rebalance_frequency='1BD',
)
File: /opt/conda/envs/sig-env/lib/python3.7/site-packages/sigtech/framework/strategies/basket_strategies.py
Type: DObjectModelMeta
Subclasses:
Create a basket that is long EURUSD and short GBPUSD.
fx_basket = sig.CashBasketStrategy(
currency='USD',
start_date=dtm.date(2016, 1, 4),
end_date=dtm.date(2017, 1, 4),
constituent_names=['EUR CASH','GBP CASH'],
weights=[0.8, -0.8],
rebalance_frequency=freqs.fridays(1), # '1W-FRI'
)
fx_basket.build()
fx_basket.history().plot()

fx_basket.plot.portfolio_table(
dts='TOP_POSITION_CHG_PTS',
end_dt=dtm.date(2016, 1, 31)
)

The
SizeVaryingBasket
building block is a basket strategy with the additional functionality to support the handling of fund subscriptions and redemptions.Input
Output
from sigtech.framework.strategies.basket_strategies import SizeVaryingBasketStrategy
SizeVaryingBasketStrategy?
Init signature:
SizeVaryingBasketStrategy(
raw_data: Dict = None,
identifier: Union[sigtech.framework.infra.data_adapter.identifier.Identifier, NoneType] = None,
cache: bool = True,
db=False,
validate=True,
check_input_parameters=True,
env: Union[sigtech.framework.config.config.ConfiguredEnvironment, NoneType] = None,
**kwargs,
)
Docstring:
Extension of ``BasketStrategy`` that a series of cash flows to and from the strategy.
The flows are defined in a dictionary ``subs_reds_data``.
If ``use_subs_reds_to_rebalance`` is True the strategy will rebalance the day following any flows.
Example of getting equally weighted exposure, rebalancing daily and after two flows:
::
SizeVaryingBasketStrategy(
currency='USD',
start_date=datetime.date(2010, 1, 6),
constituent_names=[
'USD ES INDEX LONG FRONT RF STRATEGY',
'EUR VG INDEX LONG FRONT RF STRATEGY'
],
weights=[0.5, 0.5],
rebalance_frequency='EOM',
subs_reds_data={
datetime.date(2010, 2, 10): 2000,
datetime.date(2010, 3, 10): -2000,
},
use_subs_reds_to_rebalance=True,
)
File: /opt/conda/envs/sig-env/lib/python3.7/site-packages/sigtech/framework/strategies/basket_strategies.py
Type: DObjectModelMeta
Subclasses:
This example uses the front and back month CL rolling futures strategies from above, together with a dictionary of 'subs' and 'reds'. There is an option to trigger rebalancing at a subscription or redemption event, which is used in this case. The
initial_cash
is also altered. sv_basket = SizeVaryingBasketStrategy(
currency=currency,
start_date=start_date,
constituent_names=[rf_front.name, rf_back.name],
weights=[0.5, 0.5],
rebalance_frequency=freqs.END_OF_MONTH,
# subs/reds parameters
initial_cash=500,
subs_reds_data={
dtm.date(2019, 2, 10): 200,
dtm.date(2019, 4, 17): -400,
dtm.date(2021, 2, 3): 300,
},
use_subs_reds_to_rebalance=True,
)
sv_basket.build()
sv_basket.history().plot();
.png?alt=media&token=c0947555-fe72-4950-a85d-99061a641fc2)
In addition to the usual
portfolio_table
, the timeline
method of a strategy's PlotWrapper
is an excellent way to get an overview of the details of a strategy.Selecting the period 01/02/2021 - 15/02/2021, displaying 'POSITIONS' and selecting all boxes except 'Flatten' clearly displays the cash inflow and the associated
_add_cash
method being called.Note: clicking on the top level strategy displays the substrategies.
sv_basket.plot.timeline()
Last modified 4mo ago