Trade sizing#
This page shows how trade sizing is configured on the SigTech platform.
The framework runs strategies in a model world to give target positions and orders. These need to be scaled and combined with the actual positions held to obtain actual executable trades. This action is performed by a separate trading module.
Learn more: Example notebooks.
Environment #
This section will import the relevant internal and external libraries, as well as setting up the platform environment.
Learn more: Environment setup
import sigtech.framework as sig
from sigtech.framework.internal.strategies.timeline.trading_order import TradingOrder
import datetime as dtm
import pandas as pd
import seaborn as sns
sns.set(rc={'figure.figsize': (18, 6)})
if not sig.config.is_initialised():
sig.config.init()
Strategy example #
The following simple basket consists of a rolling future and rolling FX forwards, going long GBP and short EUR.
def get_fxrf(long_ccy, forward_tenor, direction):
return sig.RollingFXForwardStrategy(
currency='USD',
direction=direction,
forward_tenor=forward_tenor,
long_currency=long_ccy,
start_date=dtm.date(2010, 1, 5),
)
st1 = get_fxrf('GBP', '3M_IMM', 'long')
st2 = get_fxrf('EUR', '3M_IMM', 'short')
rf = sig.RollingFutureStrategy(
currency='USD',
start_date=dtm.date(2010, 1, 4),
contract_code='ES',
contract_sector='INDEX',
rolling_rule='front',
front_offset='-6:-4',
)
st = sig.BasketStrategy(
currency='USD',
start_date=dtm.date(2011, 1, 10),
constituent_names=[
st1.name,
st2.name,
rf.name,
],
weights=[0.25, 0.25, 0.5],
rebalance_frequency='EOM',
)
The full strategy:
Input:
st.history().tail().dropna()
Output:
2021-03-29 2038.968037
2021-03-30 2037.233737
2021-03-31 2043.408612
2021-04-01 2054.245609
Name: (LastPrice, EOD, USD 84B0A0E8 BS STRATEGY), dtype: float64
st.history().plot()
Model trades #
The orders and positions are extracted from the strategy timeline, on the specified trade_date
. These are used to calculate netted positions and an overall target model position:
trade_date = dtm.date(2018, 6, 29)
pos = st.timeline_holdings.positions()
dt = st.decision_dt_from_date(trade_date)
port = st.expanded_slice(dt)
orders_df = pd.DataFrame([TradingOrder(order).info() for order in port.orders])
positions_df = pd.DataFrame(
[(p.name, p.quantity) for p in port.positions],
columns=['name', 'quantity']
)
# net and remove cash entries
positions = positions_df.groupby('name').sum().drop(
['USD CASH', 'ESU18 INDEX VALUATION OFFSET']
)
# net and remove cash entries
positions = positions_df.groupby('name').sum().drop(
['USD CASH', 'ESU18 INDEX MARGIN']
)
# target position
future_pos = (positions + orders)
future_pos
# orders
orders
Exposures #
The notional exposures of the target and orders based on the strategies sizing date:
size_date = st.size_date_from_date(trade_date)
model_target = {}
for name, quantity in future_pos['quantity'].items():
model_target[name] = st.get_exposure_weight(
sig.obj.get(name), quantity, size_date
)
model_target = pd.Series(model_target)
model_diff = {}
for name, quantity in orders['quantity'].items():
model_diff[name] = st.get_exposure_weight(
sig.obj.get(name), quantity, size_date
)
model_diff = pd.Series(model_diff)
Input:
# target model exposures
model_target
Output:
ESU18 INDEX 0.499491
EURUSD 1.187372750628 2018-06-12:2018-09-19 DELIVERABLE NAKED FXFORWARD -0.254798
GBPUSD 1.341317214603 2018-06-12:2018-09-19 DELIVERABLE NAKED FXFORWARD 0.255231
dtype: float64
Input:
# order exposures
model_diff
Output:
ESU18 INDEX -0.000006
EURUSD 1.187372750628 2018-06-12:2018-09-19 DELIVERABLE NAKED FXFORWARD -0.001819
GBPUSD 1.341317214603 2018-06-12:2018-09-19 DELIVERABLE NAKED FXFORWARD 0.003473
dtype: float64
Scaling #
The strategy is valued as of the sizing date, then a scaling factor is applied based on a funds AUM. The AUM is set to $100mn:
aum = 100000000
strategy_valuation = st.valuation_price_base(size_date)
scaling = aum / strategy_valuation
The model future units do not include contract size factors. To obtain the contract numbers the following functions need to be defined:
Note: These functions can then be applied to DataFrames to scale the future entries.
def future_scaling(instrument, qty, size_date):
imnt = sig.obj.get(instrument)
if hasattr(imnt, 'fut_val_pt'):
return round(qty / imnt.fut_val_pt(d=size_date), 0)
else:
return qty
def df_future_scaling(df, size_date):
df_copy = df.copy()
df_copy['quantity'] = [
future_scaling(x, y, size_date)
for x, y in df_copy['quantity'].items()
]
return df_copy
The currently held positions are read in from a portfolio management system. Example data used in the following example:
pms_positions = pd.Series({
'GBPUSD 1.341317214603 2018-06-12:2018-09-19 DELIVERABLE NAKED FXFORWARD': 33000000,
'EURUSD 1.187372750628 2018-06-12:2018-09-19 DELIVERABLE NAKED FXFORWARD': -21500000,
'ESU18 INDEX': 370,
})
The resulting trade table is constructed:
trade_table = pd.concat([
df_future_scaling(future_pos, size_date),
df_future_scaling(orders, size_date),
df_future_scaling(future_pos * scaling, size_date),
df_future_scaling(orders * scaling, size_date),
pms_positions,
df_future_scaling(future_pos * scaling,
size_date).subtract(pms_positions, axis=0)
], axis=1, sort=True)
trade_table.index = [
sig.obj.get(name).trade_name for name in trade_table.index
]
trade_table.columns = [
'Target Strategy Pos', 'Strategy Orders', 'Model Target Pos', 'Model Orders', 'PMS Pos', 'Trade Qty'
]
for col in ['Target Strategy Pos', 'Strategy Orders', 'Model Target Pos', 'Model Orders', 'PMS Pos', 'Trade Qty']:
trade_table[col] = trade_table[col].map('{:,.0f}'.format)
trade_table.index.name = 'Instrument Name'
trade_table
This is formatted and combined with trade flags to give the execution output.
Note: the forward quantities here are not given in USD and the future positions are given in contract number.
An overview of placed FX forward orders:
Another example output follows in the case of a random futures portfolio. In this example the PMS is Murex.
Murex PMS screenshot showing futures orders:
There is a separation of the strategy model world and trading module. The strategy output is run and stored in a database. This output is then read by the trading module and combined with positions, read from the PMS.
The model order quantities can be obtained directly from the strategy using the evaluate_trades
method.
st.inspect.evaluate_trades(dt, aum)