chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user