Skip to content

Commit

Permalink
Add example on volume share slippage model (#8437)
Browse files Browse the repository at this point in the history
* add csharp example

* add python example

* regression

* add pythom implementation as example model

* peer review

* dependencies

* fix regression test
  • Loading branch information
LouisSzeto authored Dec 9, 2024
1 parent 81d7774 commit 9865ce9
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 0 deletions.
135 changes: 135 additions & 0 deletions Algorithm.CSharp/VolumeShareSlippageModelAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System.Collections.Generic;
using System.Linq;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;

namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Example algorithm implementing VolumeShareSlippageModel.
/// </summary>
public class VolumeShareSlippageModelAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private List<Symbol> _longs = new();
private List<Symbol> _shorts = new();

public override void Initialize()
{
SetStartDate(2020, 11, 29);
SetEndDate(2020, 12, 2);
// To set the slippage model to limit to fill only 30% volume of the historical volume, with 5% slippage impact.
SetSecurityInitializer((security) => security.SetSlippageModel(new VolumeShareSlippageModel(0.3m, 0.05m)));

// Create SPY symbol to explore its constituents.
var spy = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);

UniverseSettings.Resolution = Resolution.Daily;
// Add universe to trade on the most and least weighted stocks among SPY constituents.
AddUniverse(Universe.ETF(spy, universeFilterFunc: Selection));
}

private IEnumerable<Symbol> Selection(IEnumerable<ETFConstituentUniverse> constituents)
{
var sortedByDollarVolume = constituents.OrderBy(x => x.Weight).ToList();
// Add the 10 most weighted stocks to the universe to long later.
_longs = sortedByDollarVolume.TakeLast(10)
.Select(x => x.Symbol)
.ToList();
// Add the 10 least weighted stocks to the universe to short later.
_shorts = sortedByDollarVolume.Take(10)
.Select(x => x.Symbol)
.ToList();

return _longs.Union(_shorts);
}

public override void OnData(Slice slice)
{
// Equally invest into the selected stocks to evenly dissipate capital risk.
// 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 ones not being the most and least popularity stocks to release fund for higher expected return trades.
SetHoldings(targets, liquidateExistingHoldings: true);
}

/// <summary>
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
/// </summary>
public bool CanRunLocally { get; } = true;

/// <summary>
/// This is used by the regression test system to indicate which languages this algorithm is written in.
/// </summary>
public List<Language> Languages { get; } = new() { Language.CSharp, Language.Python };

/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
public long DataPoints => 1035;

/// <summary>
/// Data Points count of the algorithm history
/// </summary>
public int AlgorithmHistoryDataPoints => 0;

/// <summary>
/// Final status of the algorithm
/// </summary>
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;

/// <summary>
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
/// </summary>
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"Total Orders", "4"},
{"Average Win", "0%"},
{"Average Loss", "0%"},
{"Compounding Annual Return", "20.900%"},
{"Drawdown", "0%"},
{"Expectancy", "0"},
{"Start Equity", "100000"},
{"End Equity", "100190.84"},
{"Net Profit", "0.191%"},
{"Sharpe Ratio", "9.794"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "0%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0.297"},
{"Beta", "-0.064"},
{"Annual Standard Deviation", "0.017"},
{"Annual Variance", "0"},
{"Information Ratio", "-18.213"},
{"Tracking Error", "0.099"},
{"Treynor Ratio", "-2.695"},
{"Total Fees", "$4.00"},
{"Estimated Strategy Capacity", "$4400000000.00"},
{"Lowest Capacity Asset", "GOOCV VP83T1ZUHROL"},
{"Portfolio Turnover", "4.22%"},
{"OrderListHash", "9d2bd0df7c094c393e77f72b7739bfa0"}
};
}
}
53 changes: 53 additions & 0 deletions Algorithm.Python/VolumeShareSlippageModelAlgorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *
from Orders.Slippage.VolumeShareSlippageModel import VolumeShareSlippageModel

### <summary>
### Example algorithm implementing VolumeShareSlippageModel.
### </summary>
class VolumeShareSlippageModelAlgorithm(QCAlgorithm):
longs = []
shorts = []

def initialize(self) -> None:
self.set_start_date(2020, 11, 29)
self.set_end_date(2020, 12, 2)
# To set the slippage model to limit to fill only 30% volume of the historical volume, with 5% slippage impact.
self.set_security_initializer(lambda security: security.set_slippage_model(VolumeShareSlippageModel(0.3, 0.05)))

# Create SPY symbol to explore its constituents.
spy = Symbol.create("SPY", SecurityType.EQUITY, Market.USA)

self.universe_settings.resolution = Resolution.DAILY
# Add universe to trade on the most and least weighted stocks among SPY constituents.
self.add_universe(self.universe.etf(spy, universe_filter_func=self.selection))

def selection(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]:
sorted_by_weight = sorted(constituents, key=lambda c: c.weight)
# Add the 10 most weighted stocks to the universe to long later.
self.longs = [c.symbol for c in sorted_by_weight[-10:]]
# Add the 10 least weighted stocks to the universe to short later.
self.shorts = [c.symbol for c in sorted_by_weight[:10]]

return self.longs + self.shorts

def on_data(self, slice: Slice) -> None:
# Equally invest into the selected stocks to evenly dissipate capital risk.
# 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 ones not being the most and least popularity stocks to release fund for higher expected return trades.
self.set_holdings(targets, liquidate_existing_holdings=True)
61 changes: 61 additions & 0 deletions Common/Orders/Slippage/VolumeShareSlippageModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *

class VolumeShareSlippageModel:
'''Represents a slippage model that is calculated by multiplying the price impact constant by the square of the ratio of the order to the total volume.'''

def __init__(self, volume_limit: float = 0.025, price_impact: float = 0.1) -> None:
'''Initializes a new instance of the "VolumeShareSlippageModel" class
Args:
volume_limit:
price_impact: Defines how large of an impact the order will have on the price calculation'''
self.volume_limit = volume_limit
self.price_impact = price_impact

def get_slippage_approximation(self, asset: Security, order: Order) -> float:
'''Slippage Model. Return a decimal cash slippage approximation on the order.
Args:
asset: The Security instance of the security of the order.
order: The Order instance being filled.'''
last_data = asset.get_last_data()
if not last_data:
return 0

bar_volume = 0
slippage_percent = self.volume_limit * self.volume_limit * self.price_impact

if last_data.data_type == MarketDataType.TRADE_BAR:
bar_volume = last_data.volume
elif last_data.data_type == MarketDataType.QUOTE_BAR:
bar_volume = last_data.last_bid_size if order.direction == OrderDirection.BUY else last_data.last_ask_size
else:
raise InvalidOperationException(Messages.VolumeShareSlippageModel.invalid_market_data_type(last_data))

# If volume is zero or negative, we use the maximum slippage percentage since the impact of any quantity is infinite
# In FX/CFD case, we issue a warning and return zero slippage
if bar_volume <= 0:
security_type = asset.symbol.id.security_type
if security_type == SecurityType.CFD or security_type == SecurityType.FOREX or security_type == SecurityType.CRYPTO:
Log.error(Messages.VolumeShareSlippageModel.volume_not_reported_for_market_data_type(security_type))
return 0

Log.error(Messages.VolumeShareSlippageModel.negative_or_zero_bar_volume(bar_volume, slippage_percent))
else:
# Ratio of the order to the total volume
volume_share = min(order.absolute_quantity / bar_volume, self.volume_limit)

slippage_percent = volume_share * volume_share * self.price_impact

return slippage_percent * last_data.Value;
3 changes: 3 additions & 0 deletions Common/QuantConnect.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,8 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<PackageCopyToOutput>true</PackageCopyToOutput>
</Content>
<Content Include="Orders\Slippage\VolumeShareSlippageModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

0 comments on commit 9865ce9

Please sign in to comment.