Basket strategy#
In the SigTech platform, a basket strategy serves as a general framework for managing various types of assets or sub-strategies.
It can be used to trade multiple instruments, rebalancing them on a fixed schedule. These instruments can be assets (such as stocks, ETFs, indices, commodities, futures, options, and bonds) or other strategies; you can even trade basket strategies inside another basket strategy.
Initialize SigTech
Import Python libraries:
import datetime as dtm
import numpy as np
import pandas as pd
import sigtech.framework as sig
Initialize the SigTech framework environment:
env = sig.init()
Learn more
Create a basket strategy#
Basket strategies can trade assets, strategies, or both.
For convenience, first define two helper functions with some constants for currency and start and end dates.
CURRENCY = "USD"
START_DATE = dtm.datetime(2023, 1, 1)
END_DATE = dtm.datetime(2024, 1, 1)
Define a helper function that creates basket strategies:
def create_basket_strat(
currency=None,
start_date=None,
end_date=None,
**params,
):
currency = currency or CURRENCY
start_date = start_date or START_DATE
end_date = end_date or END_DATE
strat = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
**params,
)
print(strat)
return strat
Define a helper function that creates rolling future strategies:
def create_rf_strat(
currency=None,
start_date=None,
end_date=None,
**params,
):
currency = currency or CURRENCY
start_date = start_date or START_DATE
end_date = end_date or END_DATE
return sig.RollingFutureStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
**params,
)
Create a basket strategy of assets#
Create a basket strategy that trades two assets (frozen orange juice futures and coffee futures).
First, retrieve the futures contract instruments to trade:
jo_ct = sig.obj.get("JO COMDTY FUTURES GROUP").active_contract()
kc_ct = sig.obj.get("KC COMDTY FUTURES GROUP").active_contract()
Then, use the create_basket_strat
helper function to create a basket
strategy that trades the futures contract instruments:
jo_kc_asset_basket = create_basket_strat(
constituent_names=[jo_ct.name, kc_ct.name],
weights=[0.5, 0.5],
rebalance_frequency="EOM",
ticker="JO KC ASSET BASKET",
)
jo_kc_asset_basket.history().tail()
In the code cell above:
The
constituent_names
parameter is a list of instrument object names.The
weights
parameter is a list of target weightings for the constituents specified inconstituent_names
.By default, constituent weights are percentages of the strategy’s total funds, expressed as a fraction of 100. For example, to specify constituent weights of 25%, 25%, and 50% for a strategy with three constituents, set
weights=[0.25, 0.25, 0.50]
.The
rebalance_frequency
parameter specifies when the basket strategy will rebalance its allocated funds between its constituents, using the weightings specified inweights
as targets.
View the strategy’s holdings:
jo_kc_asset_basket.plot.portfolio_table(
dts="TOP_ORDER_PTS",
)
Create a basket strategy of strategies#
Create a basket strategy that trades two rolling future strategies (one that trades frozen orange juice futures and one that trades coffee futures).
First, use the create_rf_strat
helper function to create the rolling future
strategies:
jo_rfs = create_rf_strat(contract_code="JO", contract_sector="COMDTY")
kc_rfs = create_rf_strat(contract_code="KC", contract_sector="COMDTY")
Then, use the create_basket_strat
helper function to create a basket
strategy that trades the rolling future strategies:
jo_kc_rfs_basket = create_basket_strat(
constituent_names=[jo_rfs.name, kc_rfs.name],
weights=[0.5, 0.5],
rebalance_frequency="EOM",
ticker="JO KC RFS BASKET",
)
jo_kc_rfs_basket.history().tail()
Create a basket strategy of assets and strategies#
Create a basket strategy that trades two assets and two rolling future strategies (one each for frozen orange juice futures and coffee futures):
jo_kc_asset_rfs_basket = create_basket_strat(
constituent_names=[jo_ct.name, kc_ct.name, jo_rfs.name, kc_rfs.name],
weights=[0.25, 0.25, 0.25, 0.25],
rebalance_frequency="EOM",
ticker="JO KC ASSET RFS BASKET",
)
jo_kc_asset_rfs_basket.history().tail()
Weight the constituents in a basket strategy#
When you create a basket strategy, you supply a list of constituent names
(constituent_names
), and a corresponding list of target weights (weights
).
By default, SigTech interprets the weights
parameter as a list of
percentages as fractions of 100. On a schedule defined by the
rebalance_frequency
parameter, the basket strategy rebalances its allocated
funds between constituents, aiming to match the specified weights as closely
as possible.
Use units to weight a basket strategy#
Instead of rebalancing the constituents of a basket strategy using targeted percentages, you can use a list of the number of units of each constituent to trade for constituents, weights, you can rebalance to a list of how many of each thing units such as a targeted number of shares or contracts.
To do this, use the unit_type
parameter.
Create a basket strategy that buys one JO contract and sells one KC contract:
futures_basket_units = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
constituent_names=[jo_rfs.name, kc_rfs.name],
weights=[1, -1],
rebalance_frequency="SOM",
unit_type="TRADE",
)
futures_basket_units.history().tail()
Examine the holdings:
futures_basket_units.plot.portfolio_table(
dts="TOP_ORDER_PTS",
unit_type="TRADE",
)
Use precise weights and instant sizing#
In the previous examples, the targeted weights sometimes don’t match up with the position weights actually traded in the strategies. This is because, by default, the number of units to trade is calculated using the previous day’s closing price, simulating the experience of a portfolio manager with access to EOD prices.
To achieve the exact weights specified in weights
:
Set
instant_sizing
toTrue
to ensure orders are executed on the day they are generated.Set
ignore_transaction_costs
toTrue
, since transaction costs can impact weighting.Set the sizing time and the execution time to agree.
Set the shared action time to be the exchange close.
jo_ct = sig.obj.get("JO COMDTY FUTURES GROUP").active_contract()
sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
constituent_names=[jo_rfs.name, kc_rfs.name],
weights=[1, -1],
rebalance_frequency="SOM",
unit_type="TRADE",
instant_sizing=True,
include_trading_costs=False,
size_time_input=dtm.time(16, 30),
execution_time_input=dtm.time(16, 30),
size_timezone_input=jo_ct.exchange().timezone,
execution_timezone_input=jo_ct.exchange().timezone
).history().tail()
Examine the positions:
futures_basket_instant.plot.portfolio_table(
dts="TOP_ORDER_PTS",
end_dt=end_date,
)
Rebalance the constituents a basket strategy#
Basket strategies rebalance their constituents at specified intervals using
the weights
parameter, which is a list of target weightings corresponding to
the instruments referenced by constituent_names
.
To control when a basket strategy rebalances, specify rebalance_frequency
, a
frequency string, when you create the strategy.
Create a basket strategy that rebalances at the end of the month ("EOM"
):
jo_kc_basket = sig.BasketStrategy(
currency="USD",
start_date=start_date,
end_date=end_date,
constituent_names=[jo_ct.name, kc_ct.name],
weights=[0.5, 0.5],
rebalance_frequency="EOM",
)
jo_kc_basket.history().tail()
Here are some example frequency strings:
Frequency string |
Description |
---|---|
|
End of the month |
|
Start of the month |
|
Every year |
|
Quarterly, using the IMM dates |
|
Every third business day |
|
On the 15th day of the month |
|
Every week, on Thursdays |
|
Every second month |
|
Every six months, on the 28th |
List the static frequency strings:
freqs = sig.BasketStrategy.AVAILABLE_REBALANCE_FREQS
frequencies = [f for f in dir(freqs) if not f.startswith("_")]
frequency_strings = [
eval(f"freqs.{f}") for f in frequencies if f == f.upper()
]
frequency_strings
List the methods for getting more granular frequency strings:
frequency_methods = [f for f in frequencies if f == f.lower()]
frequency_methods
Get some example frequency strings:
print(freqs.business_days(3))
print(freqs.day_of_month(15))
print(freqs.thursdays(1))
print(freqs.months(2))
print(freqs.day_of_month(28, 6))
Select a business day convention#
If a rebalancing date falls on a holiday, SigTech uses business day
conventions to select the actual date to perform the rebalance. To select a
business day convention to use for rebalancing dates that fall on holidays,
specify rebalance_bdc
, a business day convention string, when you create the
strategy.
Reschedule to the following business day if the original rebalance date is a holiday:
jo_kc_basket = sig.BasketStrategy(
currency="USD",
start_date=start_date,
end_date=end_date,
constituent_names=[
jo_ct.name,
kc_ct.name,
],
weights=[0.5, 0.5],
rebalance_frequency="EOM",
rebalance_bdc="FOLLOWING",
)
jo_kc_basket.history().tail()
List all of the business day convention strings:
bdc_strings = [
eval(f"sig.calendar.{const}")
for const in dir(sig.calendar)
if const.startswith("BDC")
]
bdc_strings
All of the business day convention strings and what they do:
Business day convention string |
Description |
---|---|
|
Following business day |
|
Preceding business day |
|
Original rebalance date |
|
Modified following business day |
|
Modified preceding business day |
The “modified” business day conventions ("MOD_FOL"
and "MOD_PRE"
) ensure
that the strategy is rebalanced in the same month in which the rebalance was
originally scheduled.
When you set
rebalance_bdc
to"MOD_FOL"
, a rebalance that falls on a holiday is performed on the preceding business day if the following business day is in the next month. Otherwise, the rebalance is performed on the following business day.Similarly, when you set
rebalance_bdc
to"MOD_PRE"
, a rebalance that falls on a holiday is performed on the following business day if the preceding business day is in the next month. Otherwise, the rebalance is performed on the preceding business day.
Create a custom rebalancing schedule#
To create a custom rebalancing schedule, use the rebalance_dates
parameter.
The rebalance_dates
parameter lets you supply a list of dates to override
the rebalance dates scheduled by the rebalance_frequency
parameter.
Create a list of rebalancing dates:
cl = sig.obj.get("CL COMDTY FUTURES GROUP").active_contract()
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]
Create a basket strategy using the custom rebalancing dates:
cl_active = sig.obj.get("JO COMDTY FUTURES GROUP").active_contract()
_basket_of_assets = sig.BasketStrategy(
currency="USD",
start_date=start_date,
end_date=end_date,
constituent_names=[jo_ct.name, kc_ct.name],
weights=[0.5, 0.5],
rebalance_frequency="EOM",
rebalance_dates=schedule,
)
_basket_of_assets.history().tail()
Example basket strategies#
Trade front- and back-month rolling future strategies#
Create a basket strategy that combines two rolling future strategies trading crude oil. One trades the front-month contract; the other trades the back-month contract.
rf_front = sig.RollingFutureStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
contract_code="CL",
contract_sector="COMDTY",
rolling_rule="front",
front_offset="-4:-2",
contract_offset=0,
)
rf_back = sig.RollingFutureStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
contract_code="CL",
contract_sector="COMDTY",
rolling_rule="front",
front_offset="-4:-2",
contract_offset=1,
)
Compare the holdings for the two rolling future strategies:
rf_front.plot.portfolio_table(
dts="TOP_ORDER_PTS",
end_dt=end_date,
)
rf_back.plot.portfolio_table(
dts="TOP_ORDER_PTS",
end_dt=end_date,
)
Create a dollar-neutral basket that goes long the back-month version of the rolling future strategy, and short the front-month version, rebalancing at the end of each month:
term_basket = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
constituent_names=[rf_front.name, rf_back.name],
weights=[-0.5, 0.5],
rebalance_frequency="EOM",
)
term_basket.history().tail()
Trade another basket#
Create a basket strategy that trades the basket strategy in the previous example, and another rolling future strategy.
The same rolling rules as above are used with a contract_offset
of zero
(the front quarterly contract is held).
rfs = sig.RollingFutureStrategy(
currency=currency,
start_date=start_date,
contract_code="VG",
contract_sector="INDEX",
rolling_rule="front",
front_offset="-4:-2",
contract_offset=0,
)
Use symmetric difference to compare the business day calendars of the two strategies:
set(rfs.history().index).symmetric_difference(
set(term_basket.history().index))
Since the calendars of the two strategies aren’t the same, use the
intersecting_business_days
parameter to create a calendar using only the
trading dates that appear in both:
baskets_basket = sig.BasketStrategy(
currency=currency,
start_date=start_date,
end_date=end_date,
constituent_names=[term_basket.name, rfs.name],
weights=[0.5, 0.5],
rebalance_frequency="2DOM",
intersecting_business_days=True,
)
baskets_basket.history().tail()
Click Expand All
in the following tree plot to view a visualization of the
hierarchy of the basket and its constituents:
baskets_basket.plot.tree(dtm.datetime(2023, 6, 1))
CashBasketStrategy#
The CashBasketStrategy
is an extension of the BasketStrategy
that enables the trading of foreign exchange (FX) spot instruments.
It focuses on cash equivalents in currency pairs, using parameters tailored for FX requirements and implementing specific logic for handling currency exposure and related risks.
Input:
sig.CashBasketStrategy?
This example creates 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)
)
SizeVaryingBasket#
The SizeVaryingBasket
building block is a basket strategy with the
additional functionality to support the handling of fund subscriptions and
redemptions.
Input:
from sigtech.framework.strategies.basket_strategies import SizeVaryingBasketStrategy
SizeVaryingBasketStrategy?
The following example uses the front- and back-month CL rolling futures strategies from above, together with a dictionary of subscriptions (“subs”) and redemptions (“reds”). It uses the option to trigger rebalancing at a subscription or redemption event. 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();
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()