Custom Swap Carry Strategy
Contents
Custom Swap Carry Strategy#
New user? The best way to learn SigTech is to follow the steps in our user guide.
Restricted data: You will only have access to the data used in this notebook if your organisation has specifically purchased it. To check your current data access, view Data. If you would like to access more data, please contact sales@sigtech.com.
Changes will not be saved: Edits made to SigTech’s example notebooks like this will only persist for the duration of your session. When you restart the research environment, this notebook will have been restored to its original state, and any changes you have made will have been lost. To make permanent changes, copy and paste the content to a new notebook in one of your workspaces.
Introduction#
The purpose of this notebook is to show how to create a custom strategy from the base Strategy
class
Environment#
This section imports the relevant internal and external libraries, and sets up the platform environment.
[ ]:
import datetime as dtm
import seaborn as sns
import pandas as pd
import numpy as np
import sigtech.framework as sig
sns.set(rc={'figure.figsize':(18, 6)})
env = sig.init(env_date=dtm.date(2024, 2, 22))
[ ]:
from sigtech.framework.instruments.ir_otc import InterestRateSwap
from sigtech.framework.schedules.schedule import SchedulePeriodic
from sigtech.framework.infra.cal import imm_date
[ ]:
class SwapCarryStrategy(sig.DailyStrategy):
ccy_calendar: str = 'TARGET CALENDAR'
rebalance_frequency: str
dv01_budget: float # DV01 budget (in USD$) to allocate to each leg before weighting by carry/vol """
# List of tuples of (ccy, fwd start, maturity)
all_legs: list[tuple[str, str, str]] = [('EUR', '2Y', '3Y'), ('EUR', '5Y', '5Y')]
irs_group: sig.InterestRateSwapGroup = sig.obj.get('EUR INTEREST RATE SWAP GROUP')
############ UTILITY FUNCTIONS #################################################################
def _get_rebalance_schedule(self) -> list:
""" Gets the rebalance schedule as next data date + rest of schedule """
first_date = self.calendar_schedule().next_data_date(self.start_date)
all_dates = SchedulePeriodic(
self.start_date, self.calculation_end_date(),
self.ccy_calendar,
frequency=self.rebalance_frequency,
).all_data_dates()[1:]
return [first_date] + all_dates
def _get_next_dec_imm_date(self, dt) -> dtm.date:
""" Gets the next December 3rd Wed IMM date given a date """
# Get 1 Dec date for the given year
dec_som = dtm.date(dt.year, 12, 1)
next_dec_imm = imm_date(dec_som, "EUR")
if next_dec_imm < dt.date():
dec_som = dtm.date(dt.year + 1, 12, 1)
return imm_date(dec_som, "EUR")
else:
return next_dec_imm
def _get_1y_carry(self, ccy: str, fwd_start: str, tenor: str, dt: dtm.date) -> float:
"""
Gets the 1-year carry at a given date defined as follows:
> i.e 2y3y rate - 1y3y rate
"""
fwd_start_1 = f"{int(fwd_start[:-1])-1}Y"
r1 = self.irs_group.data_df(tenor=tenor,
fwd_tenor=fwd_start_1,
trade_date=dt,
rate_only=True).squeeze()
r2 = self.irs_group.data_df(tenor=tenor,
fwd_tenor=fwd_start,
trade_date=dt,
rate_only=True).squeeze()
return r1 - r2
#################################################################################################
def strategy_initialization(self, dt):
""" Sets up the strategy, scheduling the future trading actions """
# Schedule all trading actions
for d in self._get_rebalance_schedule():
self.add_method(d, self._trade_processor)
def _trade_processor(self, dt):
""" Main trading function """
# Exit out of existing swaps
for instr, quantity in self.positions.iterate_instruments(dt):
if isinstance(instr, sig.InterestRateSwap):
self.add_position_target(dt, instr.name, 0.0, transaction_type="roll")
position_size = {}
# Get all position_size
for leg in self.all_legs:
_ccy = leg[0]
_fwd = leg[1]
_ten = leg[2]
# Generate sizing
historical_rates = self.irs_group.data_df(tenor=_ten,
fwd_tenor=_fwd,
start_date=dt.date() - dtm.timedelta(days=365),
rate_only=True).squeeze()
# Get leg vol
_vol = (historical_rates[:dt.date()].rolling(252).std() * np.sqrt(252))[-1]
# Get carry
_carry = self._get_1y_carry(_ccy, _fwd, _ten, dt.date())
# Store size as vol-adjusted carry
position_size[leg] = _carry / _vol
# Get start date of all swaps to trade
start_date = self._get_next_dec_imm_date(dt)
# Loop again to trade the individual legs
for leg in self.all_legs:
_ccy = leg[0]
_fwd = leg[1]
_ten = leg[2]
# Calculate final DV01 scaled by weight
size = self.dv01_budget * position_size[leg]
# Trade
self.add_trade(
dt=dt,
instrument_name=InterestRateSwap,
swap_currency=_ccy,
tenor=_ten,
start_date=start_date + pd.Timedelta(f"{int(_fwd[:-1])*365} days"),
units=size,
value_post_start=False,
unit_type='TRADE',
)
[ ]:
strategy = SwapCarryStrategy(
currency='EUR',
start_date=dtm.date(2020, 1, 5),
initial_cash=1e7,
rebalance_frequency='1M',
dv01_budget=1e6,
include_trading_costs=False,
total_return=False,
ccy_calendar = 'TARGET CALENDAR',
all_legs = [('EUR', '2Y', '3Y'), ('EUR', '5Y', '5Y')]
)
strategy.build()
[ ]:
strategy.history().plot();