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. 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. 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 (therαr^\alphaterm).

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. 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. Run the method with the parameters stored in T_COST_OVERRIDES.

  3. Inside method: execute instructions inside method, having access to all information stored in the parent class (more information below).

  4. Inside method: return the price pp at which the trade will be executed.

  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.

sig.TradePricer.spread_from_mid??

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

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)
))

GBPUSD

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)
))

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)

Last updated

© 2023 SIG Technologies Limited