chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,99 @@
/*
* 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;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Algorithm.Framework.Execution
{
/// <summary>
/// Execution model that submits orders while the current spread is in desirably tight extent.
/// </summary>
/// <remarks>Note this execution model will not work using <see cref="Resolution.Daily"/>
/// since Exchange.ExchangeOpen will be false, suggested resolution is <see cref="Resolution.Minute"/></remarks>
public class SpreadExecutionModel : ExecutionModel
{
private readonly decimal _acceptingSpreadPercent;
private readonly PortfolioTargetCollection _targetsCollection;
/// <summary>
/// Initializes a new instance of the <see cref="SpreadExecutionModel"/> class
/// </summary>
/// <param name="acceptingSpreadPercent">Maximum spread accepted comparing to current price in percentage.</param>
/// <param name="asynchronous">If true, orders will be submitted asynchronously</param>
public SpreadExecutionModel(decimal acceptingSpreadPercent = 0.005m, bool asynchronous = true)
: base(asynchronous)
{
_acceptingSpreadPercent = Math.Abs(acceptingSpreadPercent);
_targetsCollection = new PortfolioTargetCollection();
}
/// <summary>
/// Submit orders for the specified portfolio targets if the spread is tighter/equal to preset level
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The portfolio targets to be ordered</param>
public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
// update the complete set of portfolio targets with the new targets
_targetsCollection.AddRange(targets);
// for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if (!_targetsCollection.IsEmpty)
{
foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
{
var symbol = target.Symbol;
// calculate remaining quantity to be ordered
var unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target);
if (unorderedQuantity != 0)
{
// get security object
var security = algorithm.Securities[symbol];
// check order entry conditions
if (PriceIsFavorable(security))
{
algorithm.MarketOrder(symbol, unorderedQuantity, Asynchronous, target.Tag);
}
}
}
_targetsCollection.ClearFulfilled(algorithm);
}
}
/// <summary>
/// Determines if the current spread is equal or tighter than preset level
/// </summary>
protected virtual bool PriceIsFavorable(Security security)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(PriceIsFavorable), out bool result, security))
{
return result;
}
// Has to be in opening hours of exchange to avoid extreme spread in OTC period
// Price has to be larger than zero to avoid zero division error, or negative price causing the spread percentage lower than preset value by accident
return security.Exchange.ExchangeOpen
&& security.Price > 0 && security.AskPrice > 0 && security.BidPrice > 0
&& (security.AskPrice - security.BidPrice) / security.Price <= _acceptingSpreadPercent;
}
}
}
@@ -0,0 +1,60 @@
# 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 SpreadExecutionModel(ExecutionModel):
'''Execution model that submits orders while the current spread is tight.
Note this execution model will not work using Resolution.DAILY since Exchange.exchange_open will be false, suggested resolution is Minute
'''
def __init__(self, accepting_spread_percent=0.005, asynchronous=True):
'''Initializes a new instance of the SpreadExecutionModel class'''
super().__init__(asynchronous)
self.targets_collection = PortfolioTargetCollection()
# Gets or sets the maximum spread compare to current price in percentage.
self.accepting_spread_percent = Math.abs(accepting_spread_percent)
def execute(self, algorithm, targets):
'''Executes market orders if the spread percentage to price is in desirable range.
Args:
algorithm: The algorithm instance
targets: The portfolio targets'''
# update the complete set of portfolio targets with the new targets
self.targets_collection.add_range(targets)
# for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if not self.targets_collection.is_empty:
for target in self.targets_collection.order_by_margin_impact(algorithm):
symbol = target.symbol
# calculate remaining quantity to be ordered
unordered_quantity = OrderSizing.get_unordered_quantity(algorithm, target)
# check order entry conditions
if unordered_quantity != 0:
# get security information
security = algorithm.securities[symbol]
if self.spread_is_favorable(security):
algorithm.market_order(symbol, unordered_quantity, self.asynchronous, target.tag)
self.targets_collection.clear_fulfilled(algorithm)
def spread_is_favorable(self, security):
'''Determines if the spread is in desirable range.'''
# Price has to be larger than zero to avoid zero division error, or negative price causing the spread percentage < 0 by error
# Has to be in opening hours of exchange to avoid extreme spread in OTC period
return security.exchange.exchange_open \
and security.price > 0 and security.ask_price > 0 and security.bid_price > 0 \
and (security.ask_price - security.bid_price) / security.price <= self.accepting_spread_percent
@@ -0,0 +1,231 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
using QuantConnect.Orders;
namespace QuantConnect.Algorithm.Framework.Execution
{
/// <summary>
/// Execution model that submits orders while the current market prices is at least the configured number of standard
/// deviations away from the mean in the favorable direction (below/above for buy/sell respectively)
/// </summary>
public class StandardDeviationExecutionModel : ExecutionModel
{
private readonly int _period;
private readonly decimal _deviations;
private readonly Resolution _resolution;
private readonly PortfolioTargetCollection _targetsCollection;
private readonly Dictionary<Symbol, SymbolData> _symbolData;
/// <summary>
/// Gets or sets the maximum order value in units of the account currency.
/// This defaults to $20,000. For example, if purchasing a stock with a price
/// of $100, then the maximum order size would be 200 shares.
/// </summary>
public decimal MaximumOrderValue { get; set; } = 20 * 1000;
/// <summary>
/// Initializes a new instance of the <see cref="StandardDeviationExecutionModel"/> class
/// </summary>
/// <param name="period">Period of the standard deviation indicator</param>
/// <param name="deviations">The number of deviations away from the mean before submitting an order</param>
/// <param name="resolution">The resolution of the STD and SMA indicators</param>
/// <param name="asynchronous">If true, orders should be submitted asynchronously</param>
public StandardDeviationExecutionModel(
int period = 60,
decimal deviations = 2m,
Resolution resolution = Resolution.Minute,
bool asynchronous = true
)
: base(asynchronous)
{
_period = period;
_deviations = deviations;
_resolution = resolution;
_targetsCollection = new PortfolioTargetCollection();
_symbolData = new Dictionary<Symbol, SymbolData>();
}
/// <summary>
/// Executes market orders if the standard deviation of price is more than the configured number of deviations
/// in the favorable direction.
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The portfolio targets</param>
public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
_targetsCollection.AddRange(targets);
// for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if (!_targetsCollection.IsEmpty)
{
foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
{
var symbol = target.Symbol;
// calculate remaining quantity to be ordered
var unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target);
// fetch our symbol data containing our STD/SMA indicators
SymbolData data;
if (!_symbolData.TryGetValue(symbol, out data))
{
continue;
}
// check order entry conditions
if (data.STD.IsReady && PriceIsFavorable(data, unorderedQuantity))
{
// Adjust order size to respect the maximum total order value
var orderSize = OrderSizing.GetOrderSizeForMaximumValue(data.Security, MaximumOrderValue, unorderedQuantity);
if (orderSize != 0)
{
algorithm.MarketOrder(symbol, orderSize, Asynchronous, target.Tag);
}
}
}
_targetsCollection.ClearFulfilled(algorithm);
}
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
foreach (var added in changes.AddedSecurities)
{
// initialize new securities
if (!_symbolData.ContainsKey(added.Symbol))
{
_symbolData[added.Symbol] = new SymbolData(algorithm, added, _period, _resolution);
}
}
foreach (var removed in changes.RemovedSecurities)
{
// clean up data from removed securities
SymbolData data;
if (_symbolData.TryGetValue(removed.Symbol, out data))
{
if (IsSafeToRemove(algorithm, removed.Symbol))
{
_symbolData.Remove(removed.Symbol);
algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator);
}
}
}
}
/// <summary>
/// Determines if the current price is more than the configured number of standard deviations
/// away from the mean in the favorable direction.
/// </summary>
protected virtual bool PriceIsFavorable(SymbolData data, decimal unorderedQuantity)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(PriceIsFavorable), out bool result, data, unorderedQuantity))
{
return result;
}
var deviations = _deviations * data.STD;
return unorderedQuantity > 0
? data.Security.BidPrice < data.SMA - deviations
: data.Security.AskPrice > data.SMA + deviations;
}
/// <summary>
/// Determines if it's safe to remove the associated symbol data
/// </summary>
protected virtual bool IsSafeToRemove(QCAlgorithm algorithm, Symbol symbol)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(IsSafeToRemove), out bool result, algorithm, symbol))
{
return result;
}
// confirm the security isn't currently a member of any universe
return !algorithm.UniverseManager.Any(kvp => kvp.Value.ContainsMember(symbol));
}
/// <summary>
/// Symbol Data for this Execution Model
/// </summary>
protected class SymbolData
{
/// <summary>
/// Security
/// </summary>
public Security Security { get; }
/// <summary>
/// Standard Deviation
/// </summary>
public StandardDeviation STD { get; }
/// <summary>
/// Simple Moving Average
/// </summary>
public SimpleMovingAverage SMA { get; }
/// <summary>
/// Data Consolidator
/// </summary>
public IDataConsolidator Consolidator { get; }
/// <summary>
/// Initialize an instance of <see cref="SymbolData"/>
/// </summary>
/// <param name="algorithm">Algorithm for this security</param>
/// <param name="security">The security we are using</param>
/// <param name="period">Period of the SMA and STD</param>
/// <param name="resolution">Resolution for this symbol</param>
public SymbolData(QCAlgorithm algorithm, Security security, int period, Resolution resolution)
{
Security = security;
Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
var smaName = algorithm.CreateIndicatorName(security.Symbol, "SMA" + period, resolution);
SMA = new SimpleMovingAverage(smaName, period);
algorithm.RegisterIndicator(security.Symbol, SMA, Consolidator);
var stdName = algorithm.CreateIndicatorName(security.Symbol, "STD" + period, resolution);
STD = new StandardDeviation(stdName, period);
algorithm.RegisterIndicator(security.Symbol, STD, Consolidator);
// warmup our indicators by pushing history through the indicators
foreach (var bar in algorithm.History(Security.Symbol, period, resolution))
{
SMA.Update(bar.EndTime, bar.Value);
STD.Update(bar.EndTime, bar.Value);
}
}
}
}
}
@@ -0,0 +1,127 @@
# 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 StandardDeviationExecutionModel(ExecutionModel):
'''Execution model that submits orders while the current market prices is at least the configured number of standard
deviations away from the mean in the favorable direction (below/above for buy/sell respectively)'''
def __init__(self,
period = 60,
deviations = 2,
resolution = Resolution.MINUTE,
asynchronous=True):
'''Initializes a new instance of the StandardDeviationExecutionModel class
Args:
period: Period of the standard deviation indicator
deviations: The number of deviations away from the mean before submitting an order
resolution: The resolution of the STD and SMA indicators
asynchronous: If True, orders will be submitted asynchronously.'''
super().__init__(asynchronous)
self.period = period
self.deviations = deviations
self.resolution = resolution
self.targets_collection = PortfolioTargetCollection()
self._symbol_data = {}
# Gets or sets the maximum order value in units of the account currency.
# This defaults to $20,000. For example, if purchasing a stock with a price
# of $100, then the maximum order size would be 200 shares.
self.maximum_order_value = 20000
def execute(self, algorithm, targets):
'''Executes market orders if the standard deviation of price is more
than the configured number of deviations in the favorable direction.
Args:
algorithm: The algorithm instance
targets: The portfolio targets'''
self.targets_collection.add_range(targets)
# for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if not self.targets_collection.is_empty:
for target in self.targets_collection.order_by_margin_impact(algorithm):
symbol = target.symbol
# calculate remaining quantity to be ordered
unordered_quantity = OrderSizing.get_unordered_quantity(algorithm, target)
# fetch our symbol data containing our STD/SMA indicators
data = self._symbol_data.get(symbol, None)
if data is None: return
# check order entry conditions
if data.std.is_ready and self.price_is_favorable(data, unordered_quantity):
# Adjust order size to respect the maximum total order value
order_size = OrderSizing.get_order_size_for_maximum_value(data.security, self.maximum_order_value, unordered_quantity)
if order_size != 0:
algorithm.market_order(symbol, order_size, self.asynchronous, target.tag)
self.targets_collection.clear_fulfilled(algorithm)
def on_securities_changed(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
for added in changes.added_securities:
if added.symbol not in self._symbol_data:
self._symbol_data[added.symbol] = SymbolData(algorithm, added, self.period, self.resolution)
for removed in changes.removed_securities:
# clean up data from removed securities
symbol = removed.symbol
if symbol in self._symbol_data:
if self.is_safe_to_remove(algorithm, symbol):
data = self._symbol_data.pop(symbol)
algorithm.subscription_manager.remove_consolidator(symbol, data.consolidator)
def price_is_favorable(self, data, unordered_quantity):
'''Determines if the current price is more than the configured
number of standard deviations away from the mean in the favorable direction.'''
sma = data.sma.current.value
deviations = self.deviations * data.std.current.value
if unordered_quantity > 0:
return data.security.bid_price < sma - deviations
else:
return data.security.ask_price > sma + deviations
def is_safe_to_remove(self, algorithm, symbol):
'''Determines if it's safe to remove the associated symbol data'''
# confirm the security isn't currently a member of any universe
return not any([kvp.value.contains_member(symbol) for kvp in algorithm.universe_manager])
class SymbolData:
def __init__(self, algorithm, security, period, resolution):
symbol = security.symbol
self.security = security
self.consolidator = algorithm.resolve_consolidator(symbol, resolution)
sma_name = algorithm.create_indicator_name(symbol, f"SMA{period}", resolution)
self.sma = SimpleMovingAverage(sma_name, period)
algorithm.register_indicator(symbol, self.sma, self.consolidator)
std_name = algorithm.create_indicator_name(symbol, f"STD{period}", resolution)
self.std = StandardDeviation(std_name, period)
algorithm.register_indicator(symbol, self.std, self.consolidator)
# warmup our indicators by pushing history through the indicators
bars = algorithm.history[self.consolidator.input_type](symbol, period, resolution)
for bar in bars:
self.sma.update(bar.end_time, bar.close)
self.std.update(bar.end_time, bar.close)
@@ -0,0 +1,207 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
using QuantConnect.Orders;
namespace QuantConnect.Algorithm.Framework.Execution
{
/// <summary>
/// Execution model that submits orders while the current market price is more favorable that the current volume weighted average price.
/// </summary>
public class VolumeWeightedAveragePriceExecutionModel : ExecutionModel
{
private readonly PortfolioTargetCollection _targetsCollection = new PortfolioTargetCollection();
private readonly Dictionary<Symbol, SymbolData> _symbolData = new Dictionary<Symbol, SymbolData>();
/// <summary>
/// Gets or sets the maximum order quantity as a percentage of the current bar's volume.
/// This defaults to 0.01m = 1%. For example, if the current bar's volume is 100, then
/// the maximum order size would equal 1 share.
/// </summary>
public decimal MaximumOrderQuantityPercentVolume { get; set; } = 0.01m;
/// <summary>
/// Initializes a new instance of the <see cref="VolumeWeightedAveragePriceExecutionModel"/> class.
/// </summary>
/// <param name="asynchronous">If true, orders will be submitted asynchronously</param>
public VolumeWeightedAveragePriceExecutionModel(bool asynchronous = true)
: base(asynchronous)
{
}
/// <summary>
/// Submit orders for the specified portfolio targets.
/// This model is free to delay or spread out these orders as it sees fit
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="targets">The portfolio targets to be ordered</param>
public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
// update the complete set of portfolio targets with the new targets
_targetsCollection.AddRange(targets);
// for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if (!_targetsCollection.IsEmpty)
{
foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
{
var symbol = target.Symbol;
// calculate remaining quantity to be ordered
var unorderedQuantity = OrderSizing.GetUnorderedQuantity(algorithm, target);
// fetch our symbol data containing our VWAP indicator
SymbolData data;
if (!_symbolData.TryGetValue(symbol, out data))
{
continue;
}
// check order entry conditions
if (PriceIsFavorable(data, unorderedQuantity))
{
// adjust order size to respect maximum order size based on a percentage of current volume
var orderSize = OrderSizing.GetOrderSizeForPercentVolume(
data.Security, MaximumOrderQuantityPercentVolume, unorderedQuantity);
if (orderSize != 0)
{
algorithm.MarketOrder(data.Security.Symbol, orderSize, Asynchronous, target.Tag);
}
}
}
_targetsCollection.ClearFulfilled(algorithm);
}
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
foreach (var added in changes.AddedSecurities)
{
if (!_symbolData.ContainsKey(added.Symbol))
{
_symbolData[added.Symbol] = new SymbolData(algorithm, added);
}
}
foreach (var removed in changes.RemovedSecurities)
{
// clean up removed security data
SymbolData data;
if (_symbolData.TryGetValue(removed.Symbol, out data))
{
if (IsSafeToRemove(algorithm, removed.Symbol))
{
_symbolData.Remove(removed.Symbol);
algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator);
}
}
}
}
/// <summary>
/// Determines if it's safe to remove the associated symbol data
/// </summary>
protected virtual bool IsSafeToRemove(QCAlgorithm algorithm, Symbol symbol)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(IsSafeToRemove), out bool result, algorithm, symbol))
{
return result;
}
// confirm the security isn't currently a member of any universe
return !algorithm.UniverseManager.Any(kvp => kvp.Value.ContainsMember(symbol));
}
/// <summary>
/// Determines if the current price is better than VWAP
/// </summary>
protected virtual bool PriceIsFavorable(SymbolData data, decimal unorderedQuantity)
{
// Check if this method was overridden in Python
if (TryInvokePythonOverride(nameof(PriceIsFavorable), out bool result, data, unorderedQuantity))
{
return result;
}
if (unorderedQuantity > 0)
{
if (data.Security.BidPrice < data.VWAP)
{
return true;
}
}
else
{
if (data.Security.AskPrice > data.VWAP)
{
return true;
}
}
return false;
}
/// <summary>
/// Symbol data for this Execution Model
/// </summary>
protected class SymbolData
{
/// <summary>
/// Security
/// </summary>
public Security Security { get; }
/// <summary>
/// VWAP Indicator
/// </summary>
public IntradayVwap VWAP { get; }
/// <summary>
/// Data Consolidator
/// </summary>
public IDataConsolidator Consolidator { get; }
/// <summary>
/// Initialize a new instance of <see cref="SymbolData"/>
/// </summary>
public SymbolData(QCAlgorithm algorithm, Security security)
{
Security = security;
Consolidator = algorithm.ResolveConsolidator(security.Symbol, security.Resolution);
var name = algorithm.CreateIndicatorName(security.Symbol, "VWAP", security.Resolution);
VWAP = new IntradayVwap(name);
algorithm.RegisterIndicator(security.Symbol, VWAP, Consolidator, bd => (BaseData)bd);
}
}
}
}
@@ -0,0 +1,158 @@
# 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 VolumeWeightedAveragePriceExecutionModel(ExecutionModel):
'''Execution model that submits orders while the current market price is more favorable that the current volume weighted average price.'''
def __init__(self, asynchronous=True):
'''Initializes a new instance of the VolumeWeightedAveragePriceExecutionModel class'''
super().__init__(asynchronous)
self.targets_collection = PortfolioTargetCollection()
self.symbol_data = {}
# Gets or sets the maximum order quantity as a percentage of the current bar's volume.
# This defaults to 0.01m = 1%. For example, if the current bar's volume is 100,
# then the maximum order size would equal 1 share.
self.maximum_order_quantity_percent_volume = 0.01
def execute(self, algorithm, targets):
'''Executes market orders if the standard deviation of price is more
than the configured number of deviations in the favorable direction.
Args:
algorithm: The algorithm instance
targets: The portfolio targets'''
# update the complete set of portfolio targets with the new targets
self.targets_collection.add_range(targets)
# for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
if not self.targets_collection.is_empty:
for target in self.targets_collection.order_by_margin_impact(algorithm):
symbol = target.symbol
# calculate remaining quantity to be ordered
unordered_quantity = OrderSizing.get_unordered_quantity(algorithm, target)
# fetch our symbol data containing our VWAP indicator
data = self.symbol_data.get(symbol, None)
if data is None: return
# check order entry conditions
if self.price_is_favorable(data, unordered_quantity):
# adjust order size to respect maximum order size based on a percentage of current volume
order_size = OrderSizing.get_order_size_for_percent_volume(data.security, self.maximum_order_quantity_percent_volume, unordered_quantity)
if order_size != 0:
algorithm.market_order(symbol, order_size, self.asynchronous, target.tag)
self.targets_collection.clear_fulfilled(algorithm)
def on_securities_changed(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
for removed in changes.removed_securities:
# clean up removed security data
if removed.symbol in self.symbol_data:
if self.is_safe_to_remove(algorithm, removed.symbol):
data = self.symbol_data.pop(removed.symbol)
algorithm.subscription_manager.remove_consolidator(removed.symbol, data.consolidator)
for added in changes.added_securities:
if added.symbol not in self.symbol_data:
self.symbol_data[added.symbol] = SymbolData(algorithm, added)
def price_is_favorable(self, data, unordered_quantity):
'''Determines if the current price is more than the configured
number of standard deviations away from the mean in the favorable direction.'''
if unordered_quantity > 0:
if data.security.bid_price < data.vwap:
return True
else:
if data.security.ask_price > data.vwap:
return True
return False
def is_safe_to_remove(self, algorithm, symbol):
'''Determines if it's safe to remove the associated symbol data'''
# confirm the security isn't currently a member of any universe
return not any([kvp.value.contains_member(symbol) for kvp in algorithm.universe_manager])
class SymbolData:
def __init__(self, algorithm, security):
self.security = security
self.consolidator = algorithm.resolve_consolidator(security.symbol, security.resolution)
name = algorithm.create_indicator_name(security.symbol, "VWAP", security.resolution)
self._vwap = IntradayVwap(name)
algorithm.register_indicator(security.symbol, self._vwap, self.consolidator)
@property
def vwap(self):
return self._vwap.value
class IntradayVwap:
'''Defines the canonical intraday VWAP indicator'''
def __init__(self, name):
self.name = name
self.value = 0.0
self.last_date = datetime.min
self.sum_of_volume = 0.0
self.sum_of_price_times_volume = 0.0
@property
def is_ready(self):
return self.sum_of_volume > 0.0
def update(self, input):
'''Computes the new VWAP'''
success, volume, average_price = self.get_volume_and_average_price(input)
if not success:
return self.is_ready
# reset vwap on daily boundaries
if self.last_date != input.end_time.date():
self.sum_of_volume = 0.0
self.sum_of_price_times_volume = 0.0
self.last_date = input.end_time.date()
# running totals for Σ PiVi / Σ Vi
self.sum_of_volume += volume
self.sum_of_price_times_volume += average_price * volume
if self.sum_of_volume == 0.0:
# if we have no trade volume then use the current price as VWAP
self.value = input.value
return self.is_ready
self.value = self.sum_of_price_times_volume / self.sum_of_volume
return self.is_ready
def get_volume_and_average_price(self, input):
'''Determines the volume and price to be used for the current input in the VWAP computation'''
if type(input) is Tick:
if input.tick_type == TickType.TRADE:
return True, float(input.quantity), float(input.last_price)
if type(input) is TradeBar:
if not input.is_fill_forward:
average_price = float(input.high + input.low + input.close) / 3
return True, float(input.volume), average_price
return False, 0.0, 0.0