v8 Framework
Search…
⌃K

Transaction cost

This page shows how transactions costs can be altered.
When running strategies, transactions costs are not considered by default. You can include transactions costs by running the following code.
import sigtech.framework as sig
env = sig.init()
env[sig.config.IGNORE_T_COSTS] = False
There are default transaction costs defined either on the TradePricer class or on the instrument class. These can be overridden when the environment is initialised.
A transaction cost model can be set by its instrument classname, its instrument group or its individual ticker. If the transaction model is not defined in the register, it defaults to the model defined on the instrument class.
The parameters used in the transaction cost can be updated or a new transaction model with new models can be passed.
The overrides passed are a dictionary with the following keys:
  • Class name
  • Group ticker
  • Instrument ticker
The values of the dictionary are a tuple with the first value being the name of the transaction model to use and the second any parameters the model might use.
Specifically, we can assign transaction cost models to instruments based on the following criteria:
  • Class name, such as SingleStock, Future or CommodityFuture.
  • Group ticker, such asES COMDTY FUTURES GROUP or US GOVT BOND GROUP.
  • Instrument ticker, such as 1000045.SINGLE_STOCK.TRADABLE, ESH18 COMDTY or US 2.5 2006/10/31 GOVT.
Note: the above criteria is ordered by ascending specificity of scope.
Learn more: Example notebooks

Environment

This section will import relevant internal and external libraries, as well as setting up the platform environment.
Learn more: Environment setup
import sigtech.framework as sig
import pytz
import datetime as dtm
if not sig.config.is_initialised():
sig.init()
sig.config.set(sig.config.HISTORY_SCHEDULE_BUILD_FROM_DATA, True)
# Here is where the transaction costs overrides are set
sig.config.set(sig.config.T_COST_OVERRIDES, {
'TestEquity': ('fixed_cost', 2),
'US GOVT BOND GROUP': ('yield_transaction_type', None),
'TEST_2 EQUITY': ('foo', {'long_multiple': 1.2, 'short_multiple': 0.95}),
'EURUSD CURNCY': ('spread_from_mid', {'spread': 0.0005}),
'EURUSD OTC OPTION GROUP': ('option_default', None),
'GBPUSD OTC OPTION GROUP': ('option_vol_spread', 0.001),
})

Transaction cost models available by default

A number of basic transaction cost models are available on the platform—they need to be passed calibrated parameters to function.

Background

Some glossary terms:
  1. 1.
    Cost versus arrival price (
    CC
    ): represents the cost incurred on a trade, expressed as a percentage of price. Example: if
    C=0.002C = 0.002
    and bid, or ask, price is £ 35.22, then cost incurred would be ca. £ 0.07.
  2. 2.
    Participation rate (
    rr
    ):
    QV\frac{Q}{V}
    where
    QQ
    is the order size, in units, and
    VV
    is a measure of the average daily volume, in units.
    rr
    is the proportion of the daily volume that is constituted by this trade.
Cost of transacting (
CC
) is typically characterised as containing three parts:
Note: trade cost (
CC
) = instant impact + temporary impact + permanent impact
  • Instant impact: cost incurred immediately, such as crossing the bid/offer spread or incurring slippage.
  • Temporary impact: adverse market price movement during the execution of the trade.
  • Permanent impact: difference in market price before and after the trade.
    The temporary and permanent impacts are sometimes grouped together under the market impact.

Fixed commission

Applies a fixed commission to the trade, irrespective of quantity traded or price of instrument:
sig.config.set(sig.config.T_COST_OVERRIDES, {
'Instrument_scope_here': ('fixed_commission', 6.5),
})
Result: applies a fixed 6.50 units of cost to the trade, in the currency of the trade.

Fixed cost (fixed spread from mid)

Applies a fixed cost from the mid of the instrument:
sig.config.set(sig.config.T_COST_OVERRIDES, {
'Instrument_scope_here': ('fixed_cost', 2),
})
Result: applies a cost of 2 units of currency from the mid of the instrument.

Fixed relative cost (percentage spread from mid)

Applies a fixed cost relative to the mid of the instrument. 0.01 would equate to a charge of 1% of mid.
C=kC = k
sig.config.set(sig.config.T_COST_OVERRIDES, {
'Instrument_scope_here': ('fixed_relative_cost', 0.002),
})
Result: applies a cost of 20 bps spread from mid to the trade.

Square-root market impact model

Cost is represented as only a market impact, expressed as:
C=cσrαC = c \sigma r^\alpha
Where
σ\sigma
is the daily volatility of returns and where
cc
and
aa
are some constants of
O(1)\mathcal{O}(1)
. Sotiropoulos et. al (2018) suggest
c0.35c ≈ 0.35
and
a0.40a ≈ 0.40
for the U.S. equity markets.
This model can also be thought of as a spread varying with volatility (the 𝑐𝜎cσ term) and proportional to the participation rate raised to some power (the
rαr^\alpha
term).
Usage 1:
sig.config.set(sig.config.T_COST_OVERRIDES, {
'Instrument_scope_here': ('market_impact_square_root', (0.35, 0.40)),
})
Result: applies a cost of
cσrαc \sigma r^\alpha
where
c=0.35c = 0.35
and
a=0.40a = 0.40
.
Usage 2:
sig.config.set(sig.config.T_COST_OVERRIDES, {
'Instrument_scope_here': ('market_impact_square_root', (0.35, 0.40, 0.012)),
})
Result: applies a cost of
cσrαc \sigma r^\alpha
, where
c=0.35c = 0.35
,
a=0.40a = 0.40
and daily volatility
σ\sigma
is set to 1.2%.

Combined model (square-root market impact plus spread relative to mid)

Cost is represented as a combination of a spread and a market impact:
C=k+cσrαC = k + c \sigma r^\alpha
Usage:
sig.config.set(sig.config.T_COST_OVERRIDES, {
'Instrument_scope_here': ('combined_spread_market_impact', (0.004, 0.33, 0.41)),
})
Result: applies a cost of
k+cσrαk + c \sigma r^\alpha
where
k=0.004k = 0.004
,
c=0.33c = 0.33
and
a=0.41a = 0.41
. This would be equivalent to a 40 bps spread plus a square-root market impact type cost with the given values for
cc
and
aa
.

Building your own transaction cost model

Clients often have detailed and specific transaction cost data and models and wish to integrate these into the platform. The SigTech platform's flexible transaction cost API makes this as simple as building a method that takes inputs and returns a final trade price.

Anatomy of a transaction cost model

When a trade is to be executed, the platform goes through the following steps:
  1. 1.
    Using the instrument name, look in T_COST_OVERRIDES for a given method to run and parameters to pass to mentioned method. If none found, fall back to the default set of methods.
  2. 2.
    Run the method with the parameters stored in T_COST_OVERRIDES.
  3. 3.
    Inside method: execute instructions inside method, having access to all information stored in the parent class (more information below).
  4. 4.
    Inside method: return the price
    pp
    at which the trade will be executed.
  5. 5.
    Book trade at price
    pp
    .
A client's custom transaction cost model would need to be called by this method and make use of the inputs given by the parent class.

Creating a transaction cost model

Creating a transaction cost model is as simple as defining a new method and attaching this method to either the TradePricer, for non-FX instruments, or FxPricer, for FX instruments:
def my_transaction_cost_model(self):
""" A custom transaction cost model that multiplies the price of an instrument by a certain factor. """
# Gets gross price
base_price = self.instrument.valuation_price_base(self.d)
# Depending on whether we are long or short, return different multiples
if self.quantity >= 0:
return base_price * self.params['multiple_long']
else:
return base_price * self.params['multiple_short']
sig.TradePricer.my_transaction_cost_model = my_transaction_cost_model
The custom transaction cost model is saved and can be set in the sig.config.T_COST_OVERRIDES:
sig.config.set(sig.config.T_COST_OVERRIDES, {
'Instrument_scope_here': ('my_transaction_cost_model', {'multiple_long': 1.1, 'multiple_short': 1/1.1}),
})
The model takes one parameter, namely multiple, which is passed in through a dictionary.

Inputs: information available to a custom transaction cost model

A custom transaction cost model can make use of any of the properties existing in the parent class, TradePricer:
  • Instrument name to trade
  • datetime of trade
  • Quantity to trade
  • Currency of trade
  • Transaction type, such as outright and roll.
Tip: to see a full list, view the source code for TradePricer by querying sig.TradePricer??.

FX-specific transaction costs

The FX spot and forward transaction costs can be customised using the same T_COST_OVERRIDES dictionary.
Due to how the FX time series is cached in the platform, the transaction costs applied to FX instruments are done so through the FxPricer class, rather than the TradePricer.
Note: transaction costs set in this way act on the entire history rather than one time point, which is the main difference.
Note: only spread_from_mid is currently supported.

Set by currency cross

sig.config.set(sig.config.T_COST_OVERRIDES, {
'EURUSD CURNCY': ('spread_from_mid_relative', {'spread': 0.0005}),
})
This sets the EURUSD pair to use the spread_from_mid transaction model passing the parameter 0.0005. This results in a 5 bps, or 0.05%, transaction cost (from mid) being applied.
Input
Output
sig.TradePricer.spread_from_mid??
Signature: sig.TradePricer.spread_from_mid(self, t_cost=None)
Docstring:
Price computed as mid price plus/minus spread.
:param t_cost: Transaction cost.
File: ~/framework/framework/sigtech/framework/transaction_cost/transaction_cost.py
Type: function

Use case: custom option transaction cost model

The default behaviour is to take the bid/ask from the market vol surface data. The following is an example of doing the same with an override and how to apply a custom spread to the vol.

Adding the transaction cost methods

Two new transaction cost methods are added to the TradePricer in the following example:
Note: once they are added they can then be used as an override in the environment configuration.
def option_default(self):
""" Take bid/ask from the volsurface data - this is the default behaviour"""
print('-- Running option_default cost retrieval for {} unit --'.format(str(self.quantity)))
option = self.instrument
if self.quantity >= 0:
return option._trade_price(self.dt, currency=self.currency, mode='ask')
else:
return option._trade_price(self.dt, currency=self.currency, mode='bid')
def option_vol_spread(self):
print('-- Running option_vol_spread cost retrieval for {} unit --'.format(str(self.quantity)))
option = self.instrument
grp = option.group()
trade_date = self.d
# Find the mid vol for the option
mid_vol = grp.vol_from_strike(
option.option_type,
option.strike,
trade_date,
option.maturity_date,
mode='mid'
)
# Apply a spread to the vol based on the direction to trade
if self.quantity >= 0:
vol = mid_vol + self.params
mode = 'ASK'
else:
vol = mid_vol - self.params
mode = 'BID'
# Evaluate the option valuation
price = option.env.quant_bridge.black_calculator(
option._quant_library_option.option_info['payoff'],
trade_date,
option.maturity_date,
grp.spot_value(trade_date),
grp.forward_price(trade_date, option.maturity_date),
vol,
grp.discount_curve(trade_date),
grp.get_vol_surface().day_count,
['NPV'],
)
# Return the valuation adjusted for fx rates
return option.env.fx_cross.convert_at_d(
price['NPV'],
trade_date,
from_ccy=option.currency,
to_ccy=self.currency,
field=mode + 'Price'
)
# Add the method to the pricer
sig.TradePricer.option_default = option_default
sig.TradePricer.option_vol_spread = option_vol_spread

Testing the costs

Once the config is initialised with the overrides, these costs are applied throughout the framework.
The following code block shows how to retrieve the prices used on examples of EURGBP and GBPUSD options. The vol surface will be loaded for the first pricing of an option in each group.
EURUSD
Input
Output
eurusd_fx_option_group = sig.obj.get('EURUSD OTC OPTION GROUP')
eurusd_call_option = eurusd_fx_option_group.get_option(
'Call',
1.3,
dtm.date(2010, 4, 6),
dtm.date(2011, 4, 6)
)
tz = pytz.timezone('Europe/London')
eurusd_ask = sig.TradePricer(
eurusd_call_option.name,
tz.localize(dtm.datetime(2010, 5, 6, 16)),
1,
eurusd_call_option.currency,
'outright'
).trade_price()
eurusd_bid = sig.TradePricer(
eurusd_call_option.name,
tz.localize(dtm.datetime(2010, 5, 6, 16)),
-1,
eurusd_call_option.currency,
'outright'
).trade_price()
print('{} : Ask {} - Bid {}'.format(
eurusd_call_option.name,
str(eurusd_ask),
str(eurusd_bid)
))
-- Running option_default cost retrieval for 1 unit --
-- Running option_default cost retrieval for -1 unit --
EURUSD 20110406 CALL 1.3 2432C0B2 CURNCY : Ask 0.05857657771580961 - Bid 0.05711512613362098
GBPUSD
Input
Output
gbpusd_fx_option_group = sig.obj.get('GBPUSD OTC OPTION GROUP')
gbpusd_call_option = gbpusd_fx_option_group.get_option(
'Call',
1.52,
dtm.date(2010, 4, 6),
dtm.date(2011, 4, 6)
)
gbpusd_ask = sig.TradePricer(
gbpusd_call_option.name,
tz.localize(dtm.datetime(2010, 5, 6, 15)),
1,
gbpusd_call_option.currency,
'outright'
).trade_price()
gbpusd_bid = sig.TradePricer(
gbpusd_call_option.name,
tz.localize(dtm.datetime(2010, 5, 6, 15)),
-1,
gbpusd_call_option.currency,
'outright'
).trade_price()
print('{} Ask {} - Bid {}'.format(
gbpusd_call_option.name,
str(gbpusd_ask),
str(gbpusd_bid)
))
-- Running option_vol_spread cost retrieval for 1 unit --
-- Running option_vol_spread cost retrieval for -1 unit --
GBPUSD 20110406 CALL 1.52 88628D24 CURNCY Ask 0.06781705290343404 - Bid 0.06668396352274569

Querying the transaction cost model

The TradePricer interface allows you to get the information about the transaction cost model applied to a given instrument:
lme_future = sig.LMEFuture( underlying_ticker='LX',
future_ticker='K21',
strike=0)
sig.TradePricer.get_tc_model(lme_future.name, display=True)