Slippage
Key Concepts
Introduction
Slippage is the difference between the fill price you expect to get for an order and the actual fill price. Since the price can move in the direction of your trade or against the direction of your trade while you wait for the order to fill, slippage can be positive or negative. Slippage models model slippage to make backtest results more realistic.
Factors Impacting Slippage
There are many factors that can impact slippage, including the trading engine, brokerage connection, and market dynamics.
Trading Engine
How long does it take from when you place an order to when it's sent to the brokerage? Longer delays lead to more slippage.
Brokerage Connection
How long does it take for your brokerage to receive an order that you send? Slow internet connections, long travel distances, and poor infrastructure lead to more slippage
Market Dynamics
How volatile is the current market environment? More volatility leads to more slippage.
Does the market consist of sophisticated microsecond arbitrageurs? If your order creates an arbitrage opportunity, it can cause more slippage.
Set Models
The brokerage model of your algorithm automatically sets the slippage model for each security, but you can override it. To manually set the slippage model of a security, call the SetSlippageModel
set_slippage_model
method on the Security
object.
public override void Initialize() { var security = AddEquity("SPY"); // Set the slippage model for the requested security to backtest with the most realistic scenario // VolumeShareSlippageModel has slippage size affected by the volume of the order compared to the actual filled volume of the bar // It is only valid for the security with a volume property, while being more accurate with denser resolution security.SetSlippageModel(new VolumeShareSlippageModel()); }
def initialize(self) -> None: security = self.add_equity("SPY") # Set the slippage model for the requested security to backtest with the most realistic scenario # VolumeShareSlippageModel has slippage size affected by the volume of the order compared to the actual filled volume of the bar # It is only valid for the security with a volume property, while being more accurate with denser resolution security.set_slippage_model(VolumeShareSlippageModel())
You can also set the slippage model in a security initializer. If your algorithm has a dynamic universe, use the security initializer technique. In order to initialize single security subscriptions with the security initializer, call SetSecurityInitializer
set_security_initializer
before you create the subscriptions.
public class BrokerageModelExampleAlgorithm : QCAlgorithm { public override void Initialize() { // In the Initialize method, set the security initializer to seed initial the prices and models of assets. SetSecurityInitializer(new MySecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices))); } } public class MySecurityInitializer : BrokerageModelSecurityInitializer { public MySecurityInitializer(IBrokerageModel brokerageModel, ISecuritySeeder securitySeeder) : base(brokerageModel, securitySeeder) {} public override void Initialize(Security security) { // First, call the superclass definition. // This method sets the reality models of each security using the default reality models of the brokerage model. base.Initialize(security); // Next, overwrite some of the reality models security.SetSlippageModel(new VolumeShareSlippageModel()); } }
class BrokerageModelExampleAlgorithm(QCAlgorithm): def initialize(self) -> None: # In the Initialize method, set the security initializer to seed initial the prices and models of assets. self.set_security_initializer(MySecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))) # Outside of the algorithm class class MySecurityInitializer(BrokerageModelSecurityInitializer): def __init__(self, brokerage_model: IBrokerageModel, security_seeder: ISecuritySeeder) -> None: super().__init__(brokerage_model, security_seeder) def initialize(self, security: Security) -> None: # First, call the superclass definition. # This method sets the reality models of each security using the default reality models of the brokerage model. super().initialize(security) # Next, overwrite some of the reality models security.set_slippage_model(VolumeShareSlippageModel())
To view all the pre-built slippage models, see Supported Models.
Default Behavior
The brokerage model of your algorithm automatically sets the slippage model of each security. The default brokerage model is the DefaultBrokerageModel
, which uses the NullSlippageModel to model zero slippage for all securities.
Model Structure
Slippage models should implement the ISlippageModel
interface. Extensions of the ISlippageModel
interface must implement the GetSlippageApproximation
get_slippage_approximation
method, which calculates the slippage quantity.
public class CustomSlippageModelExampleAlgorithm : QCAlgorithm { public override void Initialize() { // In the Initialize method, set the custom slippage model for an added security to use the custom model var security = AddEquity("SPY"); security.SetSlippageModel(new MySlippageModel()); } } // Define the custom slippage model public class MySlippageModel : ISlippageModel { public decimal GetSlippageApproximation(Security asset, Order order) { return asset.Price*0.0001m*(decimal) Math.Log10(2*(double) order.AbsoluteQuantity); } }
class CustomSlippageModelExampleAlgorithm(QCAlgorithm): def initialize(self) -> None: # In the Initialize method, set the custom slippage model for an added security to use the custom model security = self.add_equity("SPY") security.set_slippage_model(MySlippageModel()) # Define the custom slippage model class MySlippageModel: def get_slippage_approximation(self, asset: Security, order: Order) -> float: return asset.price * 0.0001 * np.log10(2*float(order.absolute_quantity))
For a full example algorithm, see this backtestthis backtest.
Examples
The following examples demonstrate some common practices for implementing a custom slippage model.
Example 1: Volume Ratio Capped Slippage Model
The following algorithm trades the microeconomic demand popularity gap between the top 10 and bottom 10 liquid constituents of QQQ. A custom slippage model simulates the slippage on less liquid stock fills. The slippage percent is set to be 2% multiplied by the ratio of the order quantity to the previous minute bar's volume, maximized at 2%.
public class RealityModelingSlippageAlgorithm : QCAlgorithm { private List<Symbol> _longs = new(); private List<Symbol> _shorts = new(); public override void Initialize() { SetStartDate(2021, 1, 1); SetEndDate(2022, 1, 1); SetSecurityInitializer(new CustomSecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices))); // Request extended market hour QQQ data for trading. var qqq = AddEquity("QQQ").Symbol; // Weekly portfolio updating to allow time to capitalize on the popularity gap. UniverseSettings.Schedule.On(DateRules.WeekStart()); // Add universe to trade on the most and least liquid stocks among QQQ constituents. AddUniverse( // First, we select all QQQ constituents for the next filter on liquidity. Universe.ETF(qqq, Market.USA, UniverseSettings, (constituents) => constituents.Select(c => c.Symbol)), FundamentalSelection ); // Set a scheduled event to rebalance the portfolio at the start of every week. Schedule.On( DateRules.WeekStart(qqq), TimeRules.AfterMarketOpen(qqq), Rebalance ); } private IEnumerable<Symbol> FundamentalSelection(IEnumerable<Fundamental> fundamentals) { var sortedByDollarVolume = fundamentals.OrderBy(x => x.DollarVolume).ToList(); // Add the 10 most liquid stocks to the universe to long later. _longs = sortedByDollarVolume.TakeLast(10) .Select(x => x.Symbol) .ToList(); // Add the 10 least liquid stocks to the universe to short later. _shorts = sortedByDollarVolume.Take(10) .Select(x => x.Symbol) .ToList(); return _longs.Union(_shorts); } private void Rebalance() { // Equally invest in the selected stocks to dissipate capital risk evenly. // Dollar neutral of long and short stocks to eliminate systematic risk, only capitalize the popularity gap. var targets = _longs.Select(symbol => new PortfolioTarget(symbol, 0.05m)).ToList(); targets.AddRange(_shorts.Select(symbol => new PortfolioTarget(symbol, -0.05m)).ToList()); // Liquidate the stocks that are not the most and least popular to release funds for trades with higher expected returns. SetHoldings(targets, liquidateExistingHoldings: true); } internal class CustomSlippageModel : ISlippageModel { public decimal GetSlippageApproximation(Security asset, Order order) { // We set a maximum 2% slippage that is linearly linked to the ratio of the order size versus the previous bar's volume. return asset.Price * 0.02m * Math.Min(1m, order.AbsoluteQuantity / asset.Volume); } } internal class CustomSecurityInitializer : BrokerageModelSecurityInitializer { public CustomSecurityInitializer(IBrokerageModel brokerageModel, ISecuritySeeder securitySeeder) : base(brokerageModel, securitySeeder) { } public override void Initialize(Security security) { base.Initialize(security); // To set the slippage model to the custom one for all stocks entering the universe. security.SetSlippageModel(new CustomSlippageModel()); } } }
class RealityModelingSlippageAlgorithm(QCAlgorithm): longs = [] shorts = [] def initialize(self) -> None: self.set_start_date(2021, 1, 1) self.set_end_date(2022, 1, 1) self.set_security_initializer(CustomSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))) # Request extended market hour QQQ data for trading. qqq = self.add_equity("QQQ").symbol # Weekly portfolio updating to allow time to capitalize on the popularity gap. self.universe_settings.schedule.on(self.date_rules.week_start()) # Add universe to trade on the most and least liquid stocks among QQQ constituents. self.add_universe( self.universe.etf(qqq, Market.USA, self.universe_settings, lambda constituents: [c.symbol for c in constituents]), self.fundamental_selection ) # Set a scheduled event to rebalance the portfolio at the start of every week. self.schedule.on( self.date_rules.every_day(qqq), self.time_rules.after_market_open(qqq), self.rebalance ) def fundamental_selection(self, fundamentals: List[Fundamental]) -> List[Symbol]: sorted_by_dollar_volume = sorted(fundamentals, key=lambda f: f.dollar_volume) # Add the 10 most liquid stocks to the universe to long later. self.longs = [f.symbol for f in sorted_by_dollar_volume[-10:]] # Add the 10 least liquid stocks to the universe to short later. self.shorts = [f.symbol for f in sorted_by_dollar_volume[:10]] return self.longs + self.shorts def rebalance(self) -> None: # Equally invest in the selected stocks to dissipate capital risk evenly. # Dollar neutral of long and short stocks to eliminate systematic risk, only capitalize the popularity gap. targets = [PortfolioTarget(symbol, 0.05) for symbol in self.longs] targets += [PortfolioTarget(symbol, -0.05) for symbol in self.shorts] # Liquidate the stocks that are not the most and least popular to release funds for trades with higher expected returns. self.set_holdings(targets, liquidate_existing_holdings=True) class CustomSlippageModel: def get_slippage_approximation(self, asset: Security, order: Order) -> float: # We set a maximum 2% slippage that is linearly linked to the ratio of the order size versus the previous bar's volume. return asset.price * 0.02 * min(1, order.absolute_quantity / asset.volume) class CustomSecurityInitializer(BrokerageModelSecurityInitializer): def __init__(self, brokerage_model, security_seeder): super().__init__(brokerage_model, security_seeder) def initialize(self, security): super().initialize(security) # To set the slippage model to the custom one for all stocks entering the universe. security.set_slippage_model(CustomSlippageModel())
Other Examples
For more examples, see the following algorithms: