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,38 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Represents the model responsible for applying cash settlement rules
/// </summary>
/// <remarks>This model converts the amount to the account currency and applies cash settlement immediately</remarks>
public class AccountCurrencyImmediateSettlementModel : ImmediateSettlementModel
{
/// <summary>
/// Applies cash settlement rules
/// </summary>
/// <param name="applyFundsParameters">The funds application parameters</param>
public override void ApplyFunds(ApplyFundsSettlementModelParameters applyFundsParameters)
{
var currency = applyFundsParameters.CashAmount.Currency;
var amount = applyFundsParameters.CashAmount.Amount;
var portfolio = applyFundsParameters.Portfolio;
var amountInAccountCurrency = portfolio.CashBook.ConvertToAccountCurrency(amount, currency);
portfolio.CashBook[portfolio.CashBook.AccountCurrency].AddAmount(amountInAccountCurrency);
}
}
}
+56
View File
@@ -0,0 +1,56 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Messaging class signifying a change in a user's account
/// </summary>
public class AccountEvent
{
/// <summary>
/// Gets the total cash balance of the account in units of <see cref="CurrencySymbol"/>
/// </summary>
public decimal CashBalance { get; private set; }
/// <summary>
/// Gets the currency symbol
/// </summary>
public string CurrencySymbol { get; private set; }
/// <summary>
/// Creates an AccountEvent
/// </summary>
/// <param name="currencySymbol">The currency's symbol</param>
/// <param name="cashBalance">The total cash balance of the account</param>
public AccountEvent(string currencySymbol, decimal cashBalance)
{
CashBalance = cashBalance;
CurrencySymbol = currencySymbol;
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return Messages.AccountEvent.ToString(this);
}
}
}
@@ -0,0 +1,35 @@
/*
* 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.
*
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Provides an implementation of <see cref="IPriceVariationModel"/>
/// for use when data is <see cref="DataNormalizationMode.Adjusted"/>.
/// </summary>
public class AdjustedPriceVariationModel : IPriceVariationModel
{
/// <summary>
/// Get the minimum price variation from a security
/// </summary>
/// <param name="parameters">An object containing the method parameters</param>
/// <returns>Zero</returns>
public virtual decimal GetMinimumPriceVariation(GetMinimumPriceVariationParameters parameters)
{
return 0;
}
}
}
@@ -0,0 +1,68 @@
/*
* 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 QuantConnect.Orders;
using System;
namespace QuantConnect.Securities
{
/// <summary>
/// Helper parameters class for <see cref="ISettlementModel.ApplyFunds(ApplyFundsSettlementModelParameters)"/>
/// </summary>
public class ApplyFundsSettlementModelParameters
{
/// <summary>
/// The algorithm portfolio instance
/// </summary>
public SecurityPortfolioManager Portfolio { get; set; }
/// <summary>
/// The associated security type
/// </summary>
public Security Security { get; set; }
/// <summary>
/// The current Utc time
/// </summary>
public DateTime UtcTime { get; set; }
/// <summary>
/// The funds to apply
/// </summary>
public CashAmount CashAmount { get; set; }
/// <summary>
/// The associated fill event
/// </summary>
public OrderEvent Fill { get; set; }
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The fill's security</param>
/// <param name="applicationTimeUtc">The fill time (in UTC)</param>
/// <param name="cashAmount">The amount to settle</param>
/// <param name="fill">The associated fill</param>
public ApplyFundsSettlementModelParameters(SecurityPortfolioManager portfolio, Security security, DateTime applicationTimeUtc, CashAmount cashAmount, OrderEvent fill)
{
Portfolio = portfolio;
Security = security;
UtcTime = applicationTimeUtc;
CashAmount = cashAmount;
Fill = fill;
}
}
}
+206
View File
@@ -0,0 +1,206 @@
/*
* 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.Util;
namespace QuantConnect.Securities
{
/// <summary>
/// Base class for security databases, including market hours and symbol properties.
/// </summary>
public abstract class BaseSecurityDatabase<T, TEntry>
where T : BaseSecurityDatabase<T, TEntry>
{
/// <summary>
/// The database instance loaded from the data folder
/// </summary>
protected static T DataFolderDatabase { get; set; }
/// <summary>
/// Lock object for the data folder database
/// </summary>
protected static readonly object DataFolderDatabaseLock = new object();
/// <summary>
/// The database entries
/// </summary>
protected Dictionary<SecurityDatabaseKey, TEntry> Entries { get; set; }
/// <summary>
/// Custom entries set by the user.
/// </summary>
protected HashSet<SecurityDatabaseKey> CustomEntries { get; }
// _loadFromFromDataFolder and _updateEntry are used to load the database from
// the data folder and update an entry respectively.
// These are not abstract or virtual methods because they might be static methods.
private readonly Func<T> _loadFromFromDataFolder;
private readonly Action<TEntry, TEntry> _updateEntry;
/// <summary>
/// Initializes a new instance of the <see cref="BaseSecurityDatabase{T, TEntry}"/> class
/// </summary>
/// <param name="entries">The full listing of exchange hours by key</param>
/// <param name="fromDataFolder">Method to load the database form the data folder</param>
/// <param name="updateEntry">Method to update a database entry</param>
protected BaseSecurityDatabase(Dictionary<SecurityDatabaseKey, TEntry> entries,
Func<T> fromDataFolder, Action<TEntry, TEntry> updateEntry)
{
Entries = entries;
CustomEntries = new();
_loadFromFromDataFolder = fromDataFolder;
_updateEntry = updateEntry;
}
/// <summary>
/// Resets the database, forcing a reload when reused.
/// Called in tests where multiple algorithms are run sequentially,
/// and we need to guarantee that every test starts with the same environment.
/// </summary>
#pragma warning disable CA1000 // Do not declare static members on generic types
public static void Reset()
#pragma warning restore CA1000 // Do not declare static members on generic types
{
lock (DataFolderDatabaseLock)
{
DataFolderDatabase = null;
}
}
/// <summary>
/// Reload entries dictionary from file and merge them with previous custom ones
/// </summary>
internal void UpdateDataFolderDatabase()
{
lock (DataFolderDatabaseLock)
{
Reset();
var newDatabase = _loadFromFromDataFolder();
Merge(newDatabase, resetCustomEntries: false);
// Make sure we keep this as the data folder database
DataFolderDatabase = (T)this;
}
}
/// <summary>
/// Updates the entries dictionary with the new entries from the specified database
/// </summary>
internal virtual void Merge(T newDatabase, bool resetCustomEntries)
{
var newEntries = new List<KeyValuePair<SecurityDatabaseKey, TEntry>>();
foreach (var newEntry in newDatabase.Entries)
{
if (Entries.TryGetValue(newEntry.Key, out var entry))
{
if (resetCustomEntries || !CustomEntries.Contains(newEntry.Key))
{
_updateEntry(entry, newEntry.Value);
}
}
else
{
newEntries.Add(KeyValuePair.Create(newEntry.Key, newEntry.Value));
}
}
Entries = Entries
.Where(kvp => (!resetCustomEntries && CustomEntries.Contains(kvp.Key)) || newDatabase.Entries.ContainsKey(kvp.Key))
.Concat(newEntries)
.ToDictionary();
if (resetCustomEntries)
{
CustomEntries.Clear();
}
}
/// <summary>
/// Determines if the database contains the specified key
/// </summary>
/// <param name="key">The key to search for</param>
/// <returns>True if an entry is found, otherwise false</returns>
protected bool ContainsKey(SecurityDatabaseKey key)
{
return Entries.ContainsKey(key);
}
/// <summary>
/// Check whether an entry exists for the specified market/symbol/security-type
/// </summary>
/// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
/// <param name="symbol">The particular symbol being traded</param>
/// <param name="securityType">The security type of the symbol</param>
public bool ContainsKey(string market, string symbol, SecurityType securityType)
{
return ContainsKey(new SecurityDatabaseKey(market, symbol, securityType));
}
/// <summary>
/// Check whether an entry exists for the specified market/symbol/security-type
/// </summary>
/// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
/// <param name="symbol">The particular symbol being traded (Symbol class)</param>
/// <param name="securityType">The security type of the symbol</param>
public bool ContainsKey(string market, Symbol symbol, SecurityType securityType)
{
return ContainsKey(
market,
GetDatabaseSymbolKey(symbol),
securityType);
}
/// <summary>
/// Gets the correct string symbol to use as a database key
/// </summary>
/// <param name="symbol">The symbol</param>
/// <returns>The symbol string used in the database ke</returns>
#pragma warning disable CA1000 // Do not declare static members on generic types
public static string GetDatabaseSymbolKey(Symbol symbol)
#pragma warning restore CA1000 // Do not declare static members on generic types
{
string stringSymbol;
if (symbol == null)
{
stringSymbol = string.Empty;
}
else
{
switch (symbol.ID.SecurityType)
{
case SecurityType.Option:
stringSymbol = symbol.HasUnderlying ? symbol.Underlying.Value : string.Empty;
break;
case SecurityType.IndexOption:
case SecurityType.FutureOption:
stringSymbol = symbol.HasUnderlying ? symbol.ID.Symbol : string.Empty;
break;
case SecurityType.Base:
case SecurityType.Future:
stringSymbol = symbol.ID.Symbol;
break;
default:
stringSymbol = symbol.Value;
break;
}
}
return stringSymbol;
}
}
}
@@ -0,0 +1,72 @@
/*
* 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 QuantConnect.Brokerages;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides an implementation of <see cref="ISecurityInitializer"/> that initializes a security
/// by settings the <see cref="Security.FillModel"/>, <see cref="Security.FeeModel"/>,
/// <see cref="Security.SlippageModel"/>, and the <see cref="Security.SettlementModel"/> properties
/// </summary>
public class BrokerageModelSecurityInitializer : ISecurityInitializer
{
private readonly IBrokerageModel _brokerageModel;
private readonly ISecuritySeeder _securitySeeder;
/// <summary>
/// Initializes a new instance of the <see cref="BrokerageModelSecurityInitializer"/> class
/// for the specified algorithm
/// </summary>
public BrokerageModelSecurityInitializer()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BrokerageModelSecurityInitializer"/> class
/// for the specified algorithm
/// </summary>
/// <param name="brokerageModel">The brokerage model used to initialize the security models</param>
/// <param name="securitySeeder">An <see cref="ISecuritySeeder"/> used to seed the initial price of the security</param>
public BrokerageModelSecurityInitializer(IBrokerageModel brokerageModel, ISecuritySeeder securitySeeder)
{
_brokerageModel = brokerageModel;
_securitySeeder = securitySeeder;
}
/// <summary>
/// Initializes the specified security by setting up the models
/// </summary>
/// <param name="security">The security to be initialized</param>
public virtual void Initialize(Security security)
{
// Sets the security models
security.FillModel = _brokerageModel.GetFillModel(security);
security.FeeModel = _brokerageModel.GetFeeModel(security);
security.SlippageModel = _brokerageModel.GetSlippageModel(security);
security.SettlementModel = _brokerageModel.GetSettlementModel(security);
security.BuyingPowerModel = _brokerageModel.GetBuyingPowerModel(security);
security.MarginInterestRateModel = _brokerageModel.GetMarginInterestRateModel(security);
// Sets the leverage after the buying power model. Otherwise we would set the leverage of the default model.
security.SetLeverage(_brokerageModel.GetLeverage(security));
security.SetShortableProvider(_brokerageModel.GetShortableProvider(security));
_securitySeeder.SeedSecurity(security);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Defines the result for <see cref="IBuyingPowerModel.GetBuyingPower"/>
/// </summary>
public class BuyingPower
{
/// <summary>
/// Gets the buying power
/// </summary>
public decimal Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BuyingPower"/> class
/// </summary>
/// <param name="buyingPower">The buying power</param>
public BuyingPower(decimal buyingPower)
{
Value = buyingPower;
}
}
}
+566
View File
@@ -0,0 +1,566 @@
/*
* 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.Orders.Fees;
using System.Diagnostics.CodeAnalysis;
using QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides a base class for all buying power models
/// </summary>
public class BuyingPowerModel : IBuyingPowerModel
{
/// <summary>
/// Gets an implementation of <see cref="IBuyingPowerModel"/> that
/// does not check for sufficient buying power
/// </summary>
public static readonly IBuyingPowerModel Null = new NullBuyingPowerModel();
private decimal _initialMarginRequirement;
private decimal _maintenanceMarginRequirement;
/// <summary>
/// The percentage used to determine the required unused buying power for the account.
/// </summary>
protected decimal RequiredFreeBuyingPowerPercent { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="BuyingPowerModel"/> with no leverage (1x)
/// </summary>
public BuyingPowerModel()
: this(1m)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BuyingPowerModel"/>
/// </summary>
/// <param name="initialMarginRequirement">The percentage of an order's absolute cost
/// that must be held in free cash in order to place the order</param>
/// <param name="maintenanceMarginRequirement">The percentage of the holding's absolute
/// cost that must be held in free cash in order to avoid a margin call</param>
/// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required
/// unused buying power for the account.</param>
public BuyingPowerModel(
decimal initialMarginRequirement,
decimal maintenanceMarginRequirement,
decimal requiredFreeBuyingPowerPercent
)
{
if (initialMarginRequirement < 0 || initialMarginRequirement > 1)
{
throw new ArgumentException(Messages.BuyingPowerModel.InvalidInitialMarginRequirement);
}
if (maintenanceMarginRequirement < 0 || maintenanceMarginRequirement > 1)
{
throw new ArgumentException(Messages.BuyingPowerModel.InvalidMaintenanceMarginRequirement);
}
if (requiredFreeBuyingPowerPercent < 0 || requiredFreeBuyingPowerPercent > 1)
{
throw new ArgumentException(Messages.BuyingPowerModel.InvalidFreeBuyingPowerPercentRequirement);
}
_initialMarginRequirement = initialMarginRequirement;
_maintenanceMarginRequirement = maintenanceMarginRequirement;
RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent;
}
/// <summary>
/// Initializes a new instance of the <see cref="BuyingPowerModel"/>
/// </summary>
/// <param name="leverage">The leverage</param>
/// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required
/// unused buying power for the account.</param>
public BuyingPowerModel(decimal leverage, decimal requiredFreeBuyingPowerPercent = 0)
{
if (leverage < 1)
{
throw new ArgumentException(Messages.BuyingPowerModel.InvalidLeverage);
}
if (requiredFreeBuyingPowerPercent < 0 || requiredFreeBuyingPowerPercent > 1)
{
throw new ArgumentException(Messages.BuyingPowerModel.InvalidFreeBuyingPowerPercentRequirement);
}
_initialMarginRequirement = 1 / leverage;
_maintenanceMarginRequirement = 1 / leverage;
RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent;
}
/// <summary>
/// Gets the current leverage of the security
/// </summary>
/// <param name="security">The security to get leverage for</param>
/// <returns>The current leverage in the security</returns>
public virtual decimal GetLeverage(Security security)
{
return 1 / _initialMarginRequirement;
}
/// <summary>
/// Sets the leverage for the applicable securities, i.e, equities
/// </summary>
/// <remarks>
/// This is added to maintain backwards compatibility with the old margin/leverage system
/// </remarks>
/// <param name="security"></param>
/// <param name="leverage">The new leverage</param>
public virtual void SetLeverage(Security security, decimal leverage)
{
if (leverage < 1)
{
throw new ArgumentException(Messages.BuyingPowerModel.InvalidLeverage);
}
var margin = 1 / leverage;
_initialMarginRequirement = margin;
_maintenanceMarginRequirement = margin;
}
/// <summary>
/// Gets the total margin required to execute the specified order in units of the account currency including fees
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>The total margin in terms of the currency quoted in the order</returns>
public virtual InitialMargin GetInitialMarginRequiredForOrder(
InitialMarginRequiredForOrderParameters parameters
)
{
//Get the order value from the non-abstract order classes (MarketOrder, LimitOrder, StopMarketOrder)
//Market order is approximated from the current security price and set in the MarketOrder Method in QCAlgorithm.
var fees = parameters.Security.FeeModel.GetOrderFee(
new OrderFeeParameters(parameters.Security,
parameters.Order)).Value;
var feesInAccountCurrency = parameters.CurrencyConverter.
ConvertToAccountCurrency(fees).Amount;
var orderMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Order.Quantity);
return orderMargin + Math.Sign(orderMargin) * feesInAccountCurrency;
}
/// <summary>
/// Gets the margin currently allocated to the specified holding
/// </summary>
/// <param name="parameters">An object containing the security and holdings quantity/cost/value</param>
/// <returns>The maintenance margin required for the provided holdings quantity/cost/value</returns>
public virtual MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
return parameters.AbsoluteHoldingsValue * _maintenanceMarginRequirement;
}
/// <summary>
/// Gets the margin cash available for a trade
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="direction">The direction of the trade</param>
/// <returns>The margin available for the trade</returns>
protected virtual decimal GetMarginRemaining(
SecurityPortfolioManager portfolio,
Security security,
OrderDirection direction
)
{
var totalPortfolioValue = portfolio.TotalPortfolioValue;
var result = portfolio.GetMarginRemaining(totalPortfolioValue);
if (direction != OrderDirection.Hold)
{
var holdings = security.Holdings;
//If the order is in the same direction as holdings, our remaining cash is our cash
//In the opposite direction, our remaining cash is 2 x current value of assets + our cash
if (holdings.IsLong)
{
switch (direction)
{
case OrderDirection.Sell:
result +=
// portion of margin to close the existing position
this.GetMaintenanceMargin(security) +
// portion of margin to open the new position
this.GetInitialMarginRequirement(security, security.Holdings.AbsoluteQuantity);
break;
}
}
else if (holdings.IsShort)
{
switch (direction)
{
case OrderDirection.Buy:
result +=
// portion of margin to close the existing position
this.GetMaintenanceMargin(security) +
// portion of margin to open the new position
this.GetInitialMarginRequirement(security, security.Holdings.AbsoluteQuantity);
break;
}
}
}
result -= totalPortfolioValue * RequiredFreeBuyingPowerPercent;
return result < 0 ? 0 : result;
}
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
/// <param name="parameters">An object containing the security and quantity of shares</param>
/// <returns>The initial margin required for the provided security and quantity</returns>
public virtual InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
var security = parameters.Security;
var quantity = parameters.Quantity;
return security.QuoteCurrency.ConversionRate
* security.SymbolProperties.ContractMultiplier
* security.Price
* quantity
* _initialMarginRequirement;
}
/// <summary>
/// Check if there is sufficient buying power to execute this order.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>Returns buying power information for an order</returns>
public virtual HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters)
{
// short circuit the div 0 case
if (parameters.Order.Quantity == 0)
{
return parameters.Sufficient();
}
var ticket = parameters.Portfolio.Transactions.GetOrderTicket(parameters.Order.Id);
if (ticket == null)
{
return parameters.Insufficient(Messages.BuyingPowerModel.InsufficientBuyingPowerDueToNullOrderTicket(parameters.Order));
}
if (parameters.Order.Type == OrderType.OptionExercise)
{
// for option assignment and exercise orders we look into the requirements to process the underlying security transaction
var option = (Option.Option) parameters.Security;
var underlying = option.Underlying;
if (option.IsAutoExercised(underlying.Close) && underlying.IsTradable)
{
var quantity = option.GetExerciseQuantity(parameters.Order.Quantity);
var newOrder = new LimitOrder
{
Id = parameters.Order.Id,
Time = parameters.Order.Time,
LimitPrice = option.StrikePrice,
Symbol = underlying.Symbol,
Quantity = quantity
};
// we continue with this call for underlying
var parametersForUnderlying = parameters.ForUnderlying(newOrder);
var freeMargin = underlying.BuyingPowerModel.GetBuyingPower(parametersForUnderlying.Portfolio, parametersForUnderlying.Security, parametersForUnderlying.Order.Direction);
// we add the margin used by the option itself
freeMargin += GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(option, -parameters.Order.Quantity));
var initialMarginRequired = underlying.BuyingPowerModel.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(parameters.Portfolio.CashBook, underlying, newOrder));
return HasSufficientBuyingPowerForOrder(parametersForUnderlying, ticket, freeMargin, initialMarginRequired);
}
return parameters.Sufficient();
}
return HasSufficientBuyingPowerForOrder(parameters, ticket);
}
private HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters, OrderTicket ticket,
decimal? freeMarginToUse = null, decimal? initialMarginRequired = null)
{
// When order only reduces or closes a security position, capital is always sufficient
if (parameters.Security.Holdings.Quantity * parameters.Order.Quantity < 0 && Math.Abs(parameters.Security.Holdings.Quantity) >= Math.Abs(parameters.Order.Quantity))
{
return parameters.Sufficient();
}
var freeMargin = freeMarginToUse ?? GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Order.Direction);
var initialMarginRequiredForOrder = initialMarginRequired ?? GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(
parameters.Portfolio.CashBook, parameters.Security, parameters.Order
));
// pro-rate the initial margin required for order based on how much has already been filled
var percentUnfilled = (Math.Abs(parameters.Order.Quantity) - Math.Abs(ticket.QuantityFilled)) / Math.Abs(parameters.Order.Quantity);
var initialMarginRequiredForRemainderOfOrder = percentUnfilled * initialMarginRequiredForOrder;
if (Math.Abs(initialMarginRequiredForRemainderOfOrder) > freeMargin)
{
return parameters.Insufficient(Messages.BuyingPowerModel.InsufficientBuyingPowerDueToUnsufficientMargin(parameters.Order,
initialMarginRequiredForRemainderOfOrder, freeMargin));
}
return parameters.Sufficient();
}
/// <summary>
/// Get the maximum market order quantity to obtain a delta in the buying power used by a security.
/// The deltas sign defines the position side to apply it to, positive long, negative short.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the delta buying power</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
/// <remarks>Used by the margin call model to reduce the position by a delta percent.</remarks>
public virtual GetMaximumOrderQuantityResult GetMaximumOrderQuantityForDeltaBuyingPower(
GetMaximumOrderQuantityForDeltaBuyingPowerParameters parameters)
{
var usedBuyingPower = parameters.Security.BuyingPowerModel.GetReservedBuyingPowerForPosition(
new ReservedBuyingPowerForPositionParameters(parameters.Security)).AbsoluteUsedBuyingPower;
var signedUsedBuyingPower = usedBuyingPower * (parameters.Security.Holdings.IsLong ? 1 : -1);
var targetBuyingPower = signedUsedBuyingPower + parameters.DeltaBuyingPower;
var target = 0m;
if (parameters.Portfolio.TotalPortfolioValue != 0)
{
target = targetBuyingPower / parameters.Portfolio.TotalPortfolioValue;
}
return GetMaximumOrderQuantityForTargetBuyingPower(
new GetMaximumOrderQuantityForTargetBuyingPowerParameters(parameters.Portfolio,
parameters.Security,
target,
parameters.MinimumOrderMarginPortfolioPercentage,
parameters.SilenceNonErrorReasons));
}
/// <summary>
/// Get the maximum market order quantity to obtain a position with a given buying power percentage.
/// Will not take into account free buying power.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
/// <remarks>This implementation ensures that our resulting holdings is less than the target, but it does not necessarily
/// maximize the holdings to meet the target. To do that we need a minimizing algorithm that reduces the difference between
/// the target final margin value and the target holdings margin.</remarks>
public virtual GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters)
{
// this is expensive so lets fetch it once
var totalPortfolioValue = parameters.Portfolio.TotalPortfolioValue;
// adjust target buying power to comply with required Free Buying Power Percent
var signedTargetFinalMarginValue =
parameters.TargetBuyingPower * (totalPortfolioValue - totalPortfolioValue * RequiredFreeBuyingPowerPercent);
// if targeting zero, simply return the negative of the quantity
if (signedTargetFinalMarginValue == 0)
{
return new GetMaximumOrderQuantityResult(-parameters.Security.Holdings.Quantity, string.Empty, false);
}
// we use initial margin requirement here to avoid the duplicate PortfolioTarget.Percent situation:
// PortfolioTarget.Percent(1) -> fills -> PortfolioTarget.Percent(1) _could_ detect free buying power if we use Maintenance requirement here
var signedCurrentUsedMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Security.Holdings.Quantity);
// determine the unit price in terms of the account currency
var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone);
// determine the margin required for 1 unit
var absUnitMargin = this.GetInitialMarginRequirement(parameters.Security, 1);
if (absUnitMargin == 0)
{
return new GetMaximumOrderQuantityResult(0, parameters.Security.Symbol.GetZeroPriceMessage());
}
// Check that the change of margin is above our models minimum percentage change
var absDifferenceOfMargin = Math.Abs(signedTargetFinalMarginValue - signedCurrentUsedMargin);
if (!BuyingPowerModelExtensions.AboveMinimumOrderMarginPortfolioPercentage(parameters.Portfolio,
parameters.MinimumOrderMarginPortfolioPercentage, absDifferenceOfMargin))
{
string reason = null;
if (!parameters.SilenceNonErrorReasons)
{
var minimumValue = totalPortfolioValue * parameters.MinimumOrderMarginPortfolioPercentage;
reason = Messages.BuyingPowerModel.TargetOrderMarginNotAboveMinimum(absDifferenceOfMargin, minimumValue);
}
if (!PortfolioTarget.MinimumOrderMarginPercentageWarningSent.HasValue)
{
// will trigger the warning if it has not already been sent
PortfolioTarget.MinimumOrderMarginPercentageWarningSent = false;
}
return new GetMaximumOrderQuantityResult(0, reason, false);
}
// Use the following loop to converge on a value that places us under our target allocation when adjusted for fees
var lastOrderQuantity = 0m; // For safety check
decimal orderFees = 0m;
decimal signedTargetHoldingsMargin;
decimal orderQuantity;
do
{
// Calculate our order quantity
orderQuantity = GetAmountToOrder(parameters.Security, signedTargetFinalMarginValue, absUnitMargin, out signedTargetHoldingsMargin);
if (orderQuantity == 0)
{
string reason = null;
if (!parameters.SilenceNonErrorReasons)
{
reason = Messages.BuyingPowerModel.OrderQuantityLessThanLotSize(parameters.Security,
signedTargetFinalMarginValue - signedCurrentUsedMargin);
}
return new GetMaximumOrderQuantityResult(0, reason, false);
}
// generate the order
var order = new MarketOrder(parameters.Security.Symbol, orderQuantity, utcTime);
var fees = parameters.Security.FeeModel.GetOrderFee(
new OrderFeeParameters(parameters.Security,
order)).Value;
orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount;
// Update our target portfolio margin allocated when considering fees, then calculate the new FinalOrderMargin
signedTargetFinalMarginValue = (totalPortfolioValue - orderFees - totalPortfolioValue * RequiredFreeBuyingPowerPercent) * parameters.TargetBuyingPower;
// Start safe check after first loop, stops endless recursion
if (lastOrderQuantity == orderQuantity)
{
var message = Messages.BuyingPowerModel.FailedToConvergeOnTheTargetMargin(parameters, signedTargetFinalMarginValue, orderFees);
// Need to add underlying value to message to reproduce with options
if (parameters.Security is Option.Option option && option.Underlying != null)
{
var underlying = option.Underlying;
message += " " + Messages.BuyingPowerModel.FailedToConvergeOnTheTargetMarginUnderlyingSecurityInfo(underlying);
}
throw new ArgumentException(message);
}
lastOrderQuantity = orderQuantity;
}
// Ensure that our target holdings margin will be less than or equal to our target allocated margin
while (Math.Abs(signedTargetHoldingsMargin) > Math.Abs(signedTargetFinalMarginValue));
// add directionality back in
return new GetMaximumOrderQuantityResult(orderQuantity);
}
/// <summary>
/// Helper function that determines the amount to order to get to a given target safely.
/// Meaning it will either be at or just below target always.
/// </summary>
/// <param name="security">Security we are to determine order size for</param>
/// <param name="targetMargin">Target margin allocated</param>
/// <param name="marginForOneUnit">Margin requirement for one unit; used in our initial order guess</param>
/// <param name="finalMargin">Output the final margin allocated to this security</param>
/// <returns>The size of the order to get safely to our target</returns>
public decimal GetAmountToOrder([NotNull]Security security, decimal targetMargin, decimal marginForOneUnit, out decimal finalMargin)
{
var lotSize = security.SymbolProperties.LotSize;
// Start with order size that puts us back to 0, in theory this means current margin is 0
// so we can calculate holdings to get to the new target margin directly. This is very helpful for
// odd cases where margin requirements aren't linear.
var orderSize = -security.Holdings.Quantity;
// Use the margin for one unit to make our initial guess.
orderSize += targetMargin / marginForOneUnit;
// Determine the rounding mode for this order size
var roundingMode = targetMargin < 0
// Ending in short position; orders need to be rounded towards positive so we end up under our target
? MidpointRounding.ToPositiveInfinity
// Ending in long position; orders need to be rounded towards negative so we end up under our target
: MidpointRounding.ToNegativeInfinity;
// Round this order size appropriately
orderSize = orderSize.DiscretelyRoundBy(lotSize, roundingMode);
// Use our model to calculate this final margin as a final check
finalMargin = this.GetInitialMarginRequirement(security,
orderSize + security.Holdings.Quantity);
// Until our absolute final margin is equal to or below target we need to adjust; ensures we don't overshoot target
// This isn't usually the case, but for non-linear margin per unit cases this may be necessary.
// For example https://www.quantconnect.com/forum/discussion/12470, (covered in OptionMarginBuyingPowerModelTests)
var marginDifference = finalMargin - targetMargin;
while ((targetMargin < 0 && marginDifference < 0) || (targetMargin > 0 && marginDifference > 0))
{
// TODO: Can this be smarter about its adjustment, instead of just stepping by lotsize?
// We adjust according to the target margin being a short or long
orderSize += targetMargin < 0 ? lotSize : -lotSize;
// Recalculate final margin with this adjusted orderSize
finalMargin = this.GetInitialMarginRequirement(security,
orderSize + security.Holdings.Quantity);
// Safety check, does not occur in any of our testing, but to be sure we don't enter a endless loop
// have this guy check that the difference between the two is not growing.
var newDifference = finalMargin - targetMargin;
if (Math.Abs(newDifference) > Math.Abs(marginDifference) && Math.Sign(newDifference) == Math.Sign(marginDifference))
{
// We have a problem and are correcting in the wrong direction
var errorMessage = "BuyingPowerModel().GetAmountToOrder(): " +
Messages.BuyingPowerModel.MarginBeingAdjustedInTheWrongDirection(targetMargin, marginForOneUnit, security);
// Need to add underlying value to message to reproduce with options
if (security is Option.Option option && option.Underlying != null)
{
errorMessage += " " + Messages.BuyingPowerModel.MarginBeingAdjustedInTheWrongDirectionUnderlyingSecurityInfo(option.Underlying);
}
throw new ArgumentException(errorMessage);
}
marginDifference = newDifference;
}
return orderSize;
}
/// <summary>
/// Gets the amount of buying power reserved to maintain the specified position
/// </summary>
/// <param name="parameters">A parameters object containing the security</param>
/// <returns>The reserved buying power in account currency</returns>
public virtual ReservedBuyingPowerForPosition GetReservedBuyingPowerForPosition(ReservedBuyingPowerForPositionParameters parameters)
{
var maintenanceMargin = this.GetMaintenanceMargin(parameters.Security);
return parameters.ResultInAccountCurrency(maintenanceMargin);
}
/// <summary>
/// Gets the buying power available for a trade
/// </summary>
/// <param name="parameters">A parameters object containing the algorithm's portfolio, security, and order direction</param>
/// <returns>The buying power available for the trade</returns>
public virtual BuyingPower GetBuyingPower(BuyingPowerParameters parameters)
{
var marginRemaining = GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Direction);
return parameters.ResultInAccountCurrency(marginRemaining);
}
}
}
@@ -0,0 +1,173 @@
/*
* 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;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides extension methods as backwards compatibility shims
/// </summary>
public static class BuyingPowerModelExtensions
{
/// <summary>
/// Gets the amount of buying power reserved to maintain the specified position
/// </summary>
/// <param name="model">The <see cref="IBuyingPowerModel"/></param>
/// <param name="security">The security</param>
/// <returns>The reserved buying power in account currency</returns>
public static decimal GetReservedBuyingPowerForPosition(this IBuyingPowerModel model, Security security)
{
var context = new ReservedBuyingPowerForPositionParameters(security);
var reservedBuyingPower = model.GetReservedBuyingPowerForPosition(context);
return reservedBuyingPower.AbsoluteUsedBuyingPower;
}
/// <summary>
/// Check if there is sufficient buying power to execute this order.
/// </summary>
/// <param name="model">The <see cref="IBuyingPowerModel"/></param>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="order">The order</param>
/// <returns>Returns buying power information for an order</returns>
public static HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(
this IBuyingPowerModel model,
SecurityPortfolioManager portfolio,
Security security,
Order order
)
{
var parameters = new HasSufficientBuyingPowerForOrderParameters(portfolio, security, order);
return model.HasSufficientBuyingPowerForOrder(parameters);
}
/// <summary>
/// Get the maximum market order quantity to obtain a position with a given value in account currency
/// </summary>
/// <param name="model">The <see cref="IBuyingPowerModel"/></param>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="target">The target percent holdings</param>
/// <param name="minimumOrderMarginPortfolioPercentage">Configurable minimum order margin portfolio percentage to ignore orders with unrealistic small sizes</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
public static GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(
this IBuyingPowerModel model,
SecurityPortfolioManager portfolio,
Security security,
decimal target,
decimal minimumOrderMarginPortfolioPercentage
)
{
var parameters = new GetMaximumOrderQuantityForTargetBuyingPowerParameters(portfolio, security, target, minimumOrderMarginPortfolioPercentage);
return model.GetMaximumOrderQuantityForTargetBuyingPower(parameters);
}
/// <summary>
/// Gets the buying power available for a trade
/// </summary>
/// <param name="model">The <see cref="IBuyingPowerModel"/></param>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="direction">The direction of the trade</param>
/// <returns>The buying power available for the trade</returns>
public static decimal GetBuyingPower(
this IBuyingPowerModel model,
SecurityPortfolioManager portfolio,
Security security,
OrderDirection direction
)
{
var context = new BuyingPowerParameters(portfolio, security, direction);
var buyingPower = model.GetBuyingPower(context);
// existing implementations assume certain non-account currency units, so return raw value
return buyingPower.Value;
}
/// <summary>
/// Gets the margin currently allocated to the specified holding
/// </summary>
/// <param name="model">The buying power model</param>
/// <param name="security">The security</param>
/// <returns>The maintenance margin required for the provided holdings quantity/cost/value</returns>
public static decimal GetMaintenanceMargin(this IBuyingPowerModel model, Security security)
{
return model.GetMaintenanceMargin(MaintenanceMarginParameters.ForCurrentHoldings(security));
}
/// <summary>
/// Gets the margin currently allocated to the specified holding
/// </summary>
/// <param name="model">The buying power model</param>
/// <param name="security">The security</param>
/// <param name="quantity">The quantity of shares</param>
/// <returns>The initial margin required for the provided security and quantity</returns>
public static decimal GetInitialMarginRequirement(this IBuyingPowerModel model, Security security, decimal quantity)
{
return model.GetInitialMarginRequirement(new InitialMarginParameters(security, quantity));
}
/// <summary>
/// Helper method to determine if the requested quantity is above the algorithm minimum order margin portfolio percentage
/// </summary>
/// <param name="model">The buying power model</param>
/// <param name="security">The security</param>
/// <param name="quantity">The quantity of shares</param>
/// <param name="portfolioManager">The algorithm's portfolio</param>
/// <param name="minimumOrderMarginPortfolioPercentage">Minimum order margin portfolio percentage to ignore bad orders, orders with unrealistic small sizes</param>
/// <remarks>If we are trading with negative margin remaining this method will return true always</remarks>
/// <returns>True if this order quantity is above the minimum requested</returns>
public static bool AboveMinimumOrderMarginPortfolioPercentage(this IBuyingPowerModel model, Security security,
decimal quantity, SecurityPortfolioManager portfolioManager, decimal minimumOrderMarginPortfolioPercentage)
{
if (minimumOrderMarginPortfolioPercentage == 0)
{
return true;
}
var absFinalOrderMargin = Math.Abs(model.GetInitialMarginRequirement(new InitialMarginParameters(
security, quantity)).Value);
return AboveMinimumOrderMarginPortfolioPercentage(portfolioManager, minimumOrderMarginPortfolioPercentage, absFinalOrderMargin);
}
/// <summary>
/// Helper method to determine if the requested quantity is above the algorithm minimum order margin portfolio percentage
/// </summary>
/// <param name="portfolioManager">The algorithm's portfolio</param>
/// <param name="minimumOrderMarginPortfolioPercentage">Minimum order margin portfolio percentage to ignore bad orders, orders with unrealistic small sizes</param>
/// <param name="absFinalOrderMargin">The calculated order margin value</param>
/// <remarks>If we are trading with negative margin remaining this method will return true always</remarks>
/// <returns>True if this order quantity is above the minimum requested</returns>
public static bool AboveMinimumOrderMarginPortfolioPercentage(SecurityPortfolioManager portfolioManager,
decimal minimumOrderMarginPortfolioPercentage,
decimal absFinalOrderMargin)
{
var minimumValue = portfolioManager.TotalPortfolioValue * minimumOrderMarginPortfolioPercentage;
if (minimumValue > absFinalOrderMargin
// if margin remaining is negative allow the order to pass so we can reduce the position
&& portfolioManager.GetMarginRemaining(portfolioManager.TotalPortfolioValue) > 0)
{
return false;
}
return true;
}
}
}
@@ -0,0 +1,75 @@
/*
* 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 QuantConnect.Orders;
namespace QuantConnect.Securities
{
/// <summary>
/// Defines the parameters for <see cref="IBuyingPowerModel.GetBuyingPower"/>
/// </summary>
public class BuyingPowerParameters
{
/// <summary>
/// Gets the security
/// </summary>
public Security Security { get; }
/// <summary>
/// Gets the algorithm's portfolio
/// </summary>
public SecurityPortfolioManager Portfolio { get; }
/// <summary>
/// Gets the direction in which buying power is to be computed
/// </summary>
public OrderDirection Direction { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BuyingPowerParameters"/> class
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security</param>
/// <param name="direction">The direction to compute buying power in</param>
public BuyingPowerParameters(SecurityPortfolioManager portfolio, Security security, OrderDirection direction)
{
Portfolio = portfolio;
Security = security;
Direction = direction;
}
/// <summary>
/// Creates the result using the specified buying power
/// </summary>
/// <param name="buyingPower">The buying power</param>
/// <param name="currency">The units the buying power is denominated in</param>
/// <returns>The buying power</returns>
public BuyingPower Result(decimal buyingPower, string currency)
{
// TODO: Properly account for 'currency' - not accounted for currently as only performing mechanical refactoring
return new BuyingPower(buyingPower);
}
/// <summary>
/// Creates the result using the specified buying power in units of the account currency
/// </summary>
/// <param name="buyingPower">The buying power</param>
/// <returns>The buying power</returns>
public BuyingPower ResultInAccountCurrency(decimal buyingPower)
{
return new BuyingPower(buyingPower);
}
}
}
+400
View File
@@ -0,0 +1,400 @@
/*
* 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 Newtonsoft.Json;
using ProtoBuf;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Securities.CurrencyConversion;
namespace QuantConnect.Securities
{
/// <summary>
/// Represents a holding of a currency in cash.
/// </summary>
[ProtoContract(SkipConstructor = true)]
public class Cash
{
private ICurrencyConversion _currencyConversion;
private readonly object _locker = new object();
/// <summary>
/// Event fired when this instance is updated
/// <see cref="AddAmount"/>, <see cref="SetAmount"/>, <see cref="Update"/>
/// </summary>
public event EventHandler Updated;
/// <summary>
/// Event fired when this instance's <see cref="CurrencyConversion"/> is set/updated
/// </summary>
public event EventHandler CurrencyConversionUpdated;
/// <summary>
/// Gets the symbols of the securities required to provide conversion rates.
/// If this cash represents the account currency, then an empty enumerable is returned.
/// </summary>
public IEnumerable<Symbol> SecuritySymbols => CurrencyConversion.ConversionRateSecurities.Any()
? CurrencyConversion.ConversionRateSecurities.Select(x => x.Symbol)
// we do this only because Newtonsoft.Json complains about empty enumerables
: new List<Symbol>(0);
/// <summary>
/// Gets the object that calculates the conversion rate to account currency
/// </summary>
[JsonIgnore]
public ICurrencyConversion CurrencyConversion
{
get
{
return _currencyConversion;
}
internal set
{
var lastConversionRate = 0m;
if (_currencyConversion != null)
{
lastConversionRate = _currencyConversion.ConversionRate;
_currencyConversion.ConversionRateUpdated -= OnConversionRateUpdated;
}
_currencyConversion = value;
if (_currencyConversion != null)
{
if (lastConversionRate != 0m)
{
// If a user adds cash with an initial conversion rate and then this is overriden to a SecurityCurrencyConversion,
// we want to keep the previous rate until the new one is updated.
_currencyConversion.ConversionRate = lastConversionRate;
}
_currencyConversion.ConversionRateUpdated += OnConversionRateUpdated;
}
CurrencyConversionUpdated?.Invoke(this, EventArgs.Empty);
}
}
private void OnConversionRateUpdated(object sender, decimal e)
{
OnUpdate();
}
/// <summary>
/// Gets the symbol used to represent this cash
/// </summary>
[ProtoMember(1)]
public string Symbol { get; }
/// <summary>
/// Gets or sets the amount of cash held
/// </summary>
[ProtoMember(2)]
public decimal Amount { get; private set; }
/// <summary>
/// Gets the conversion rate into account currency
/// </summary>
[ProtoMember(3)]
public decimal ConversionRate
{
get
{
return _currencyConversion.ConversionRate;
}
internal set
{
if (_currencyConversion == null)
{
CurrencyConversion = new ConstantCurrencyConversion(Symbol, null, value);
}
_currencyConversion.ConversionRate = value;
}
}
/// <summary>
/// The symbol of the currency, such as $
/// </summary>
[ProtoMember(4)]
public string CurrencySymbol { get; }
/// <summary>
/// Gets the value of this cash in the account currency
/// </summary>
public decimal ValueInAccountCurrency => Amount * ConversionRate;
/// <summary>
/// Initializes a new instance of the <see cref="Cash"/> class
/// </summary>
/// <param name="symbol">The symbol used to represent this cash</param>
/// <param name="amount">The amount of this currency held</param>
/// <param name="conversionRate">The initial conversion rate of this currency into the <see cref="CashBook.AccountCurrency"/></param>
public Cash(string symbol, decimal amount, decimal conversionRate)
{
if (string.IsNullOrEmpty(symbol))
{
throw new ArgumentException(Messages.Cash.NullOrEmptyCashSymbol);
}
Amount = amount;
Symbol = symbol.LazyToUpper();
CurrencySymbol = Currencies.GetCurrencySymbol(Symbol);
CurrencyConversion = new ConstantCurrencyConversion(Symbol, null, conversionRate);
}
/// <summary>
/// Marks this cash object's conversion rate as being potentially outdated
/// </summary>
public void Update()
{
_currencyConversion.Update();
}
/// <summary>
/// Adds the specified amount of currency to this Cash instance and returns the new total.
/// This operation is thread-safe
/// </summary>
/// <param name="amount">The amount of currency to be added</param>
/// <returns>The amount of currency directly after the addition</returns>
public decimal AddAmount(decimal amount)
{
lock (_locker)
{
Amount += amount;
}
OnUpdate();
return Amount;
}
/// <summary>
/// Sets the Quantity to the specified amount
/// </summary>
/// <param name="amount">The amount to set the quantity to</param>
public void SetAmount(decimal amount)
{
var updated = false;
// lock can be null when proto deserializing this instance
lock (_locker ?? new object())
{
if (Amount != amount)
{
Amount = amount;
// only update if there was actually one
updated = true;
}
}
if (updated)
{
OnUpdate();
}
}
/// <summary>
/// Ensures that we have a data feed to convert this currency into the base currency.
/// This will add a <see cref="SubscriptionDataConfig"/> and create a <see cref="Security"/> at the lowest resolution if one is not found.
/// </summary>
/// <param name="securities">The security manager</param>
/// <param name="subscriptions">The subscription manager used for searching and adding subscriptions</param>
/// <param name="marketMap">The market map that decides which market the new security should be in</param>
/// <param name="changes">Will be used to consume <see cref="SecurityChanges.AddedSecurities"/></param>
/// <param name="securityService">Will be used to create required new <see cref="Security"/></param>
/// <param name="accountCurrency">The account currency</param>
/// <param name="defaultResolution">The default resolution to use for the internal subscriptions</param>
/// <returns>Returns the added <see cref="SubscriptionDataConfig"/>, otherwise null</returns>
public List<SubscriptionDataConfig> EnsureCurrencyDataFeed(SecurityManager securities,
SubscriptionManager subscriptions,
IReadOnlyDictionary<SecurityType, string> marketMap,
SecurityChanges changes,
ISecurityService securityService,
string accountCurrency,
Resolution defaultResolution = Resolution.Minute
)
{
// this gets called every time we add securities using universe selection,
// so must of the time we've already resolved the value and don't need to again
if (CurrencyConversion.DestinationCurrency != null)
{
return null;
}
if (Symbol == accountCurrency)
{
CurrencyConversion = ConstantCurrencyConversion.Identity(accountCurrency);
return null;
}
// existing securities
var securitiesToSearch = securities.Select(kvp => kvp.Value)
.Concat(changes.AddedSecurities)
.Where(s => ProvidesConversionRate(s.Type));
// Create a SecurityType to Market mapping with the markets from SecurityManager members
var markets = securities.Select(x => x.Key)
.GroupBy(x => x.SecurityType)
.ToDictionary(x => x.Key, y => y.Select(symbol => symbol.ID.Market).ToHashSet());
if (markets.ContainsKey(SecurityType.Cfd) && !markets.ContainsKey(SecurityType.Forex))
{
markets.Add(SecurityType.Forex, markets[SecurityType.Cfd]);
}
if (markets.ContainsKey(SecurityType.Forex) && !markets.ContainsKey(SecurityType.Cfd))
{
markets.Add(SecurityType.Cfd, markets[SecurityType.Forex]);
}
var forexEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Forex, marketMap, markets);
var cfdEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Cfd, marketMap, markets);
var cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Crypto, marketMap, markets);
if (marketMap.TryGetValue(SecurityType.CryptoFuture, out var cryptoFutureMarket) && cryptoFutureMarket == Market.DYDX)
{
// Put additional logic for dYdX crypto futures as they don't have Crypto (Spot) market
// Also need to add them first to give the priority
// TODO: remove once dydx SPOT market will be imlemented
cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.CryptoFuture, marketMap, markets).Concat(cryptoEntries);
}
var potentialEntries = forexEntries
.Concat(cfdEntries)
.Concat(cryptoEntries)
.ToList();
// Special case for crypto markets without direct pairs (They wont be found by the above)
// This allows us to add cash for "StableCoins" that are 1-1 with our account currency without needing a conversion security.
// Check out the StableCoinsWithoutPairs static var for those that are missing their 1-1 conversion pairs
if (marketMap.TryGetValue(SecurityType.Crypto, out var market) && Currencies.IsStableCoinWithoutPair(accountCurrency, Symbol, market))
{
CurrencyConversion = ConstantCurrencyConversion.Identity(accountCurrency, Symbol);
return null;
}
if (!potentialEntries.Any(x =>
Symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) ||
Symbol == x.Value.QuoteCurrency))
{
// currency not found in any tradeable pair
Log.Error(Messages.Cash.NoTradablePairFoundForCurrencyConversion(Symbol, accountCurrency, marketMap.Where(kvp => ProvidesConversionRate(kvp.Key))));
CurrencyConversion = ConstantCurrencyConversion.Null(accountCurrency, Symbol);
return null;
}
var requiredSecurities = new List<SubscriptionDataConfig>();
var potentials = potentialEntries
.Select(x => QuantConnect.Symbol.Create(x.Key.Symbol, x.Key.SecurityType, x.Key.Market));
var minimumResolution = subscriptions.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(defaultResolution).Min();
var makeNewSecurity = new Func<Symbol, Security>(symbol =>
{
var securityType = symbol.ID.SecurityType;
// use the first subscription defined in the subscription manager
var type = subscriptions.LookupSubscriptionConfigDataTypes(securityType, minimumResolution, false).First();
var objectType = type.Item1;
var tickType = type.Item2;
// set this as an internal feed so that the data doesn't get sent into the algorithm's OnData events
var config = subscriptions.SubscriptionDataConfigService.Add(symbol,
minimumResolution,
fillForward: true,
extendedMarketHours: false,
isInternalFeed: true,
subscriptionDataTypes: new List<Tuple<Type, TickType>>
{new Tuple<Type, TickType>(objectType, tickType)}).First();
var newSecurity = securityService.CreateSecurity(symbol,
config,
addToSymbolCache: false,
// All securities added for currency conversion will be seeded in batch after all are created
seedSecurity: false);
Log.Trace("Cash.EnsureCurrencyDataFeed(): " + Messages.Cash.AddingSecuritySymbolForCashCurrencyFeed(symbol, Symbol));
securities.Add(symbol, newSecurity);
requiredSecurities.Add(config);
return newSecurity;
});
CurrencyConversion = SecurityCurrencyConversion.LinearSearch(Symbol,
accountCurrency,
securitiesToSearch.ToList(),
potentials,
makeNewSecurity);
return requiredSecurities;
}
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="Cash"/>.
/// </summary>
/// <returns>A <see cref="string"/> that represents the current <see cref="Cash"/>.</returns>
public override string ToString()
{
return ToString(Currencies.USD);
}
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="Cash"/>.
/// </summary>
/// <returns>A <see cref="string"/> that represents the current <see cref="Cash"/>.</returns>
public string ToString(string accountCurrency)
{
return Messages.Cash.ToString(this, accountCurrency);
}
private static IEnumerable<KeyValuePair<SecurityDatabaseKey, SymbolProperties>> GetAvailableSymbolPropertiesDatabaseEntries(
SecurityType securityType,
IReadOnlyDictionary<SecurityType, string> marketMap,
IReadOnlyDictionary<SecurityType, HashSet<string>> markets
)
{
var marketJoin = new HashSet<string>();
{
string market;
if (marketMap.TryGetValue(securityType, out market))
{
marketJoin.Add(market);
}
HashSet<string> existingMarkets;
if (markets.TryGetValue(securityType, out existingMarkets))
{
foreach (var existingMarket in existingMarkets)
{
marketJoin.Add(existingMarket);
}
}
}
return marketJoin.SelectMany(market => SymbolPropertiesDatabase.FromDataFolder()
.GetSymbolPropertiesList(market, securityType));
}
private static bool ProvidesConversionRate(SecurityType securityType)
{
return securityType == SecurityType.Forex || securityType == SecurityType.Crypto || securityType == SecurityType.Cfd;
}
private void OnUpdate()
{
Updated?.Invoke(this, EventArgs.Empty);
}
}
}
+95
View File
@@ -0,0 +1,95 @@
/*
* 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 ProtoBuf;
namespace QuantConnect.Securities
{
/// <summary>
/// Represents a cash amount which can be converted to account currency using a currency converter
/// </summary>
[ProtoContract(SkipConstructor = true)]
public struct CashAmount
{
/// <summary>
/// The amount of cash
/// </summary>
[ProtoMember(1)]
public decimal Amount { get; }
/// <summary>
/// The currency in which the cash amount is denominated
/// </summary>
[ProtoMember(2)]
public string Currency { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CashAmount"/> class
/// </summary>
/// <param name="amount">The amount</param>
/// <param name="currency">The currency</param>
public CashAmount(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
/// <summary>
/// Will determine if two <see cref="CashAmount"/> instances are equal
/// Useful to compare against the default instance
/// </summary>
/// <returns>True if <see cref="Currency"/> and <see cref="Amount"/> are equal</returns>
public static bool operator ==(CashAmount lhs, CashAmount rhs)
{
return Equals(lhs, rhs);
}
/// <summary>
/// Will determine if two <see cref="CashAmount"/> instances are different
/// Useful to compare against the default instance
/// </summary>
/// <returns>True if <see cref="Currency"/> or <see cref="Amount"/> are different</returns>
public static bool operator !=(CashAmount lhs, CashAmount rhs)
{
return !Equals(lhs, rhs);
}
/// <summary>
/// Used to compare two <see cref="CashAmount"/> instances.
/// Useful to compare against the default instance
/// </summary>
/// <param name="obj">The other object to compare with</param>
/// <returns>True if <see cref="Currency"/> and <see cref="Amount"/> are equal</returns>
public override bool Equals(object obj)
{
if (obj is CashAmount)
{
var cashAmountObj = (CashAmount) obj;
return Amount == cashAmountObj.Amount
&& Currency == cashAmountObj.Currency;
}
return false;
}
/// <summary>
/// Get Hash Code for this Object
/// </summary>
/// <returns>Integer Hash Code</returns>
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}
+474
View File
@@ -0,0 +1,474 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Util;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides a means of keeping track of the different cash holdings of an algorithm
/// </summary>
public class CashBook : ExtendedDictionary<string, Cash>, IDictionary<string, Cash>, ICurrencyConverter
{
private string _accountCurrency;
/// <summary>
/// Event fired when a <see cref="Cash"/> instance is added or removed, and when
/// the <see cref="Cash.Updated"/> is triggered for the currently hold instances
/// </summary>
public event EventHandler<CashBookUpdatedEventArgs> Updated;
/// <summary>
/// Gets the base currency used
/// </summary>
public string AccountCurrency
{
get { return _accountCurrency; }
set
{
var amount = 0m;
Cash accountCurrency;
// remove previous account currency if any
if (!_accountCurrency.IsNullOrEmpty()
&& TryGetValue(_accountCurrency, out accountCurrency))
{
amount = accountCurrency.Amount;
Remove(_accountCurrency);
}
// add new account currency using same amount as previous
_accountCurrency = value.LazyToUpper();
Add(_accountCurrency, new Cash(_accountCurrency, amount, 1.0m));
}
}
/// <summary>
/// No need for concurrent collection, they are expensive. Currencies barely change and only on the start
/// by the main thread, so if they do we will just create a new collection, reference change is atomic
/// </summary>
private Dictionary<string, Cash> _currencies;
/// <summary>
/// Gets the total value of the cash book in units of the base currency
/// </summary>
public decimal TotalValueInAccountCurrency
{
get
{
return this.Aggregate(0m, (d, pair) => d + pair.Value.ValueInAccountCurrency);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CashBook"/> class.
/// </summary>
public CashBook()
{
_currencies = new();
AccountCurrency = Currencies.USD;
}
/// <summary>
/// Adds a new cash of the specified symbol and quantity
/// </summary>
/// <param name="symbol">The symbol used to reference the new cash</param>
/// <param name="quantity">The amount of new cash to start</param>
/// <param name="conversionRate">The conversion rate used to determine the initial
/// portfolio value/starting capital impact caused by this currency position.</param>
/// <returns>The added cash instance</returns>
public Cash Add(string symbol, decimal quantity, decimal conversionRate)
{
var cash = new Cash(symbol, quantity, conversionRate);
// let's return the cash instance we are using
return AddIternal(symbol, cash);
}
/// <summary>
/// Checks the current subscriptions and adds necessary currency pair feeds to provide real time conversion data
/// </summary>
/// <param name="securities">The SecurityManager for the algorithm</param>
/// <param name="subscriptions">The SubscriptionManager for the algorithm</param>
/// <param name="marketMap">The market map that decides which market the new security should be in</param>
/// <param name="changes">Will be used to consume <see cref="SecurityChanges.AddedSecurities"/></param>
/// <param name="securityService">Will be used to create required new <see cref="Security"/></param>
/// <param name="defaultResolution">The default resolution to use for the internal subscriptions</param>
/// <returns>Returns a list of added currency <see cref="SubscriptionDataConfig"/></returns>
public List<SubscriptionDataConfig> EnsureCurrencyDataFeeds(SecurityManager securities,
SubscriptionManager subscriptions,
IReadOnlyDictionary<SecurityType, string> marketMap,
SecurityChanges changes,
ISecurityService securityService,
Resolution defaultResolution = Resolution.Minute)
{
var addedSubscriptionDataConfigs = new List<SubscriptionDataConfig>();
foreach (var kvp in _currencies)
{
var cash = kvp.Value;
var subscriptionDataConfigs = cash.EnsureCurrencyDataFeed(
securities,
subscriptions,
marketMap,
changes,
securityService,
AccountCurrency,
defaultResolution);
if (subscriptionDataConfigs != null)
{
foreach (var subscriptionDataConfig in subscriptionDataConfigs)
{
addedSubscriptionDataConfigs.Add(subscriptionDataConfig);
}
}
}
return addedSubscriptionDataConfigs;
}
/// <summary>
/// Converts a quantity of source currency units into the specified destination currency
/// </summary>
/// <param name="sourceQuantity">The quantity of source currency to be converted</param>
/// <param name="sourceCurrency">The source currency symbol</param>
/// <param name="destinationCurrency">The destination currency symbol</param>
/// <returns>The converted value</returns>
public decimal Convert(decimal sourceQuantity, string sourceCurrency, string destinationCurrency)
{
if (sourceQuantity == 0)
{
return 0;
}
var source = this[sourceCurrency];
var destination = this[destinationCurrency];
if (source.ConversionRate == 0)
{
throw new ArgumentException(Messages.CashBook.ConversionRateNotFound(sourceCurrency));
}
if (destination.ConversionRate == 0)
{
throw new ArgumentException(Messages.CashBook.ConversionRateNotFound(destinationCurrency));
}
var conversionRate = source.ConversionRate / destination.ConversionRate;
return sourceQuantity * conversionRate;
}
/// <summary>
/// Converts a quantity of source currency units into the account currency
/// </summary>
/// <param name="sourceQuantity">The quantity of source currency to be converted</param>
/// <param name="sourceCurrency">The source currency symbol</param>
/// <returns>The converted value</returns>
public decimal ConvertToAccountCurrency(decimal sourceQuantity, string sourceCurrency)
{
if (sourceCurrency == AccountCurrency)
{
return sourceQuantity;
}
return Convert(sourceQuantity, sourceCurrency, AccountCurrency);
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return Messages.CashBook.ToString(this);
}
#region IDictionary Implementation
/// <summary>
/// Gets the count of Cash items in this CashBook.
/// </summary>
/// <value>The count.</value>
public override int Count
{
get
{
return _currencies.Count;
}
}
/// <summary>
/// Gets a value indicating whether this instance is read only.
/// </summary>
/// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value>
public override bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Add the specified item to this CashBook.
/// </summary>
/// <param name="item">KeyValuePair of symbol -> Cash item</param>
public void Add(KeyValuePair<string, Cash> item)
{
Add(item.Key, item.Value);
}
/// <summary>
/// Add the specified key and value.
/// </summary>
/// <param name="symbol">The symbol of the Cash value.</param>
/// <param name="value">Value.</param>
public void Add(string symbol, Cash value)
{
AddIternal(symbol, value);
}
/// <summary>
/// Clear this instance of all Cash entries.
/// </summary>
public override void Clear()
{
_currencies = new();
OnUpdate(CashBookUpdateType.Removed, null);
}
/// <summary>
/// Remove the Cash item corresponding to the specified symbol
/// </summary>
/// <param name="symbol">The symbolto be removed</param>
public override bool Remove(string symbol)
{
return Remove(symbol, calledInternally: false);
}
/// <summary>
/// Remove the specified item.
/// </summary>
/// <param name="item">Item.</param>
public bool Remove(KeyValuePair<string, Cash> item)
{
return Remove(item.Key);
}
/// <summary>
/// Determines whether the current instance contains an entry with the specified symbol.
/// </summary>
/// <returns><c>true</c>, if key was contained, <c>false</c> otherwise.</returns>
/// <param name="symbol">Key.</param>
public override bool ContainsKey(string symbol)
{
return _currencies.ContainsKey(symbol);
}
/// <summary>
/// Try to get the value.
/// </summary>
/// <remarks>To be added.</remarks>
/// <returns><c>true</c>, if get value was tryed, <c>false</c> otherwise.</returns>
/// <param name="symbol">The symbol.</param>
/// <param name="value">Value.</param>
public override bool TryGetValue(string symbol, out Cash value)
{
return _currencies.TryGetValue(symbol, out value);
}
/// <summary>
/// Determines whether the current collection contains the specified value.
/// </summary>
/// <param name="item">Item.</param>
public bool Contains(KeyValuePair<string, Cash> item)
{
return _currencies.Contains(item);
}
/// <summary>
/// Copies to the specified array.
/// </summary>
/// <param name="array">Array.</param>
/// <param name="arrayIndex">Array index.</param>
public void CopyTo(KeyValuePair<string, Cash>[] array, int arrayIndex)
{
((IDictionary<string, Cash>) _currencies).CopyTo(array, arrayIndex);
}
/// <summary>
/// Gets or sets the <see cref="QuantConnect.Securities.Cash"/> with the specified symbol.
/// </summary>
/// <param name="symbol">Symbol.</param>
public override Cash this[string symbol]
{
get
{
if (symbol == Currencies.NullCurrency)
{
throw new InvalidOperationException(Messages.CashBook.UnexpectedRequestForNullCurrency);
}
Cash cash;
if (!_currencies.TryGetValue(symbol, out cash))
{
throw new KeyNotFoundException(Messages.CashBook.CashSymbolNotFound(symbol));
}
return cash;
}
set
{
Add(symbol, value);
}
}
/// <summary>
/// Gets the keys.
/// </summary>
/// <value>The keys.</value>
public ICollection<string> Keys => _currencies.Keys;
/// <summary>
/// Gets the values.
/// </summary>
/// <value>The values.</value>
public ICollection<Cash> Values => _currencies.Values;
/// <summary>
/// Gets the keys.
/// </summary>
/// <value>The keys.</value>
protected override IEnumerable<string> GetKeys => Keys;
/// <summary>
/// Gets the values.
/// </summary>
/// <value>The values.</value>
protected override IEnumerable<Cash> GetValues => Values;
/// <summary>
/// Gets all the items in the dictionary
/// </summary>
/// <returns>All the items in the dictionary</returns>
public override IEnumerable<KeyValuePair<string, Cash>> GetItems() => _currencies;
/// <summary>
/// Gets the enumerator.
/// </summary>
/// <returns>The enumerator.</returns>
public IEnumerator<KeyValuePair<string, Cash>> GetEnumerator()
{
return _currencies.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _currencies.GetEnumerator();
}
#endregion
#region ICurrencyConverter Implementation
/// <summary>
/// Converts a cash amount to the account currency
/// </summary>
/// <param name="cashAmount">The <see cref="CashAmount"/> instance to convert</param>
/// <returns>A new <see cref="CashAmount"/> instance denominated in the account currency</returns>
public CashAmount ConvertToAccountCurrency(CashAmount cashAmount)
{
if (cashAmount.Currency == AccountCurrency)
{
return cashAmount;
}
var amount = Convert(cashAmount.Amount, cashAmount.Currency, AccountCurrency);
return new CashAmount(amount, AccountCurrency);
}
#endregion
private Cash AddIternal(string symbol, Cash value)
{
if (symbol == Currencies.NullCurrency)
{
return null;
}
if (!_currencies.TryGetValue(symbol, out var cash))
{
// we link our Updated event with underlying cash instances
// so interested listeners just subscribe to our event
value.Updated += OnCashUpdate;
var newCurrencies = new Dictionary<string, Cash>(_currencies)
{
[symbol] = value
};
_currencies = newCurrencies;
OnUpdate(CashBookUpdateType.Added, value);
return value;
}
else
{
// override the values, it will trigger an update event already
// we keep the instance because it might be used by securities already
cash.ConversionRate = value.ConversionRate;
cash.SetAmount(value.Amount);
return cash;
}
}
private bool Remove(string symbol, bool calledInternally)
{
Cash cash = null;
var newCurrencies = new Dictionary<string, Cash>(_currencies);
var removed = newCurrencies.Remove(symbol, out cash);
_currencies = newCurrencies;
if (!removed)
{
if (!calledInternally)
{
Log.Error("CashBook.Remove(): " + Messages.CashBook.FailedToRemoveRecord(symbol));
}
}
else
{
cash.Updated -= OnCashUpdate;
if (!calledInternally)
{
OnUpdate(CashBookUpdateType.Removed, cash);
}
}
return removed;
}
private void OnCashUpdate(object sender, EventArgs eventArgs)
{
OnUpdate(CashBookUpdateType.Updated, sender as Cash);
}
private void OnUpdate(CashBookUpdateType updateType, Cash cash)
{
Updated?.Invoke(this, new CashBookUpdatedEventArgs(updateType, cash));
}
}
}
@@ -0,0 +1,46 @@
/*
* 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;
namespace QuantConnect.Securities
{
/// <summary>
/// Event fired when the cash book is updated
/// </summary>
public class CashBookUpdatedEventArgs : EventArgs
{
/// <summary>
/// The update type
/// </summary>
public CashBookUpdateType UpdateType { get; }
/// <summary>
/// The updated cash instance.
/// </summary>
/// <remarks>This will be null for <see cref="CashBookUpdateType.Removed"/> events that clear the whole cash book</remarks>
public Cash Cash { get; }
/// <summary>
/// Creates a new instance
/// </summary>
public CashBookUpdatedEventArgs(CashBookUpdateType type, Cash cash)
{
UpdateType = type;
Cash = cash;
}
}
}
+463
View File
@@ -0,0 +1,463 @@
/*
* 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 QuantConnect.Orders;
using QuantConnect.Orders.Fees;
namespace QuantConnect.Securities
{
/// <summary>
/// Represents a buying power model for cash accounts
/// </summary>
public class CashBuyingPowerModel : BuyingPowerModel
{
/// <summary>
/// Initializes a new instance of the <see cref="CashBuyingPowerModel"/> class
/// </summary>
public CashBuyingPowerModel()
: base(1m, 0m, 0m)
{
}
/// <summary>
/// Gets the current leverage of the security
/// </summary>
/// <param name="security">The security to get leverage for</param>
/// <returns>The current leverage in the security</returns>
public override decimal GetLeverage(Security security)
{
// Always returns 1. Cash accounts have no leverage.
return 1m;
}
/// <summary>
/// Sets the leverage for the applicable securities, i.e, equities
/// </summary>
/// <remarks>
/// This is added to maintain backwards compatibility with the old margin/leverage system
/// </remarks>
/// <param name="security">The security to set leverage for</param>
/// <param name="leverage">The new leverage</param>
public override void SetLeverage(Security security, decimal leverage)
{
if (leverage != 1)
{
throw new InvalidOperationException(Messages.CashBuyingPowerModel.UnsupportedLeverage);
}
}
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
/// <param name="parameters">An object containing the security and quantity of shares</param>
public override InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
var security = parameters.Security;
var quantity = parameters.Quantity;
return security.QuoteCurrency.ConversionRate
* security.SymbolProperties.ContractMultiplier
* security.Price
* quantity;
}
/// <summary>
/// Check if there is sufficient buying power to execute this order.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>Returns buying power information for an order</returns>
public override HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters)
{
var baseCurrency = parameters.Security as IBaseCurrencySymbol;
if (baseCurrency == null)
{
return parameters.Insufficient(Messages.CashBuyingPowerModel.UnsupportedSecurity(parameters.Security));
}
decimal totalQuantity;
decimal orderQuantity;
if (parameters.Order.Direction == OrderDirection.Buy)
{
// quantity available for buying in quote currency
totalQuantity = parameters.Security.QuoteCurrency.Amount;
orderQuantity = parameters.Order.AbsoluteQuantity * GetOrderPrice(parameters.Security, parameters.Order);
}
else
{
// quantity available for selling in base currency
totalQuantity = baseCurrency.BaseCurrency.Amount;
orderQuantity = parameters.Order.AbsoluteQuantity;
}
// calculate reserved quantity for open orders (in quote or base currency depending on direction)
var openOrdersReservedQuantity = GetOpenOrdersReservedQuantity(parameters.Portfolio, parameters.Security, parameters.Order);
if (parameters.Order.Direction == OrderDirection.Sell)
{
// can sell available and non-reserved quantities
if (orderQuantity <= totalQuantity - openOrdersReservedQuantity)
{
return parameters.Sufficient();
}
return parameters.Insufficient(Messages.CashBuyingPowerModel.SellOrderShortHoldingsNotSupported(totalQuantity,
openOrdersReservedQuantity, orderQuantity, baseCurrency));
}
var maximumQuantity = 0m;
if (parameters.Order.Type == OrderType.Market)
{
// include existing holdings (in quote currency)
var holdingsValue =
parameters.Portfolio.CashBook.Convert(baseCurrency.BaseCurrency.Amount, baseCurrency.BaseCurrency.Symbol, parameters.Security.QuoteCurrency.Symbol);
// find a target value in account currency for buy market orders
var targetValue =
parameters.Portfolio.CashBook.ConvertToAccountCurrency(totalQuantity - openOrdersReservedQuantity + holdingsValue,
parameters.Security.QuoteCurrency.Symbol);
// convert the target into a percent in relation to TPV
var targetPercent = parameters.Portfolio.TotalPortfolioValue == 0 ? 0 : targetValue / parameters.Portfolio.TotalPortfolioValue;
// maximum quantity that can be bought (in quote currency)
maximumQuantity =
GetMaximumOrderQuantityForTargetBuyingPower(
new GetMaximumOrderQuantityForTargetBuyingPowerParameters(parameters.Portfolio, parameters.Security, targetPercent, 0)).Quantity * GetOrderPrice(parameters.Security, parameters.Order);
if (orderQuantity <= Math.Abs(maximumQuantity))
{
return parameters.Sufficient();
}
return parameters.Insufficient(Messages.CashBuyingPowerModel.BuyOrderQuantityGreaterThanMaxForBuyingPower(totalQuantity,
maximumQuantity, openOrdersReservedQuantity, orderQuantity, baseCurrency, parameters.Security, parameters.Order));
}
// for limit orders, add fees to the order cost
var orderFee = 0m;
if (parameters.Order.Type == OrderType.Limit)
{
var fee = parameters.Security.FeeModel.GetOrderFee(
new OrderFeeParameters(parameters.Security,
parameters.Order)).Value;
orderFee = parameters.Portfolio.CashBook.Convert(
fee.Amount,
fee.Currency,
parameters.Security.QuoteCurrency.Symbol);
}
maximumQuantity = totalQuantity - openOrdersReservedQuantity - orderFee;
if (orderQuantity <= maximumQuantity)
{
return parameters.Sufficient();
}
return parameters.Insufficient(Messages.CashBuyingPowerModel.BuyOrderQuantityGreaterThanMaxForBuyingPower(totalQuantity,
maximumQuantity, openOrdersReservedQuantity, orderQuantity, baseCurrency, parameters.Security, parameters.Order));
}
/// <summary>
/// Get the maximum market order quantity to obtain a delta in the buying power used by a security.
/// The deltas sign defines the position side to apply it to, positive long, negative short.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the delta buying power</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
/// <remarks>Used by the margin call model to reduce the position by a delta percent.</remarks>
public override GetMaximumOrderQuantityResult GetMaximumOrderQuantityForDeltaBuyingPower(
GetMaximumOrderQuantityForDeltaBuyingPowerParameters parameters)
{
throw new NotImplementedException(Messages.CashBuyingPowerModel.GetMaximumOrderQuantityForDeltaBuyingPowerNotImplemented);
}
/// <summary>
/// Get the maximum market order quantity to obtain a position with a given buying power percentage.
/// Will not take into account free buying power.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
public override GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters)
{
var targetPortfolioValue = parameters.TargetBuyingPower * parameters.Portfolio.TotalPortfolioValue;
// no shorting allowed
if (targetPortfolioValue < 0)
{
return new GetMaximumOrderQuantityResult(0, Messages.CashBuyingPowerModel.ShortingNotSupported);
}
var baseCurrency = parameters.Security as IBaseCurrencySymbol;
if (baseCurrency == null)
{
return new GetMaximumOrderQuantityResult(0, Messages.CashBuyingPowerModel.InvalidSecurity);
}
// if target value is zero, return amount of base currency available to sell
if (targetPortfolioValue == 0)
{
return new GetMaximumOrderQuantityResult(-baseCurrency.BaseCurrency.Amount);
}
// convert base currency cash to account currency
var baseCurrencyPosition = parameters.Portfolio.CashBook.ConvertToAccountCurrency(
baseCurrency.BaseCurrency.Amount,
baseCurrency.BaseCurrency.Symbol);
// remove directionality, we'll work in the land of absolutes
var targetOrderValue = Math.Abs(targetPortfolioValue - baseCurrencyPosition);
var direction = targetPortfolioValue > baseCurrencyPosition ? OrderDirection.Buy : OrderDirection.Sell;
// determine the unit price in terms of the account currency
var unitPrice = direction == OrderDirection.Buy ? parameters.Security.AskPrice : parameters.Security.BidPrice;
unitPrice *= parameters.Security.QuoteCurrency.ConversionRate * parameters.Security.SymbolProperties.ContractMultiplier;
if (unitPrice == 0)
{
if (parameters.Security.QuoteCurrency.ConversionRate == 0)
{
return new GetMaximumOrderQuantityResult(0,
Messages.CashBuyingPowerModel.NoDataInInternalCashFeedYet(parameters.Security, parameters.Portfolio));
}
if (parameters.Security.SymbolProperties.ContractMultiplier == 0)
{
return new GetMaximumOrderQuantityResult(0, Messages.CashBuyingPowerModel.ZeroContractMultiplier(parameters.Security));
}
// security.Price == 0
return new GetMaximumOrderQuantityResult(0, parameters.Security.Symbol.GetZeroPriceMessage());
}
// continue iterating while we do not have enough cash for the order
decimal orderFees = 0;
decimal currentOrderValue = 0;
// compute the initial order quantity
var orderQuantity = targetOrderValue / unitPrice;
// rounding off Order Quantity to the nearest multiple of Lot Size
orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize;
if (orderQuantity == 0)
{
string reason = null;
if (!parameters.SilenceNonErrorReasons)
{
reason = Messages.CashBuyingPowerModel.OrderQuantityLessThanLotSize(parameters.Security);
}
return new GetMaximumOrderQuantityResult(0, reason, false);
}
// Just in case...
var lastOrderQuantity = 0m;
var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone);
do
{
// Each loop will reduce the order quantity based on the difference between
// (cashRequired + orderFees) and targetOrderValue
if (currentOrderValue > targetOrderValue)
{
var currentOrderValuePerUnit = currentOrderValue / orderQuantity;
var amountOfOrdersToRemove = (currentOrderValue - targetOrderValue) / currentOrderValuePerUnit;
if (amountOfOrdersToRemove < parameters.Security.SymbolProperties.LotSize)
{
// we will always substract at leat 1 LotSize
amountOfOrdersToRemove = parameters.Security.SymbolProperties.LotSize;
}
orderQuantity -= amountOfOrdersToRemove;
}
// rounding off Order Quantity to the nearest multiple of Lot Size
orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize;
if (orderQuantity <= 0)
{
return new GetMaximumOrderQuantityResult(0,
Messages.CashBuyingPowerModel.OrderQuantityLessThanLotSize(parameters.Security) +
Messages.CashBuyingPowerModel.OrderQuantityLessThanLotSizeOrderDetails(targetOrderValue, orderQuantity, orderFees)
);
}
if (lastOrderQuantity == orderQuantity)
{
throw new ArgumentException(Messages.CashBuyingPowerModel.FailedToConvergeOnTargetOrderValue(targetOrderValue, currentOrderValue,
orderQuantity, orderFees, parameters.Security));
}
lastOrderQuantity = orderQuantity;
// generate the order
var order = new MarketOrder(parameters.Security.Symbol, orderQuantity, utcTime);
var orderValue = orderQuantity * unitPrice;
var fees = parameters.Security.FeeModel.GetOrderFee(
new OrderFeeParameters(parameters.Security,
order)).Value;
orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount;
currentOrderValue = orderValue + orderFees;
} while (currentOrderValue > targetOrderValue);
// add directionality back in
return new GetMaximumOrderQuantityResult((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity);
}
/// <summary>
/// Gets the amount of buying power reserved to maintain the specified position
/// </summary>
/// <param name="parameters">A parameters object containing the security</param>
/// <returns>The reserved buying power in account currency</returns>
public override ReservedBuyingPowerForPosition GetReservedBuyingPowerForPosition(ReservedBuyingPowerForPositionParameters parameters)
{
// Always returns 0. Since we're purchasing currencies outright, the position doesn't consume buying power
return parameters.ResultInAccountCurrency(0m);
}
/// <summary>
/// Gets the buying power available for a trade
/// </summary>
/// <param name="parameters">A parameters object containing the algorithm's portfolio, security, and order direction</param>
/// <returns>The buying power available for the trade</returns>
public override BuyingPower GetBuyingPower(BuyingPowerParameters parameters)
{
var security = parameters.Security;
var portfolio = parameters.Portfolio;
var direction = parameters.Direction;
var baseCurrency = security as IBaseCurrencySymbol;
if (baseCurrency == null)
{
return parameters.ResultInAccountCurrency(0m);
}
var baseCurrencyPosition = baseCurrency.BaseCurrency.Amount;
var quoteCurrencyPosition = portfolio.CashBook[security.QuoteCurrency.Symbol].Amount;
// determine the unit price in terms of the quote currency
var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone);
var unitPrice = new MarketOrder(security.Symbol, 1, utcTime).GetValue(security) / security.QuoteCurrency.ConversionRate;
if (unitPrice == 0)
{
return parameters.ResultInAccountCurrency(0m);
}
// NOTE: This is returning in units of the BASE currency
if (direction == OrderDirection.Buy)
{
// invert units for math, 6500USD per BTC, currency pairs aren't real fractions
// (USD)/(BTC/USD) => 10kUSD/ (6500 USD/BTC) => 10kUSD * (1BTC/6500USD) => ~ 1.5BTC
return parameters.Result(quoteCurrencyPosition / unitPrice, baseCurrency.BaseCurrency.Symbol);
}
if (direction == OrderDirection.Sell)
{
return parameters.Result(baseCurrencyPosition, baseCurrency.BaseCurrency.Symbol);
}
return parameters.ResultInAccountCurrency(0m);
}
private static decimal GetOrderPrice(Security security, Order order)
{
var orderPrice = 0m;
switch (order.Type)
{
case OrderType.Market:
orderPrice = security.Price;
break;
case OrderType.Limit:
orderPrice = ((LimitOrder)order).LimitPrice;
break;
case OrderType.StopMarket:
orderPrice = ((StopMarketOrder)order).StopPrice;
break;
case OrderType.StopLimit:
orderPrice = ((StopLimitOrder)order).LimitPrice;
break;
case OrderType.LimitIfTouched:
orderPrice = ((LimitIfTouchedOrder)order).LimitPrice;
break;
case OrderType.TrailingStop:
orderPrice = ((TrailingStopOrder)order).StopPrice;
break;
}
return orderPrice;
}
private static decimal GetOpenOrdersReservedQuantity(SecurityPortfolioManager portfolio, Security security, Order order)
{
var baseCurrency = security as IBaseCurrencySymbol;
if (baseCurrency == null) return 0;
// find the target currency for the requested direction and the securities potentially involved
var targetCurrency = order.Direction == OrderDirection.Buy
? security.QuoteCurrency.Symbol
: baseCurrency.BaseCurrency.Symbol;
var symbolDirectionPairs = new Dictionary<Symbol, OrderDirection>();
foreach (var portfolioSecurity in portfolio.Securities.Values)
{
var basePortfolioSecurity = portfolioSecurity as IBaseCurrencySymbol;
if (basePortfolioSecurity == null) continue;
if (basePortfolioSecurity.BaseCurrency.Symbol == targetCurrency)
{
symbolDirectionPairs.Add(portfolioSecurity.Symbol, OrderDirection.Sell);
}
else if (portfolioSecurity.QuoteCurrency.Symbol == targetCurrency)
{
symbolDirectionPairs.Add(portfolioSecurity.Symbol, OrderDirection.Buy);
}
}
// fetch open orders with matching symbol/side
var openOrders = portfolio.Transactions.GetOpenOrders(x =>
{
OrderDirection dir;
return symbolDirectionPairs.TryGetValue(x.Symbol, out dir) &&
// same direction of our order
dir == x.Direction &&
// don't count our current order
x.Id != order.Id &&
// only count working orders
(x.Type == OrderType.Limit || x.Type == OrderType.StopMarket);
}
);
// calculate reserved quantity for selected orders
var openOrdersReservedQuantity = 0m;
foreach (var openOrder in openOrders)
{
var orderSecurity = portfolio.Securities[openOrder.Symbol];
var orderBaseCurrency = orderSecurity as IBaseCurrencySymbol;
if (orderBaseCurrency != null)
{
// convert order value to target currency
var quantityInTargetCurrency = openOrder.AbsoluteQuantity;
if (orderSecurity.QuoteCurrency.Symbol == targetCurrency)
{
quantityInTargetCurrency *= GetOrderPrice(security, openOrder);
}
openOrdersReservedQuantity += quantityInTargetCurrency;
}
}
return openOrdersReservedQuantity;
}
}
}
+196
View File
@@ -0,0 +1,196 @@
/*
* 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.Data;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
namespace QuantConnect.Securities.Cfd
{
/// <summary>
/// CFD Security Object Implementation for CFD Assets
/// </summary>
/// <seealso cref="Security"/>
public class Cfd : Security
{
private readonly ContractSymbolProperties _symbolProperties;
/// <summary>
/// Constructor for the CFD security
/// </summary>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="config">The subscription configuration for this security</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
public Cfd(SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
SubscriptionDataConfig config,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes)
: this(exchangeHours, quoteCurrency, config, new ContractSymbolProperties(symbolProperties), currencyConverter, registeredTypes)
{
}
/// <summary>
/// Constructor for the CFD security
/// </summary>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="config">The subscription configuration for this security</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
public Cfd(SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
SubscriptionDataConfig config,
ContractSymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes)
: base(config,
quoteCurrency,
symbolProperties,
new CfdExchange(exchangeHours),
new CfdCache(),
new SecurityPortfolioModel(),
new ImmediateFillModel(),
new ConstantFeeModel(0),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new SecurityMarginModel(50m),
new CfdDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
Holdings = new CfdHolding(this, currencyConverter);
_symbolProperties = symbolProperties;
}
/// <summary>
/// Constructor for the CFD security
/// </summary>
/// <param name="symbol">The security's symbol</param>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
/// <param name="securityCache">Cache for storing Security data</param>
public Cfd(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache)
: this(symbol, exchangeHours, quoteCurrency, new ContractSymbolProperties(symbolProperties), currencyConverter, registeredTypes, securityCache)
{
}
/// <summary>
/// Constructor for the CFD security
/// </summary>
/// <param name="symbol">The security's symbol</param>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
/// <param name="securityCache">Cache for storing Security data</param>
public Cfd(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
ContractSymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache)
: base(symbol,
quoteCurrency,
symbolProperties,
new CfdExchange(exchangeHours),
securityCache,
new SecurityPortfolioModel(),
new ImmediateFillModel(),
new ConstantFeeModel(0),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new SecurityMarginModel(50m),
new CfdDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
Holdings = new CfdHolding(this, currencyConverter);
_symbolProperties = symbolProperties;
}
/// <summary>
/// Gets or sets the contract multiplier for this CFD security
/// </summary>
public decimal ContractMultiplier
{
get { return _symbolProperties.ContractMultiplier; }
set { _symbolProperties.SetContractMultiplier(value); }
}
/// <summary>
/// Gets the minimum price variation for this CFD security
/// </summary>
public decimal MinimumPriceVariation
{
get { return SymbolProperties.MinimumPriceVariation; }
}
/// <summary>
/// Decomposes the specified currency pair into a base and quote currency provided as out parameters
/// </summary>
/// <param name="symbol">The input symbol to be decomposed</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="baseCurrency">The output base currency</param>
/// <param name="quoteCurrency">The output quote currency</param>
public static void DecomposeCurrencyPair(Symbol symbol, SymbolProperties symbolProperties, out string baseCurrency, out string quoteCurrency)
{
quoteCurrency = symbolProperties.QuoteCurrency;
if (symbol.Value.EndsWith(quoteCurrency))
{
baseCurrency = symbol.Value.RemoveFromEnd(quoteCurrency);
}
else
{
throw new InvalidOperationException($"Symbol doesn't end with {quoteCurrency}");
}
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(Cfd security) => security.Symbol;
}
}
+26
View File
@@ -0,0 +1,26 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Cfd
{
/// <summary>
/// CFD specific caching support
/// </summary>
/// <remarks>Class is virtually empty and scheduled to be made obsolete. Potentially could be used for user data storage.</remarks>
/// <seealso cref="SecurityCache"/>
public class CfdCache : SecurityCache
{
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Cfd
{
/// <summary>
/// CFD packet by packet data filtering mechanism for dynamically detecting bad ticks.
/// </summary>
/// <seealso cref="SecurityDataFilter"/>
public class CfdDataFilter : SecurityDataFilter
{
}
}
+43
View File
@@ -0,0 +1,43 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Cfd
{
/// <summary>
/// CFD exchange class - information and helper tools for CFD exchange properties
/// </summary>
/// <seealso cref="SecurityExchange"/>
public class CfdExchange : SecurityExchange
{
/// <summary>
/// Number of trading days per year for this security, used for performance statistics.
/// </summary>
public override int TradingDaysPerYear
{
// 365 - Saturdays = 313;
get { return 313; }
}
/// <summary>
/// Initializes a new instance of the <see cref="CfdExchange"/> class using the specified
/// exchange hours to determine open/close times
/// </summary>
/// <param name="exchangeHours">Contains the weekly exchange schedule plus holidays</param>
public CfdExchange(SecurityExchangeHours exchangeHours)
: base(exchangeHours)
{
}
}
}
+34
View File
@@ -0,0 +1,34 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Cfd
{
/// <summary>
/// CFD holdings implementation of the base securities class
/// </summary>
/// <seealso cref="SecurityHolding"/>
public class CfdHolding : SecurityHolding
{
/// <summary>
/// CFD Holding Class constructor
/// </summary>
/// <param name="security">The CFD security being held</param>
/// <param name="currencyConverter">A currency converter instance</param>
public CfdHolding(Cfd security, ICurrencyConverter currencyConverter)
: base(security, currencyConverter)
{
}
}
}
@@ -0,0 +1,76 @@
/*
* 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 Python.Runtime;
using QuantConnect.Python;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides an implementation of <see cref="ISecurityInitializer"/> that executes
/// each initializer in order
/// </summary>
public class CompositeSecurityInitializer : ISecurityInitializer
{
private readonly List<ISecurityInitializer> _initializers;
/// <summary>
/// Gets the list of internal security initializers
/// </summary>
public List<ISecurityInitializer> Initializers => _initializers.ToList();
/// <summary>
/// Initializes a new instance of the <see cref="CompositeSecurityInitializer"/> class
/// </summary>
/// <param name="initializers">The initializers to execute in order</param>
public CompositeSecurityInitializer(params PyObject[] initializers)
{
_initializers = initializers.Select(x => (ISecurityInitializer)new SecurityInitializerPythonWrapper(x)).ToList();
}
/// <summary>
/// Initializes a new instance of the <see cref="CompositeSecurityInitializer"/> class
/// </summary>
/// <param name="initializers">The initializers to execute in order</param>
public CompositeSecurityInitializer(params ISecurityInitializer[] initializers)
{
_initializers = initializers.ToList();
}
/// <summary>
/// Execute each of the internally held initializers in sequence
/// </summary>
/// <param name="security">The security to be initialized</param>
public void Initialize(Security security)
{
foreach (var initializer in _initializers)
{
initializer.Initialize(security);
}
}
/// <summary>
/// Adds a new security initializer to this composite initializer
/// </summary>
/// <param name="initializer">The initializer to add</param>
public void AddSecurityInitializer(ISecurityInitializer initializer)
{
_initializers.Add(initializer);
}
}
}
@@ -0,0 +1,72 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Provides an implementation of <see cref="IBuyingPowerModel"/> that uses an absurdly low margin
/// requirement to ensure all orders have sufficient margin provided the portfolio is not underwater.
/// </summary>
public class ConstantBuyingPowerModel : BuyingPowerModel
{
private readonly decimal _marginRequiredPerUnitInAccountCurrency;
/// <summary>
/// Initializes a new instance of the <see cref="ConstantBuyingPowerModel"/> class
/// </summary>
/// <param name="marginRequiredPerUnitInAccountCurrency">The constant amount of margin required per single unit
/// of an asset. Each unit is defined as a quantity of 1 and NOT based on the lot size.</param>
public ConstantBuyingPowerModel(decimal marginRequiredPerUnitInAccountCurrency)
{
_marginRequiredPerUnitInAccountCurrency = marginRequiredPerUnitInAccountCurrency;
}
/// <summary>
/// Sets the leverage for the applicable securities, i.e, equities
/// </summary>
/// <remarks>
/// This is added to maintain backwards compatibility with the old margin/leverage system
/// </remarks>
/// <param name="security"></param>
/// <param name="leverage">The new leverage</param>
public override void SetLeverage(Security security, decimal leverage)
{
// ignored -- reasoning is user has an algorithm that has margin issues and so they quickly swap
// this impl in, but their code calls set leverage, they would need to comment that out and such
// said another way -- user made the decision to ignore margin/leverage by selecting this model
}
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
/// <param name="parameters">An object containing the security and quantity of shares</param>
/// <returns>The initial margin required for the provided security and quantity</returns>
public override InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
return parameters.Quantity * _marginRequiredPerUnitInAccountCurrency;
}
/// <summary>
/// Gets the margin currently allocated to the specified holding
/// </summary>
/// <param name="parameters">An object containing the security</param>
/// <returns>The maintenance margin required for the provided holdings quantity/cost/value</returns>
public override MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
return parameters.AbsoluteQuantity * _marginRequiredPerUnitInAccountCurrency;
}
}
}
@@ -0,0 +1,435 @@
/*
* 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.Linq;
using Python.Runtime;
using System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Securities
{
/// <summary>
/// Base class for contract symbols filtering universes.
/// Used by OptionFilterUniverse and FutureFilterUniverse
/// </summary>
public abstract class ContractSecurityFilterUniverse<T, TData> : IDerivativeSecurityFilterUniverse<TData>
where T : ContractSecurityFilterUniverse<T, TData>
where TData : IChainUniverseData
{
private bool _alreadyAppliedTypeFilters;
private IReadOnlyList<TData> _data;
/// <summary>
/// Defines listed contract types with Flags attribute
/// </summary>
[Flags]
protected enum ContractExpirationType : int
{
/// <summary>
/// Standard contracts
/// </summary>
Standard = 1,
/// <summary>
/// Non standard weekly contracts
/// </summary>
Weekly = 2
}
/// <summary>
/// The default expiration type filter value
/// </summary>
protected static readonly ContractExpirationType DefaultExpirationType = ContractExpirationType.Standard | ContractExpirationType.Weekly;
/// <summary>
/// Expiration Types allowed through the filter
/// Standards only by default
/// </summary>
protected ContractExpirationType Type { get; set; } = DefaultExpirationType;
/// <summary>
/// The local exchange current time
/// </summary>
public DateTime LocalTime { get; private set; }
/// <summary>
/// The number of contracts in the universe
/// </summary>
public int Count => _data.Count;
/// <summary>
/// All data in this filter
/// Marked internal for use by extensions
/// </summary>
/// <remarks>
/// Setting it will also set AllSymbols
/// </remarks>
internal IReadOnlyList<TData> Data
{
get
{
return _data;
}
set
{
_data = value;
}
}
/// <summary>
/// All Symbols in this filter
/// Marked internal for use by extensions
/// </summary>
/// <remarks>
/// Setting it will remove any data that doesn't have a symbol in AllSymbols
/// </remarks>
internal IEnumerable<Symbol> AllSymbols
{
get
{
return _data.Select(x => x.Symbol);
}
set
{
// We create a "fake" data instance for each symbol that is not in the data,
// so we are polite to the user and keep backwards compatibility
_data = value.Select(symbol => _data.FirstOrDefault(x => x.Symbol == symbol) ?? CreateDataInstance(symbol)).ToList();
}
}
/// <summary>
/// Constructs ContractSecurityFilterUniverse
/// </summary>
protected ContractSecurityFilterUniverse()
{
Type = DefaultExpirationType;
}
/// <summary>
/// Constructs ContractSecurityFilterUniverse
/// </summary>
protected ContractSecurityFilterUniverse(IReadOnlyList<TData> allData, DateTime localTime)
{
Data = allData;
LocalTime = localTime;
Type = DefaultExpirationType;
}
/// <summary>
/// Function to determine if the given symbol is a standard contract
/// </summary>
/// <returns>True if standard type</returns>
protected abstract bool IsStandard(Symbol symbol);
/// <summary>
/// Creates a new instance of the data type for the given symbol
/// </summary>
/// <returns>A data instance for the given symbol</returns>
protected abstract TData CreateDataInstance(Symbol symbol);
/// <summary>
/// Returns universe, filtered by contract type
/// </summary>
/// <returns>Universe with filter applied</returns>
internal T ApplyTypesFilter()
{
if (_alreadyAppliedTypeFilters)
{
return (T)this;
}
// memoization map for ApplyTypesFilter()
var memoizedMap = new Dictionary<DateTime, bool>();
Func<TData, bool> memoizedIsStandardType = data =>
{
var dt = data.ID.Date;
bool result;
if (memoizedMap.TryGetValue(dt, out result))
return result;
var res = IsStandard(data.Symbol);
memoizedMap[dt] = res;
return res;
};
Data = Data.Where(x =>
{
switch (Type)
{
case ContractExpirationType.Weekly:
return !memoizedIsStandardType(x);
case ContractExpirationType.Standard:
return memoizedIsStandardType(x);
case ContractExpirationType.Standard | ContractExpirationType.Weekly:
return true;
default:
return false;
}
}).ToList();
_alreadyAppliedTypeFilters = true;
return (T)this;
}
/// <summary>
/// Refreshes this filter universe
/// </summary>
/// <param name="allData">All data for contracts in the Universe</param>
/// <param name="localTime">The local exchange current time</param>
public virtual void Refresh(IReadOnlyList<TData> allData, DateTime localTime)
{
Data = allData;
LocalTime = localTime;
Type = DefaultExpirationType;
_alreadyAppliedTypeFilters = false;
}
/// <summary>
/// Sets universe of standard contracts (if any) as selection
/// Contracts by default are standards; only needed to switch back if changed
/// </summary>
/// <returns>Universe with filter applied</returns>
public T StandardsOnly()
{
if (_alreadyAppliedTypeFilters)
{
throw new InvalidOperationException("Type filters have already been applied, " +
"please call StandardsOnly() before applying other filters such as FrontMonth() or BackMonths()");
}
Type = ContractExpirationType.Standard;
return (T)this;
}
/// <summary>
/// Includes universe of non-standard weeklys contracts (if any) into selection
/// </summary>
/// <returns>Universe with filter applied</returns>
[Obsolete("IncludeWeeklys is obsolete because weekly contracts are now included by default.")]
public T IncludeWeeklys()
{
if (_alreadyAppliedTypeFilters)
{
throw new InvalidOperationException("Type filters have already been applied, " +
"please call IncludeWeeklys() before applying other filters such as FrontMonth() or BackMonths()");
}
Type |= ContractExpirationType.Weekly;
return (T)this;
}
/// <summary>
/// Sets universe of weeklys contracts (if any) as selection
/// </summary>
/// <returns>Universe with filter applied</returns>
public T WeeklysOnly()
{
Type = ContractExpirationType.Weekly;
return (T)this;
}
/// <summary>
/// Returns front month contract
/// </summary>
/// <returns>Universe with filter applied</returns>
public virtual T FrontMonth()
{
ApplyTypesFilter();
var ordered = Data.OrderBy(x => x.ID.Date).ToList();
if (ordered.Count == 0) return (T)this;
var frontMonth = ordered.TakeWhile(x => ordered[0].ID.Date == x.ID.Date);
Data = frontMonth.ToList();
return (T)this;
}
/// <summary>
/// Returns a list of back month contracts
/// </summary>
/// <returns>Universe with filter applied</returns>
public virtual T BackMonths()
{
ApplyTypesFilter();
var ordered = Data.OrderBy(x => x.ID.Date).ToList();
if (ordered.Count == 0) return (T)this;
var backMonths = ordered.SkipWhile(x => ordered[0].ID.Date == x.ID.Date);
Data = backMonths.ToList();
return (T)this;
}
/// <summary>
/// Returns first of back month contracts
/// </summary>
/// <returns>Universe with filter applied</returns>
public T BackMonth()
{
return BackMonths().FrontMonth();
}
/// <summary>
/// Adjust the reference date used for expiration filtering. By default it just returns the same date.
/// </summary>
/// <param name="referenceDate">The reference date to be adjusted</param>
/// <returns>The adjusted date</returns>
protected virtual DateTime AdjustExpirationReferenceDate(DateTime referenceDate)
{
return referenceDate;
}
/// <summary>
/// Applies filter selecting options contracts based on a range of expiration dates relative to the current day
/// </summary>
/// <param name="minExpiry">The minimum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiry">The maximum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in more than 10 days</param>
/// <returns>Universe with filter applied</returns>
public virtual T Expiration(TimeSpan minExpiry, TimeSpan maxExpiry)
{
if (LocalTime == default)
{
return (T)this;
}
if (maxExpiry > Time.MaxTimeSpan) maxExpiry = Time.MaxTimeSpan;
var referenceDate = AdjustExpirationReferenceDate(LocalTime.Date);
var minExpiryToDate = referenceDate + minExpiry;
var maxExpiryToDate = referenceDate + maxExpiry;
Data = Data
.Where(symbol => symbol.ID.Date.Date >= minExpiryToDate && symbol.ID.Date.Date <= maxExpiryToDate)
.ToList();
return (T)this;
}
/// <summary>
/// Applies filter selecting contracts based on a range of expiration dates relative to the current day
/// </summary>
/// <param name="minExpiryDays">The minimum time, expressed in days, until expiry to include, for example, 10
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiryDays">The maximum time, expressed in days, until expiry to include, for example, 10
/// would exclude contracts expiring in more than 10 days</param>
/// <returns>Universe with filter applied</returns>
public T Expiration(int minExpiryDays, int maxExpiryDays)
{
return Expiration(TimeSpan.FromDays(minExpiryDays), TimeSpan.FromDays(maxExpiryDays));
}
/// <summary>
/// Explicitly sets the selected contract symbols for this universe.
/// This overrides and and all other methods of selecting symbols assuming it is called last.
/// </summary>
/// <param name="contracts">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(PyObject contracts)
{
// Let's first check if the object is a selector:
if (contracts.TrySafeAs(out Func<IEnumerable<TData>, IEnumerable<Symbol>> contractSelector))
{
return Contracts(contractSelector);
}
// Else, it should be a list of symbols:
return Contracts(contracts.ConvertToSymbolEnumerable());
}
/// <summary>
/// Explicitly sets the selected contract symbols for this universe.
/// This overrides and and all other methods of selecting symbols assuming it is called last.
/// </summary>
/// <param name="contracts">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(IEnumerable<Symbol> contracts)
{
AllSymbols = contracts.ToList();
return (T)this;
}
/// <summary>
/// Explicitly sets the selected contract symbols for this universe.
/// This overrides and and all other methods of selecting symbols assuming it is called last.
/// </summary>
/// <param name="contracts">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(IEnumerable<TData> contracts)
{
Data = contracts.ToList();
return (T)this;
}
/// <summary>
/// Sets a function used to filter the set of available contract filters. The input to the 'contractSelector'
/// function will be the already filtered list if any other filters have already been applied.
/// </summary>
/// <param name="contractSelector">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(Func<IEnumerable<TData>, IEnumerable<Symbol>> contractSelector)
{
// force materialization using ToList
AllSymbols = contractSelector(Data).ToList();
return (T)this;
}
/// <summary>
/// Sets a function used to filter the set of available contract filters. The input to the 'contractSelector'
/// function will be the already filtered list if any other filters have already been applied.
/// </summary>
/// <param name="contractSelector">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(Func<IEnumerable<TData>, IEnumerable<TData>> contractSelector)
{
// force materialization using ToList
Data = contractSelector(Data).ToList();
return (T)this;
}
/// <summary>
/// Instructs the engine to only filter contracts on the first time step of each market day.
/// </summary>
/// <returns>Universe with filter applied</returns>
/// <remarks>Deprecated since filters are always non-dynamic now</remarks>
[Obsolete("Deprecated as of 2023-12-13. Filters are always non-dynamic as of now, which means they will only bee applied daily.")]
public T OnlyApplyFilterAtMarketOpen()
{
return (T)this;
}
/// <summary>
/// IEnumerable interface method implementation
/// </summary>
/// <returns>IEnumerator of Symbols in Universe</returns>
public IEnumerator<TData> GetEnumerator()
{
return Data.GetEnumerator();
}
/// <summary>
/// IEnumerable interface method implementation
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return Data.GetEnumerator();
}
}
}
@@ -0,0 +1,65 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Represents common properties for contract-based securities such as options and CFDs
/// </summary>
public class ContractSymbolProperties : SymbolProperties
{
/// <summary>
/// The contract multiplier for the security.
/// </summary>
/// <remarks>
/// If manually set by a consumer, this value will be used instead of the
/// <see cref="SymbolProperties.ContractMultiplier"/> and also allows to make
/// sure it is not overridden when the symbol properties database gets updated.
/// </remarks>
private decimal? _contractMultiplier;
/// <summary>
/// The contract multiplier for the security
/// </summary>
public override decimal ContractMultiplier => _contractMultiplier ?? base.ContractMultiplier;
/// <summary>
/// Creates an instance of the <see cref="ContractSymbolProperties"/> class from a <see cref="SymbolProperties"/> instance
/// </summary>
public ContractSymbolProperties(SymbolProperties properties)
: base(properties)
{
}
/// <summary>
/// Creates an instance of the <see cref="ContractSymbolProperties"/> class
/// </summary>
public ContractSymbolProperties(string description, string quoteCurrency, decimal contractMultiplier,
decimal minimumPriceVariation, decimal lotSize, string marketTicker,
decimal? minimumOrderSize = null, decimal priceMagnifier = 1, decimal strikeMultiplier = 1)
: base(description, quoteCurrency, contractMultiplier, minimumPriceVariation, lotSize, marketTicker,
minimumOrderSize, priceMagnifier, strikeMultiplier)
{
}
/// <summary>
/// Sets a custom contract multiplier that persists through symbol properties database updates
/// </summary>
internal void SetContractMultiplier(decimal multiplier)
{
_contractMultiplier = multiplier;
}
}
}
@@ -0,0 +1,63 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// A cash amount that can easily be converted into account currency
/// </summary>
public class ConvertibleCashAmount
{
/// <summary>
/// The amount
/// </summary>
public decimal Amount { get; }
/// <summary>
/// The cash associated with the amount
/// </summary>
public Cash Cash { get; }
/// <summary>
/// The amount in account currency
/// </summary>
public decimal InAccountCurrency => Amount * Cash.ConversionRate;
/// <summary>
/// Creates a new instance
/// </summary>
public ConvertibleCashAmount (decimal amount, Cash cash)
{
Amount = amount;
Cash = cash;
}
/// <summary>
/// The amount in account currency
/// </summary>
public static implicit operator decimal(ConvertibleCashAmount convertibleCashAmount)
{
return convertibleCashAmount.InAccountCurrency;
}
/// <summary>
/// The amount in account currency
/// </summary>
public static implicit operator CashAmount(ConvertibleCashAmount convertibleCashAmount)
{
return new CashAmount(convertibleCashAmount.Amount, convertibleCashAmount.Cash.Symbol);
}
}
}
+155
View File
@@ -0,0 +1,155 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities.Forex;
using System;
namespace QuantConnect.Securities.Crypto
{
/// <summary>
/// Crypto Security Object Implementation for Crypto Assets
/// </summary>
/// <seealso cref="Security"/>
public class Crypto : Security, IBaseCurrencySymbol
{
/// <summary>
/// Gets the currency acquired by going long this currency pair
/// </summary>
/// <remarks>
/// For example, the EUR/USD has a base currency of the euro, and as a result
/// of going long the EUR/USD a trader is acquiring euros in exchange for US dollars
/// </remarks>
public Cash BaseCurrency { get; protected set; }
/// <summary>
/// Constructor for the Crypto security
/// </summary>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="baseCurrency">The cash object that represent the base currency</param>
/// <param name="config">The subscription configuration for this security</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
public Crypto(SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
Cash baseCurrency,
SubscriptionDataConfig config,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes)
: base(config,
quoteCurrency,
symbolProperties,
new CryptoExchange(exchangeHours),
new ForexCache(),
new SecurityPortfolioModel(),
new ImmediateFillModel(),
new GDAXFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new CashBuyingPowerModel(),
new ForexDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
BaseCurrency = baseCurrency;
Holdings = new CryptoHolding(this, currencyConverter);
}
/// <summary>
/// Constructor for the Crypto security
/// </summary>
/// <param name="symbol">The security's symbol</param>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="baseCurrency">The cash object that represent the base currency</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
/// <param name="securityCache">Cache to store <see cref="Security"/> data</param>
public Crypto(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
Cash baseCurrency,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache)
: base(symbol,
quoteCurrency,
symbolProperties,
new CryptoExchange(exchangeHours),
securityCache,
new SecurityPortfolioModel(),
new ImmediateFillModel(),
new GDAXFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new CashBuyingPowerModel(),
new ForexDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
BaseCurrency = baseCurrency;
Holdings = new CryptoHolding(this, currencyConverter);
}
/// <summary>
/// Get the current value of the security.
/// </summary>
public override decimal Price => Cache.GetData<TradeBar>()?.Close ?? Cache.Price;
/// <summary>
/// Decomposes the specified currency pair into a base and quote currency provided as out parameters
/// </summary>
/// <param name="symbol">The input symbol to be decomposed</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="baseCurrency">The output base currency</param>
/// <param name="quoteCurrency">The output quote currency</param>
public static void DecomposeCurrencyPair(Symbol symbol, SymbolProperties symbolProperties, out string baseCurrency, out string quoteCurrency)
{
quoteCurrency = symbolProperties.QuoteCurrency;
if (symbol.Value.EndsWith(quoteCurrency))
{
baseCurrency = symbol.Value.RemoveFromEnd(quoteCurrency);
}
else
{
throw new InvalidOperationException($"symbol doesn't end with {quoteCurrency}");
}
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(Crypto security) => security.Symbol;
}
}
@@ -0,0 +1,44 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Crypto
{
/// <summary>
/// Crypto exchange class - information and helper tools for Crypto exchange properties
/// </summary>
/// <seealso cref="SecurityExchange"/>
public class CryptoExchange : SecurityExchange
{
/// <summary>
/// Initializes a new instance of the <see cref="CryptoExchange"/> class using market hours
/// derived from the market-hours-database for the Crypto market
/// </summary>
public CryptoExchange(string market)
: base(MarketHoursDatabase.FromDataFolder().GetExchangeHours(market, null, SecurityType.Crypto))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CryptoExchange"/> class using the specified
/// exchange hours to determine open/close times
/// </summary>
/// <param name="exchangeHours">Contains the weekly exchange schedule plus holidays</param>
public CryptoExchange(SecurityExchangeHours exchangeHours)
: base(exchangeHours)
{
}
}
}
+34
View File
@@ -0,0 +1,34 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Crypto
{
/// <summary>
/// Crypto holdings implementation of the base securities class
/// </summary>
/// <seealso cref="SecurityHolding"/>
public class CryptoHolding : SecurityHolding
{
/// <summary>
/// Crypto Holding Class
/// </summary>
/// <param name="security">The Crypto security being held</param>
/// <param name="currencyConverter">A currency converter instance</param>
public CryptoHolding(Crypto security, ICurrencyConverter currencyConverter)
: base(security, currencyConverter)
{
}
}
}
@@ -0,0 +1,93 @@
/*
* 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.
*/
namespace QuantConnect.Securities.CryptoFuture
{
/// <summary>
/// Binance-specific crypto future margin model that includes supplementary stable coin
/// currencies as alternative collateral for non-coin (USDⓈ-M) futures.
/// </summary>
/// <remarks>
/// EU/EEA users under MiCA Credits Trading Mode have BNFCR in their account.
/// When BNFCR is present, this model aggregates all supplementary collateral assets
/// using their walletBalance values converted to the primary collateral currency.
/// Non-EU accounts don't have BNFCR — the check is a no-op for them.
/// See: https://www.binance.com/en/support/faq/detail/0e857c392a2d47cebde0af762d9255ae
/// </remarks>
public class BinanceCryptoFutureMarginModel : CryptoFutureMarginModel
{
/// <summary>
/// Binance Futures Credits currency symbol, present in EU/EEA accounts under MiCA Credits Trading Mode.
/// </summary>
private const string BNFCRCurrency = "BNFCR";
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="leverage">The leverage to use, default 25x</param>
public BinanceCryptoFutureMarginModel(decimal leverage = 25)
: base(leverage)
{
}
/// <summary>
/// Gets the total collateral amount for a Binance crypto future, including supplementary
/// collateral assets for EU/EEA accounts in MiCA Credits Trading Mode.
/// For coin futures (e.g. BTCUSD), only the primary collateral (base currency) is used.
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The crypto future security</param>
/// <param name="primaryCollateral">The primary collateral cash (e.g. USDT)</param>
/// <returns>Total collateral amount in terms of the primary collateral currency</returns>
protected override decimal GetTotalCollateralAmount(
SecurityPortfolioManager portfolio, Security security, Cash primaryCollateral)
{
// BNFCR presence means EU/EEA account in MiCA Credits Trading Mode.
// Non-EU accounts don't have BNFCR in CashBook — skip entirely.
var cashBook = portfolio.CashBook;
if (!cashBook.ContainsKey(BNFCRCurrency))
{
return base.GetTotalCollateralAmount(portfolio, security, primaryCollateral);
}
// Aggregate all collateral assets using walletBalance values.
// Binance controls which assets are in the account — we sum everything
// with a non-zero balance, converting to the primary collateral currency.
// Negative amounts (e.g. BNFCR fees) correctly reduce the total.
var total = 0m;
foreach (var kvp in cashBook)
{
var cash = kvp.Value;
if (cash.Amount == 0)
{
continue;
}
total += cashBook.Convert(cash.Amount, cash.Symbol, primaryCollateral.Symbol);
}
return total;
}
/// <summary>
/// When BNFCR is present (EU/MiCA mode), all USDⓈ-M futures share the same collateral
/// pool regardless of quote currency (USDT, USDC, etc.).
/// </summary>
protected override bool SharesCollateral(SecurityPortfolioManager portfolio, Cash collateralCurrency, Security otherCryptoFuture)
{
return portfolio.CashBook.ContainsKey(BNFCRCurrency) || base.SharesCollateral(portfolio, collateralCurrency, otherCryptoFuture);
}
}
}
@@ -0,0 +1,100 @@
/*
* 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.Data.Market;
namespace QuantConnect.Securities.CryptoFuture
{
/// <summary>
/// The responsability of this model is to apply future funding rate cash flows to the portfolio based on open positions
/// </summary>
public class BinanceFutureMarginInterestRateModel : IMarginInterestRateModel
{
private DateTime _nextFundingRateApplication = DateTime.MaxValue;
/// <summary>
/// Apply margin interest rates to the portfolio
/// </summary>
/// <param name="marginInterestRateParameters">The parameters to use</param>
public void ApplyMarginInterestRate(MarginInterestRateParameters marginInterestRateParameters)
{
var security = marginInterestRateParameters.Security;
var time = marginInterestRateParameters.Time;
var cryptoFuture = (CryptoFuture)security;
if (!cryptoFuture.Invested)
{
// nothing to do
_nextFundingRateApplication = DateTime.MaxValue;
return;
}
else if (_nextFundingRateApplication == DateTime.MaxValue)
{
// we opened a new position
_nextFundingRateApplication = GetNextFundingRateApplication(time);
}
var marginInterest = cryptoFuture.Cache.GetData<MarginInterestRate>();
if(marginInterest == null)
{
return;
}
while(time >= _nextFundingRateApplication)
{
// When the funding rate is positive, the price of the perpetual contract is higher than the mark price,
// thus, traders who are long pay for short positions. Conversely, a negative funding rate indicates that perpetual
// prices are below the mark price, which means that short positions pay for longs.
// Funding Amount = Nominal Value of Positions * Funding Rate
var holdings = cryptoFuture.Holdings;
var positionValue = cryptoFuture.Holdings.GetQuantityValue(holdings.Quantity);
var funding = marginInterest.InterestRate * positionValue.Amount;
funding *= -1;
// '* -1' because:
// - we pay when 'funding' positive:
// long position & positive rate
// short position & negative rate
// - we ear when 'funding' negative:
// long position & negative rate
// short position & positive rate
positionValue.Cash.AddAmount(funding);
_nextFundingRateApplication = GetNextFundingRateApplication(_nextFundingRateApplication);
}
}
private static DateTime GetNextFundingRateApplication(DateTime currentTime)
{
if(currentTime.Hour >= 16)
{
// tomorrow 00:00
return currentTime.Date.AddDays(1);
}
else if (currentTime.Hour >= 8)
{
return currentTime.Date.AddHours(16);
}
else
{
return currentTime.Date.AddHours(8);
}
}
}
}
@@ -0,0 +1,25 @@
/*
* 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.
*
*/
namespace QuantConnect.Securities.CryptoFuture
{
/// <summary>
/// The responsibility of this model is to apply future funding rate cash flows to the portfolio based on open positions
/// </summary>
public class BybitFutureMarginInterestRateModel : BinanceFutureMarginInterestRateModel
{
}
}
@@ -0,0 +1,104 @@
/*
* 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 QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
namespace QuantConnect.Securities.CryptoFuture
{
/// <summary>
/// Crypto Future Security Object Implementation for Crypto Future Assets
/// </summary>
public class CryptoFuture : Security, IBaseCurrencySymbol
{
/// <summary>
/// Gets the currency acquired by going long this currency pair
/// </summary>
/// <remarks>
/// For example, the EUR/USD has a base currency of the euro, and as a result
/// of going long the EUR/USD a trader is acquiring euros in exchange for US dollars
/// </remarks>
public Cash BaseCurrency { get; protected set; }
/// <summary>
/// Constructor for the Crypto Future security
/// </summary>
/// <param name="symbol">The symbol</param>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="baseCurrency">The cash object that represent the base currency</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
/// <param name="cache">The security cache</param>
public CryptoFuture(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
Cash baseCurrency,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache cache)
: base(symbol,
quoteCurrency,
symbolProperties,
new CryptoFutureExchange(exchangeHours),
cache,
new SecurityPortfolioModel(),
new ImmediateFillModel(),
IsCryptoCoinFuture(quoteCurrency.Symbol) ? new BinanceCoinFuturesFeeModel() : new BinanceFuturesFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new CryptoFutureMarginModel(),
new SecurityDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
// only applies for perpetual futures
symbol.ID.Date == SecurityIdentifier.DefaultDate ? new BinanceFutureMarginInterestRateModel() : Securities.MarginInterestRateModel.Null
)
{
BaseCurrency = baseCurrency;
Holdings = new CryptoFutureHolding(this, currencyConverter);
}
/// <summary>
/// Checks whether the security is a crypto coin future
/// </summary>
/// <returns>True if the security is a crypto coin future</returns>
public bool IsCryptoCoinFuture()
{
return IsCryptoCoinFuture(QuoteCurrency.Symbol);
}
/// <summary>
/// Checks whether the security is a crypto coin future
/// </summary>
/// <param name="quoteCurrency">The security quote currency</param>
/// <returns>True if the security is a crypto coin future</returns>
private static bool IsCryptoCoinFuture(string quoteCurrency)
{
return quoteCurrency != "USDT" && quoteCurrency != "BUSD" && quoteCurrency != "USDC";
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(CryptoFuture security) => security.Symbol;
}
}
@@ -0,0 +1,43 @@
/*
* 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.
*/
namespace QuantConnect.Securities.CryptoFuture
{
/// <summary>
/// Crypto future exchange class - information and helper tools for Crypto future exchange properties
/// </summary>
/// <seealso cref="SecurityExchange"/>
public class CryptoFutureExchange : SecurityExchange
{
/// <summary>
/// Initializes a new instance of the <see cref="CryptoFutureExchange"/> class using market hours
/// derived from the market-hours-database for the Crypto future market
/// </summary>
public CryptoFutureExchange(string market)
: base(MarketHoursDatabase.FromDataFolder().GetExchangeHours(market, null, SecurityType.CryptoFuture))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CryptoFutureExchange"/> class using the specified
/// exchange hours to determine open/close times
/// </summary>
/// <param name="exchangeHours">Contains the weekly exchange schedule plus holidays</param>
public CryptoFutureExchange(SecurityExchangeHours exchangeHours)
: base(exchangeHours)
{
}
}
}
@@ -0,0 +1,75 @@
/*
* 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.
*/
namespace QuantConnect.Securities.CryptoFuture
{
/// <summary>
/// Crypto Future holdings implementation of the base securities class
/// </summary>
/// <seealso cref="SecurityHolding"/>
public class CryptoFutureHolding : SecurityHolding
{
/// <summary>
/// Crypto Future Holding Class constructor
/// </summary>
/// <param name="security">The crypto future security being held</param>
/// <param name="currencyConverter">A currency converter instance</param>
public CryptoFutureHolding(Security security, ICurrencyConverter currencyConverter)
: base(security, currencyConverter)
{
}
/// <summary>
/// Gets the total value of the specified <paramref name="quantity"/> of shares of this security
/// in the account currency
/// </summary>
/// <param name="quantity">The quantity of shares</param>
/// <param name="price">The current price</param>
/// <returns>The value of the quantity of shares in the account currency</returns>
public override ConvertibleCashAmount GetQuantityValue(decimal quantity, decimal price)
{
if (Symbol.ID.Market == Market.DYDX)
{
// common math quantity * quote price
return base.GetQuantityValue(quantity, price);
}
var cryptoFuture = (CryptoFuture)Security;
Cash cash;
decimal notionalPositionValue;
// We could check quote currency or the contract multiplier being 1
if (!cryptoFuture.IsCryptoCoinFuture())
{
// https://www.binance.com/en/support/faq/how-to-calculate-cost-required-to-open-a-position-in-perpetual-futures-contracts-87fa7ee33b574f7084d42bd2ce2e463b
// example BTCUSDT: (9,253.30 * 1 BTC) = 9,253.3 USDT
notionalPositionValue = price * quantity * cryptoFuture.SymbolProperties.ContractMultiplier;
// USDT is the QUOTE currency we will need to convert it into account currency
cash = cryptoFuture.QuoteCurrency;
}
else
{
// https://www.binance.com/en/support/faq/leverage-and-margin-in-coin-margined-futures-contracts-be2c7d9d95b04a7e8044ed02dd7dfe5c
// example BTCUSD: [ (10*100 USD) / 9,800 USD ] = 0.10204 BTC
notionalPositionValue = quantity * cryptoFuture.SymbolProperties.ContractMultiplier / price;
// BTC is the BASE currency we will need to convert it into account currency
cash = cryptoFuture.BaseCurrency;
}
return new ConvertibleCashAmount(notionalPositionValue, cash);
}
}
}
@@ -0,0 +1,184 @@
/*
* 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.Linq;
using QuantConnect.Orders;
namespace QuantConnect.Securities.CryptoFuture
{
/// <summary>
/// The crypto future margin model which supports both Coin and USDT futures
/// </summary>
public class CryptoFutureMarginModel : SecurityMarginModel
{
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="leverage">The leverage to use, used on initial margin requirements, default 25x</param>
/// <param name="maintenanceMarginRate">The maintenance margin rate, default 5%</param>
/// <param name="maintenanceAmount">The maintenance amount which will reduce maintenance margin requirements, default 0</param>
[Obsolete("This constructor is deprecated, please use the overload without maintenanceMarginRate and maintenanceAmount parameters.")]
public CryptoFutureMarginModel(decimal leverage, decimal maintenanceMarginRate = 0.05m, decimal maintenanceAmount = 0)
: base(leverage, 0)
{
}
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="leverage">The leverage to use, used on initial margin requirements, default 25x</param>
public CryptoFutureMarginModel(decimal leverage = 25)
: base(leverage, 0)
{
}
/// <summary>
/// Gets the margin currently alloted to the specified holding.
/// </summary>
/// <param name="parameters">An object containing the security</param>
/// <returns>The maintenance margin required for the option</returns>
public override MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
return new MaintenanceMargin(GetInitialMarginRequirement(new InitialMarginParameters(parameters.Security, parameters.Quantity)));
}
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
/// <param name="parameters">An object containing the security and quantity of shares</param>
/// <returns>The initial margin required for the option (i.e. the equity required to enter a position for this option)</returns>
public override InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
var security = parameters.Security;
var quantity = parameters.Quantity;
if (security?.GetLastData() == null || quantity == 0m)
{
return InitialMargin.Zero;
}
var positionValue = security.Holdings.GetQuantityValue(quantity, security.Price);
var marginRequirementInCollateral = Math.Abs(positionValue.Amount) / GetLeverage(security);
return new InitialMargin(marginRequirementInCollateral * positionValue.Cash.ConversionRate);
}
/// <summary>
/// Gets the margin cash available for a trade
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="direction">The direction of the trade</param>
/// <returns>The margin available for the trade</returns>
/// <remarks>What we do specially here is that instead of using the total portfolio value as potential margin remaining we only consider the collateral currency</remarks>
protected override decimal GetMarginRemaining(SecurityPortfolioManager portfolio, Security security, OrderDirection direction)
{
var collateralCurrency = GetCollateralCash(security);
var totalCollateralCurrency = GetTotalCollateralAmount(portfolio, security, collateralCurrency);
var result = totalCollateralCurrency;
foreach (var kvp in portfolio.Where(holdings => holdings.Value.Invested && holdings.Value.Type == SecurityType.CryptoFuture && holdings.Value.Symbol != security.Symbol))
{
var otherCryptoFuture = portfolio.Securities[kvp.Key];
// check if we share the collateral
if (SharesCollateral(portfolio, collateralCurrency, otherCryptoFuture))
{
// we reduce the available collateral based on total usage of all other positions too
result -= otherCryptoFuture.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForCurrentHoldings(otherCryptoFuture));
}
}
if (direction != OrderDirection.Hold)
{
var holdings = security.Holdings;
//If the order is in the same direction as holdings, our remaining cash is our cash
//In the opposite direction, our remaining cash is 2 x current value of assets + our cash
if (holdings.IsLong)
{
switch (direction)
{
case OrderDirection.Sell:
result +=
// portion of margin to close the existing position
this.GetMaintenanceMargin(security) +
// portion of margin to open the new position
this.GetInitialMarginRequirement(security, security.Holdings.AbsoluteQuantity);
break;
}
}
else if (holdings.IsShort)
{
switch (direction)
{
case OrderDirection.Buy:
result +=
// portion of margin to close the existing position
this.GetMaintenanceMargin(security) +
// portion of margin to open the new position
this.GetInitialMarginRequirement(security, security.Holdings.AbsoluteQuantity);
break;
}
}
}
result -= totalCollateralCurrency * RequiredFreeBuyingPowerPercent;
// convert into account currency
result *= collateralCurrency.ConversionRate;
return result < 0 ? 0 : result;
}
/// <summary>
/// Determines whether the given security shares collateral with another crypto future.
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="collateralCurrency">The collateral cash for the current security</param>
/// <param name="otherCryptoFuture">The other crypto future security to check</param>
/// <returns>True if both securities share the same collateral</returns>
protected virtual bool SharesCollateral(SecurityPortfolioManager portfolio, Cash collateralCurrency, Security otherCryptoFuture)
{
return collateralCurrency == GetCollateralCash(otherCryptoFuture);
}
/// <summary>
/// Helper method to determine what's the collateral currency for the given crypto future
/// </summary>
private static Cash GetCollateralCash(Security security)
{
var cryptoFuture = (CryptoFuture)security;
var collateralCurrency = cryptoFuture.BaseCurrency;
if (!cryptoFuture.IsCryptoCoinFuture())
{
collateralCurrency = cryptoFuture.QuoteCurrency;
}
return collateralCurrency;
}
/// <summary>
/// Gets the total collateral amount for the given crypto future position.
/// The base implementation returns only the primary collateral amount.
/// Override in subclasses to include supplementary collateral currencies.
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The crypto future security</param>
/// <param name="primaryCollateral">The primary collateral cash (e.g. USDT for non-coin futures, BTC for coin futures)</param>
/// <returns>Total collateral amount in terms of the primary collateral currency</returns>
protected virtual decimal GetTotalCollateralAmount(SecurityPortfolioManager portfolio, Security security, Cash primaryCollateral)
{
return primaryCollateral.Amount;
}
}
}
@@ -0,0 +1,25 @@
/*
* 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.
*
*/
namespace QuantConnect.Securities.CryptoFuture
{
/// <summary>
/// The responsibility of this model is to apply future funding rate cash flows to the portfolio based on open positions
/// </summary>
public class dYdXFutureMarginInterestRateModel : BinanceFutureMarginInterestRateModel
{
}
}
@@ -0,0 +1,110 @@
/*
* 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;
namespace QuantConnect.Securities.CurrencyConversion
{
/// <summary>
/// Provides an implementation of <see cref="ICurrencyConversion"/> with a fixed conversion rate
/// </summary>
public class ConstantCurrencyConversion : ICurrencyConversion
{
private decimal _conversionRate;
/// <summary>
/// Event fired when the conversion rate is updated
/// </summary>
public event EventHandler<decimal> ConversionRateUpdated;
/// <summary>
/// The currency this conversion converts from
/// </summary>
public string SourceCurrency { get; }
/// <summary>
/// The currency this conversion converts to
/// </summary>
public string DestinationCurrency { get; }
/// <summary>
/// The current conversion rate
/// </summary>
public decimal ConversionRate
{
get
{
return _conversionRate;
}
set
{
if (_conversionRate != value)
{
// only update if there was actually one
_conversionRate = value;
ConversionRateUpdated?.Invoke(this, value);
}
}
}
/// <summary>
/// The securities which the conversion rate is based on
/// </summary>
public IEnumerable<Security> ConversionRateSecurities => Enumerable.Empty<Security>();
/// <summary>
/// Initializes a new instance of the <see cref="ConstantCurrencyConversion"/> class.
/// </summary>
/// <param name="sourceCurrency">The currency this conversion converts from</param>
/// <param name="destinationCurrency">The currency this conversion converts to</param>
/// <param name="conversionRate">The conversion rate between the currencies</param>
public ConstantCurrencyConversion(string sourceCurrency, string destinationCurrency, decimal conversionRate = 1m)
{
SourceCurrency = sourceCurrency;
DestinationCurrency = destinationCurrency;
ConversionRate = conversionRate;
}
/// <summary>
/// Marks the conversion rate as potentially outdated, needing an update based on the latest data
/// </summary>
/// <remarks>This conversion is not based on securities, so we don't really need an update</remarks>
public void Update()
{
}
/// <summary>
/// Creates a new identity conversion, where the conversion rate is set to 1 and the source and destination currencies might the same
/// </summary>
/// <param name="sourceCurrency">The currency this conversion converts from</param>
/// <param name="destinationCurrency">The currency this conversion converts to. If null, the destination and source currencies are the same</param>
/// <returns>The identity currency conversion</returns>
public static ConstantCurrencyConversion Identity(string sourceCurrency, string destinationCurrency = null)
{
return new ConstantCurrencyConversion(sourceCurrency, destinationCurrency ?? sourceCurrency);
}
/// <summary>
/// Returns an instance of <see cref="ConstantCurrencyConversion"/> that represents a null conversion
/// </summary>
public static ConstantCurrencyConversion Null(string sourceCurrency, string destinationCurrency)
{
return new ConstantCurrencyConversion(sourceCurrency, destinationCurrency, 0m);
}
}
}
@@ -0,0 +1,58 @@
/*
* 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;
namespace QuantConnect.Securities.CurrencyConversion
{
/// <summary>
/// Represents a type capable of calculating the conversion rate between two currencies
/// </summary>
public interface ICurrencyConversion
{
/// <summary>
/// Event fired when the conversion rate is updated
/// </summary>
event EventHandler<decimal> ConversionRateUpdated;
/// <summary>
/// The currency this conversion converts from
/// </summary>
string SourceCurrency { get; }
/// <summary>
/// The currency this conversion converts to
/// </summary>
string DestinationCurrency { get; }
/// <summary>
/// The current conversion rate between <see cref="SourceCurrency"/> and <see cref="DestinationCurrency"/>
/// </summary>
decimal ConversionRate { get; set; }
/// <summary>
/// The securities which the conversion rate is based on
/// </summary>
IEnumerable<Security> ConversionRateSecurities { get; }
/// <summary>
/// Updates the internal conversion rate based on the latest data, and returns the new conversion rate
/// </summary>
/// <returns>The new conversion rate</returns>
void Update();
}
}
@@ -0,0 +1,281 @@
/*
* 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.Util;
namespace QuantConnect.Securities.CurrencyConversion
{
/// <summary>
/// Provides an implementation of <see cref="ICurrencyConversion"/> to find and use multi-leg currency conversions
/// </summary>
public class SecurityCurrencyConversion : ICurrencyConversion
{
/// <summary>
/// Class that holds the information of a single step in a multi-leg currency conversion
/// </summary>
private class Step
{
/// <summary>
/// The security used in this conversion step
/// </summary>
public Security RateSecurity { get; }
/// <summary>
/// Whether the price of the security must be inverted in the conversion
/// </summary>
public bool Inverted { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Step"/> class
/// </summary>
/// <param name="rateSecurity">The security to use in this currency conversion step</param>
/// <param name="inverted">Whether the price of the security should be inverted in the conversion</param>
public Step(Security rateSecurity, bool inverted)
{
RateSecurity = rateSecurity;
Inverted = inverted;
}
}
private readonly List<Step> _steps;
private decimal _conversionRate;
private bool _conversionRateNeedsUpdate;
/// <summary>
/// Event fired when the conversion rate is updated
/// </summary>
public event EventHandler<decimal> ConversionRateUpdated;
/// <summary>
/// The currency this conversion converts from
/// </summary>
public string SourceCurrency { get; }
/// <summary>
/// The currency this conversion converts to
/// </summary>
public string DestinationCurrency { get; }
/// <summary>
/// The current conversion rate
/// </summary>
public decimal ConversionRate
{
get
{
if (_conversionRateNeedsUpdate)
{
var newConversionRate = 1m;
var stepWithoutDataFound = false;
_steps.ForEach(step =>
{
if (stepWithoutDataFound)
{
return;
}
var lastData = step.RateSecurity.GetLastData();
if (lastData == null || lastData.Price == 0m)
{
newConversionRate = 0m;
stepWithoutDataFound = true;
return;
}
if (step.Inverted)
{
newConversionRate /= lastData.Price;
}
else
{
newConversionRate *= lastData.Price;
}
});
_conversionRateNeedsUpdate = false;
_conversionRate = newConversionRate;
ConversionRateUpdated?.Invoke(this, _conversionRate);
}
return _conversionRate;
}
set
{
if (_conversionRate != value)
{
// only update if there was actually one
_conversionRate = value;
_conversionRateNeedsUpdate = false;
ConversionRateUpdated?.Invoke(this, _conversionRate);
}
}
}
/// <summary>
/// The securities which the conversion rate is based on
/// </summary>
public IEnumerable<Security> ConversionRateSecurities => _steps.Select(step => step.RateSecurity);
/// <summary>
/// Initializes a new instance of the <see cref="SecurityCurrencyConversion"/> class.
/// This constructor is intentionally private as only <see cref="LinearSearch"/> is supposed to create it.
/// </summary>
/// <param name="sourceCurrency">The currency this conversion converts from</param>
/// <param name="destinationCurrency">The currency this conversion converts to</param>
/// <param name="steps">The steps between sourceCurrency and destinationCurrency</param>
private SecurityCurrencyConversion(string sourceCurrency, string destinationCurrency, List<Step> steps)
{
SourceCurrency = sourceCurrency;
DestinationCurrency = destinationCurrency;
_steps = steps;
}
/// <summary>
/// Signals an updates to the internal conversion rate based on the latest data.
/// It will set the conversion rate as potentially outdated so it gets re-calculated.
/// </summary>
public void Update()
{
_conversionRateNeedsUpdate = true;
}
/// <summary>
/// Finds a conversion between two currencies by looking through all available 1 and 2-leg options
/// </summary>
/// <param name="sourceCurrency">The currency to convert from</param>
/// <param name="destinationCurrency">The currency to convert to</param>
/// <param name="existingSecurities">The securities which are already added to the algorithm</param>
/// <param name="potentialSymbols">The symbols to consider, may overlap with existingSecurities</param>
/// <param name="makeNewSecurity">The function to call when a symbol becomes part of the conversion, must return the security that will provide price data about the symbol</param>
/// <returns>A new <see cref="SecurityCurrencyConversion"/> instance representing the conversion from sourceCurrency to destinationCurrency</returns>
/// <exception cref="ArgumentException">Thrown when no conversion from sourceCurrency to destinationCurrency can be found</exception>
public static SecurityCurrencyConversion LinearSearch(
string sourceCurrency,
string destinationCurrency,
IList<Security> existingSecurities,
IEnumerable<Symbol> potentialSymbols,
Func<Symbol, Security> makeNewSecurity)
{
var allSymbols = existingSecurities.Select(sec => sec.Symbol).Concat(potentialSymbols)
.Where(CurrencyPairUtil.IsDecomposable)
.ToList();
var securitiesBySymbol = existingSecurities.Aggregate(new Dictionary<Symbol, Security>(),
(mapping, security) =>
{
if (!mapping.ContainsKey(security.Symbol))
{
mapping[security.Symbol] = security;
}
return mapping;
});
// Search for 1 leg conversions
foreach (var potentialConversionRateSymbol in allSymbols)
{
var leg1Match = potentialConversionRateSymbol.ComparePair(sourceCurrency, destinationCurrency);
if (leg1Match == CurrencyPairUtil.Match.NoMatch)
{
continue;
}
var inverted = leg1Match == CurrencyPairUtil.Match.InverseMatch;
return new SecurityCurrencyConversion(sourceCurrency, destinationCurrency, new List<Step>(1)
{
CreateStep(potentialConversionRateSymbol, inverted, securitiesBySymbol, makeNewSecurity)
});
}
// Search for 2 leg conversions
foreach (var potentialConversionRateSymbol1 in allSymbols)
{
var middleCurrency = potentialConversionRateSymbol1.CurrencyPairDual(sourceCurrency);
if (middleCurrency == null)
{
continue;
}
foreach (var potentialConversionRateSymbol2 in allSymbols)
{
var leg2Match = potentialConversionRateSymbol2.ComparePair(middleCurrency, destinationCurrency);
if (leg2Match == CurrencyPairUtil.Match.NoMatch)
{
continue;
}
var secondStepInverted = leg2Match == CurrencyPairUtil.Match.InverseMatch;
var steps = new List<Step>(2);
// Step 1
string baseCurrency;
string quoteCurrency;
CurrencyPairUtil.DecomposeCurrencyPair(
potentialConversionRateSymbol1,
out baseCurrency,
out quoteCurrency);
steps.Add(CreateStep(potentialConversionRateSymbol1,
sourceCurrency == quoteCurrency,
securitiesBySymbol,
makeNewSecurity));
// Step 2
steps.Add(CreateStep(potentialConversionRateSymbol2,
secondStepInverted,
securitiesBySymbol,
makeNewSecurity));
return new SecurityCurrencyConversion(sourceCurrency, destinationCurrency, steps);
}
}
throw new ArgumentException(
$"No conversion path found between source currency {sourceCurrency} and destination currency {destinationCurrency}");
}
/// <summary>
/// Creates a new step
/// </summary>
/// <param name="symbol">The symbol of the step</param>
/// <param name="inverted">Whether the step is inverted or not</param>
/// <param name="existingSecurities">The existing securities, which are preferred over creating new ones</param>
/// <param name="makeNewSecurity">The function to call when a new security must be created</param>
private static Step CreateStep(
Symbol symbol,
bool inverted,
IDictionary<Symbol, Security> existingSecurities,
Func<Symbol, Security> makeNewSecurity)
{
Security security;
if (existingSecurities.TryGetValue(symbol, out security))
{
return new Step(security, inverted);
}
return new Step(makeNewSecurity(symbol), inverted);
}
}
}
+230
View File
@@ -0,0 +1,230 @@
/*
* 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.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities.Positions;
namespace QuantConnect.Securities
{
/// <summary>
/// Represents the model responsible for picking which orders should be executed during a margin call
/// </summary>
/// <remarks>
/// This is a default implementation that orders the generated margin call orders by the unrealized
/// profit (losers first) and executes each order synchronously until we're within the margin requirements
/// </remarks>
public class DefaultMarginCallModel : IMarginCallModel
{
/// <summary>
/// The percent margin buffer to use when checking whether the total margin used is
/// above the total portfolio value to generate margin call orders
/// </summary>
private readonly decimal _marginBuffer;
/// <summary>
/// Gets the portfolio that margin calls will be transacted against
/// </summary>
protected SecurityPortfolioManager Portfolio { get; }
/// <summary>
/// Gets the default order properties to be used in margin call orders
/// </summary>
protected IOrderProperties DefaultOrderProperties { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DefaultMarginCallModel"/> class
/// </summary>
/// <param name="portfolio">The portfolio object to receive margin calls</param>
/// <param name="defaultOrderProperties">The default order properties to be used in margin call orders</param>
/// <param name="marginBuffer">
/// The percent margin buffer to use when checking whether the total margin used is
/// above the total portfolio value to generate margin call orders
/// </param>
public DefaultMarginCallModel(SecurityPortfolioManager portfolio, IOrderProperties defaultOrderProperties, decimal marginBuffer = 0.10m)
{
Portfolio = portfolio;
DefaultOrderProperties = defaultOrderProperties;
_marginBuffer = marginBuffer;
}
/// <summary>
/// Scan the portfolio and the updated data for a potential margin call situation which may get the holdings below zero!
/// If there is a margin call, liquidate the portfolio immediately before the portfolio gets sub zero.
/// </summary>
/// <param name="issueMarginCallWarning">Set to true if a warning should be issued to the algorithm</param>
/// <returns>True for a margin call on the holdings.</returns>
public List<SubmitOrderRequest> GetMarginCallOrders(out bool issueMarginCallWarning)
{
issueMarginCallWarning = false;
var totalMarginUsed = Portfolio.TotalMarginUsed;
// don't issue a margin call if we're not using margin
if (totalMarginUsed <= 0)
{
return new List<SubmitOrderRequest>();
}
var totalPortfolioValue = Portfolio.TotalPortfolioValue;
var marginRemaining = Portfolio.GetMarginRemaining(totalPortfolioValue);
// issue a margin warning when we're down to 5% margin remaining
if (marginRemaining <= totalPortfolioValue * 0.05m)
{
issueMarginCallWarning = true;
}
// generate a listing of margin call orders
var marginCallOrders = new List<SubmitOrderRequest>();
// if we still have margin remaining then there's no need for a margin call
if (marginRemaining <= 0)
{
if (totalMarginUsed > totalPortfolioValue * (1 + _marginBuffer))
{
foreach (var positionGroup in Portfolio.Positions.Groups)
{
var positionMarginCallOrders = GenerateMarginCallOrders(
new MarginCallOrdersParameters(positionGroup, totalPortfolioValue, totalMarginUsed)).ToList();
if (positionMarginCallOrders.Count > 0 && positionMarginCallOrders.All(x => x.Quantity != 0))
{
marginCallOrders.AddRange(positionMarginCallOrders);
}
}
}
issueMarginCallWarning = marginCallOrders.Count > 0;
}
return marginCallOrders;
}
/// <summary>
/// Generates a new order for the specified security taking into account the total margin
/// used by the account. Returns null when no margin call is to be issued.
/// </summary>
/// <param name="parameters">The set of parameters required to generate the margin call orders</param>
/// <returns>An order object representing a liquidation order to be executed to bring the account within margin requirements</returns>
protected virtual IEnumerable<SubmitOrderRequest> GenerateMarginCallOrders(MarginCallOrdersParameters parameters)
{
var positionGroup = parameters.PositionGroup;
if (positionGroup.Positions.Any(position => Portfolio.Securities[position.Symbol].QuoteCurrency.ConversionRate == 0))
{
// check for div 0 - there's no conv rate, so we can't place an order
return Enumerable.Empty<SubmitOrderRequest>();
}
// compute the amount of quote currency we need to liquidate in order to get within margin requirements
var deltaAccountCurrency = parameters.TotalUsedMargin - parameters.TotalPortfolioValue;
var currentlyUsedBuyingPower = positionGroup.BuyingPowerModel.GetReservedBuyingPowerForPositionGroup(Portfolio, positionGroup);
// if currentlyUsedBuyingPower > deltaAccountCurrency, means we can keep using the diff in buying power
var buyingPowerToKeep = Math.Max(0, currentlyUsedBuyingPower - deltaAccountCurrency);
// we want a reduction so we send the inverse side of our position
var deltaBuyingPower = (currentlyUsedBuyingPower - buyingPowerToKeep) * -Math.Sign(positionGroup.Quantity);
var result = positionGroup.BuyingPowerModel.GetMaximumLotsForDeltaBuyingPower(new GetMaximumLotsForDeltaBuyingPowerParameters(
Portfolio, positionGroup, deltaBuyingPower,
// margin is negative, we need to reduce positions, no minimum
minimumOrderMarginPortfolioPercentage: 0
));
var absQuantity = Math.Abs(result.NumberOfLots);
var orderType = positionGroup.Count > 1 ? OrderType.ComboMarket : OrderType.Market;
GroupOrderManager groupOrderManager = null;
if (orderType == OrderType.ComboMarket)
{
groupOrderManager = new GroupOrderManager(Portfolio.Transactions.GetIncrementGroupOrderManagerId(), positionGroup.Count,
absQuantity);
}
return positionGroup.Positions.Select(position =>
{
var security = Portfolio.Securities[position.Symbol];
// Always reducing, so we take the absolute quantity times the opposite sign of the position
var legQuantity = absQuantity * position.UnitQuantity * -Math.Sign(position.Quantity);
return new SubmitOrderRequest(
orderType,
security.Type,
security.Symbol,
legQuantity.GetOrderLegGroupQuantity(groupOrderManager),
0,
0,
security.LocalTime.ConvertToUtc(security.Exchange.TimeZone),
Messages.DefaultMarginCallModel.MarginCallOrderTag,
DefaultOrderProperties?.Clone(),
groupOrderManager);
});
}
/// <summary>
/// Executes synchronous orders to bring the account within margin requirements.
/// </summary>
/// <param name="generatedMarginCallOrders">These are the margin call orders that were generated
/// by individual security margin models.</param>
/// <returns>The list of orders that were actually executed</returns>
public virtual List<OrderTicket> ExecuteMarginCall(IEnumerable<SubmitOrderRequest> generatedMarginCallOrders)
{
// if our margin used is back under the portfolio value then we can stop liquidating
if (Portfolio.MarginRemaining >= 0)
{
return new List<OrderTicket>();
}
// order by losers first
var executedOrders = new List<OrderTicket>();
var ordersWithSecurities = generatedMarginCallOrders.ToDictionary(x => x, x => Portfolio[x.Symbol]);
var groupManagerTemporalIds = -ordersWithSecurities.Count;
var orderedByLosers = ordersWithSecurities
// group orders by their group manager id so they are executed together
.GroupBy(x => x.Key.GroupOrderManager?.Id ?? groupManagerTemporalIds++)
.OrderBy(x => x.Sum(kvp => kvp.Value.UnrealizedProfit))
.Select(x => x.Select(kvp => kvp.Key));
foreach (var requests in orderedByLosers)
{
var tickets = new List<OrderTicket>();
foreach (var request in requests)
{
tickets.Add(Portfolio.Transactions.AddOrder(request));
}
foreach (var ticket in tickets)
{
if (ticket.Status.IsOpen())
{
Portfolio.Transactions.WaitForOrder(ticket.OrderId);
}
executedOrders.Add(ticket);
}
// if our margin used is back under the portfolio value then we can stop liquidating
if (Portfolio.MarginRemaining >= 0)
{
break;
}
}
return executedOrders;
}
}
}
+141
View File
@@ -0,0 +1,141 @@
/*
* 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;
namespace QuantConnect.Securities
{
/// <summary>
/// Represents the model responsible for applying cash settlement rules
/// </summary>
/// <remarks>This model applies cash settlement after T+N days</remarks>
public class DelayedSettlementModel : ISettlementModel
{
private readonly int _numberOfDays;
private readonly TimeSpan _timeOfDay;
private CashBook _cashBook;
/// <summary>
/// The list of pending funds waiting for settlement time
/// </summary>
private readonly Queue<UnsettledCashAmount> _unsettledCashAmounts;
/// <summary>
/// Creates an instance of the <see cref="DelayedSettlementModel"/> class
/// </summary>
/// <param name="numberOfDays">The number of days required for settlement</param>
/// <param name="timeOfDay">The time of day used for settlement</param>
public DelayedSettlementModel(int numberOfDays, TimeSpan timeOfDay)
{
_timeOfDay = timeOfDay;
_numberOfDays = numberOfDays;
_unsettledCashAmounts = new();
}
/// <summary>
/// Applies cash settlement rules
/// </summary>
/// <param name="applyFundsParameters">The funds application parameters</param>
public void ApplyFunds(ApplyFundsSettlementModelParameters applyFundsParameters)
{
var currency = applyFundsParameters.CashAmount.Currency;
var amount = applyFundsParameters.CashAmount.Amount;
var security = applyFundsParameters.Security;
var portfolio = applyFundsParameters.Portfolio;
if (amount > 0)
{
// positive amount: sell order filled
portfolio.UnsettledCashBook[currency].AddAmount(amount);
// find the correct settlement date (usually T+3 or T+1)
var settlementDate = applyFundsParameters.UtcTime.ConvertFromUtc(security.Exchange.TimeZone).Date;
for (var i = 0; i < _numberOfDays; i++)
{
settlementDate = settlementDate.AddDays(1);
// only count days when market is open
if (!security.Exchange.Hours.IsDateOpen(settlementDate))
i--;
}
// use correct settlement time
var settlementTimeUtc = settlementDate.Add(_timeOfDay).ConvertToUtc(security.Exchange.Hours.TimeZone);
lock (_unsettledCashAmounts)
{
_unsettledCashAmounts.Enqueue(new UnsettledCashAmount(settlementTimeUtc, currency, amount));
}
}
else
{
// negative amount: buy order filled
portfolio.CashBook[currency].AddAmount(amount);
}
// We just keep it to use currency conversion in GetUnsettledCash method
if (_cashBook == null)
{
_cashBook = portfolio.UnsettledCashBook;
}
}
/// <summary>
/// Scan for pending settlements
/// </summary>
/// <param name="settlementParameters">The settlement parameters</param>
public void Scan(ScanSettlementModelParameters settlementParameters)
{
lock (_unsettledCashAmounts)
{
while (_unsettledCashAmounts.TryPeek(out var item)
// check if settlement time has passed
&& settlementParameters.UtcTime >= item.SettlementTimeUtc)
{
// remove item from unsettled funds list
_unsettledCashAmounts.Dequeue();
// update unsettled cashbook
settlementParameters.Portfolio.UnsettledCashBook[item.Currency].AddAmount(-item.Amount);
// update settled cashbook
settlementParameters.Portfolio.CashBook[item.Currency].AddAmount(item.Amount);
}
}
}
/// <summary>
/// Gets the unsettled cash amount for the security
/// </summary>
public CashAmount GetUnsettledCash()
{
var accountCurrency = _cashBook != null ? _cashBook.AccountCurrency : Currencies.USD;
lock (_unsettledCashAmounts)
{
if (_unsettledCashAmounts.Count == 0)
{
return default;
}
return new CashAmount(_unsettledCashAmounts.Sum(x => _cashBook.ConvertToAccountCurrency(x.Amount, x.Currency)), accountCurrency);
}
}
}
}
+212
View File
@@ -0,0 +1,212 @@
/*
* 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Python.Runtime;
using QuantConnect.Data;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides access to a security's data via it's type. This implementation supports dynamic access
/// by type name.
/// </summary>
public class DynamicSecurityData : IDynamicMetaObjectProvider
{
private static readonly MethodInfo SetPropertyMethodInfo = typeof(DynamicSecurityData).GetMethod("SetProperty");
private static readonly MethodInfo GetPropertyMethodInfo = typeof(DynamicSecurityData).GetMethod("GetProperty");
private readonly IRegisteredSecurityDataTypesProvider _registeredTypes;
private readonly ConcurrentDictionary<Type, Type> _genericTypes = new ConcurrentDictionary<Type, Type>();
private readonly SecurityCache _cache;
/// <summary>
/// Initializes a new instance of the <see cref="DynamicSecurityData"/> class
/// </summary>
/// <param name="registeredTypes">Provides all the registered data types for the algorithm</param>
/// <param name="cache">The security cache</param>
public DynamicSecurityData(IRegisteredSecurityDataTypesProvider registeredTypes, SecurityCache cache)
{
_registeredTypes = registeredTypes;
_cache = cache;
}
/// <summary>Returns the <see cref="T:System.Dynamic.DynamicMetaObject" /> responsible for binding operations performed on this object.</summary>
/// <returns>The <see cref="T:System.Dynamic.DynamicMetaObject" /> to bind this object.</returns>
/// <param name="parameter">The expression tree representation of the runtime value.</param>
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return new GetSetPropertyDynamicMetaObject(parameter, this, SetPropertyMethodInfo, GetPropertyMethodInfo);
}
/// <summary>
/// Gets whether or not this dynamic data instance has data stored for the specified type
/// </summary>
public bool HasData<T>()
{
return _cache.HasData(typeof(T));
}
/// <summary>
/// Gets whether or not this dynamic data instance has a property with the specified name.
/// This is a case-insensitive search.
/// </summary>
/// <param name="name">The property name to check for</param>
/// <returns>True if the property exists, false otherwise</returns>
public bool HasProperty(string name)
{
Type type;
if (_registeredTypes.TryGetType(name, out type))
{
return _cache.HasData(type);
}
return false;
}
/// <summary>
/// Gets the last item in the data list for the specified type
/// </summary>
public T Get<T>()
{
var list = GetAll<T>();
return list.LastOrDefault();
}
/// <summary>
/// Gets the data list for the specified type
/// </summary>
public IReadOnlyList<T> GetAll<T>()
{
return GetAllImpl(typeof(T));
}
/// <summary>
/// Get the matching cached object in a python friendly accessor
/// </summary>
/// <param name="type">Type to search for</param>
/// <returns>Matching object</returns>
public PyObject Get(Type type)
{
var list = GetAll(type);
if (list.Count == 0)
{
return null;
}
using (Py.GIL())
{
return list[list.Count - 1].ToPython();
}
}
/// <summary>
/// Get all the matching types with a python friendly overload.
/// </summary>
/// <param name="type">Search type</param>
/// <returns>List of matching objects cached</returns>
public IList GetAll(Type type)
{
return GetAllImpl(type);
}
/// <summary>
/// Sets the property with the specified name to the value. This is a case-insensitve search.
/// </summary>
/// <param name="name">The property name to set</param>
/// <param name="value">The new property value</param>
/// <returns>Returns the input value back to the caller</returns>
[Obsolete("DynamicSecurityData is a view of the SecurityCache. It is readonly, properties can not be set")]
public object SetProperty(string name, object value)
{
throw new InvalidOperationException(Messages.DynamicSecurityData.PropertiesCannotBeSet);
}
/// <summary>
/// Gets the property's value with the specified name. This is a case-insensitve search.
/// </summary>
/// <param name="name">The property name to access</param>
/// <returns>object value of BaseData</returns>
public object GetProperty(string name)
{
// check to see if the requested name matches one of the algorithm registered data types and if
// so, we'll return a new empty list. this precludes us from always needing to check HasData<T>
Type type;
if (_registeredTypes.TryGetType(name, out type))
{
IReadOnlyList<BaseData> data;
if (_cache.TryGetValue(type, out data))
{
return data;
}
var listType = GetGenericListType(type);
return Activator.CreateInstance(listType);
}
throw new KeyNotFoundException(Messages.DynamicSecurityData.PropertyNotFound(name));
}
/// <summary>
/// Get all implementation that covers both Python and C#
/// </summary>
private dynamic GetAllImpl(Type type)
{
var data = GetProperty(type.Name);
var dataType = data.GetType();
if (dataType.GetElementType() == type // covers arrays
// covers lists
|| dataType.GenericTypeArguments.Length == 1
&& dataType.GenericTypeArguments[0] == type)
{
return data;
}
var baseDataList = data as IReadOnlyList<BaseData>;
if (baseDataList != null)
{
var listType = GetGenericListType(type);
var list = (IList)Activator.CreateInstance(listType);
foreach (var baseData in baseDataList)
{
list.Add(baseData);
}
return list;
}
throw new InvalidOperationException(Messages.DynamicSecurityData.UnexpectedTypesForGetAll(type, data));
}
private Type GetGenericListType(Type type)
{
Type containerType;
if (!_genericTypes.TryGetValue(type, out containerType))
{
// for performance we keep the generic type
_genericTypes[type] = containerType = typeof(List<>).MakeGenericType(type);
}
return containerType;
}
}
}
+62
View File
@@ -0,0 +1,62 @@
/*
* 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.Linq;
using System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Securities
{
/// <summary>
/// Derivate security universe selection filter which will always return empty
/// </summary>
public class EmptyContractFilter<T> : IDerivativeSecurityFilter<T>
where T : IChainUniverseData
{
/// <summary>
/// True if this universe filter can run async in the data stack
/// </summary>
public bool Asynchronous { get; set; } = true;
/// <summary>
/// Filters the input set of symbols represented by the universe
/// </summary>
/// <param name="universe">derivative symbols universe used in filtering</param>
/// <returns>The filtered set of symbols</returns>
public IDerivativeSecurityFilterUniverse<T> Filter(IDerivativeSecurityFilterUniverse<T> universe)
{
return new NoneIDerivativeSecurityFilterUniverse();
}
private class NoneIDerivativeSecurityFilterUniverse : IDerivativeSecurityFilterUniverse<T>
{
public DateTime LocalTime => default;
public int Count => 0;
public IEnumerator<T> GetEnumerator()
{
return Enumerable.Empty<T>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Enumerable.Empty<T>().GetEnumerator();
}
}
}
}
+159
View File
@@ -0,0 +1,159 @@
/*
* 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.Data;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
namespace QuantConnect.Securities.Equity
{
/// <summary>
/// Equity Security Type : Extension of the underlying Security class for equity specific behaviours.
/// </summary>
/// <seealso cref="Security"/>
public class Equity : Security
{
/// <summary>
/// The default number of days required to settle an equity sale
/// </summary>
public static int DefaultSettlementDays { get; set; } = 1;
/// <summary>
/// The default time of day for settlement
/// </summary>
public static readonly TimeSpan DefaultSettlementTime = new TimeSpan(6, 0, 0);
/// <summary>
/// Checks if the equity is a shortable asset. Note that this does not
/// take into account any open orders or existing holdings. To check if the asset
/// is currently shortable, use QCAlgorithm's ShortableQuantity property instead.
/// </summary>
/// <returns>True if the security is a shortable equity</returns>
public bool Shortable
{
get
{
var shortableQuantity = ShortableProvider.ShortableQuantity(Symbol, LocalTime);
// null means we don't have the data
return shortableQuantity == null || shortableQuantity > 0m;
}
}
/// <summary>
/// Gets the total quantity shortable for this security. This does not take into account
/// any open orders or existing holdings. To check the asset's currently shortable quantity,
/// use QCAlgorithm's ShortableQuantity property instead.
/// </summary>
/// <returns>Zero if not shortable, null if infinitely shortable, or a number greater than zero if shortable</returns>
public long? TotalShortableQuantity => ShortableProvider.ShortableQuantity(Symbol, LocalTime);
/// <summary>
/// Equity primary exchange.
/// </summary>
public Exchange PrimaryExchange { get; }
/// <summary>
/// Construct the Equity Object
/// </summary>
public Equity(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache,
Exchange primaryExchange = null)
: base(symbol,
quoteCurrency,
symbolProperties,
new EquityExchange(exchangeHours),
securityCache,
new SecurityPortfolioModel(),
new EquityFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new SecurityMarginModel(2m),
new EquityDataFilter(),
new AdjustedPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
Holdings = new EquityHolding(this, currencyConverter);
PrimaryExchange = primaryExchange ?? QuantConnect.Exchange.UNKNOWN;
}
/// <summary>
/// Construct the Equity Object
/// </summary>
public Equity(SecurityExchangeHours exchangeHours,
SubscriptionDataConfig config,
Cash quoteCurrency,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
Exchange primaryExchange = null)
: base(
config,
quoteCurrency,
symbolProperties,
new EquityExchange(exchangeHours),
new EquityCache(),
new SecurityPortfolioModel(),
new EquityFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new SecurityMarginModel(2m),
new EquityDataFilter(),
new AdjustedPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
Holdings = new EquityHolding(this, currencyConverter);
PrimaryExchange = primaryExchange ?? QuantConnect.Exchange.UNKNOWN;;
}
/// <summary>
/// Sets the data normalization mode to be used by this security
/// </summary>
public override void SetDataNormalizationMode(DataNormalizationMode mode)
{
base.SetDataNormalizationMode(mode);
if (mode == DataNormalizationMode.Adjusted)
{
PriceVariationModel = new AdjustedPriceVariationModel();
}
else
{
PriceVariationModel = new EquityPriceVariationModel();
}
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(Equity security) => security.Symbol;
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Equity
{
/// <summary>
/// Equity cache override.
/// </summary>
/// <remarks>Scheduled for obsolesence</remarks>
/// <seealso cref="SecurityCache"/>
public class EquityCache : SecurityCache
{
/// <summary>
/// Start a new Cache for the set Index Code
/// </summary>
public EquityCache()
: base()
{
}
}
}
@@ -0,0 +1,47 @@
/*
* 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 QuantConnect.Data;
namespace QuantConnect.Securities.Equity
{
/// <summary>
/// Equity security type data filter
/// </summary>
/// <seealso cref="SecurityDataFilter"/>
public class EquityDataFilter : SecurityDataFilter
{
/// <summary>
/// Initialize Data Filter Class:
/// </summary>
public EquityDataFilter() : base()
{
}
/// <summary>
/// Equity filter the data: true - accept, false - fail.
/// </summary>
/// <param name="data">Data class</param>
/// <param name="vehicle">Security asset</param>
public override bool Filter(Security vehicle, BaseData data)
{
// No data filter for bad ticks. All raw data will be piped into algorithm
return true;
}
} //End Filter
} //End Namespace
@@ -0,0 +1,51 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Equity
{
/// <summary>
/// Equity exchange information
/// </summary>
/// <seealso cref="SecurityExchange"/>
public class EquityExchange : SecurityExchange
{
/// <summary>
/// Number of trading days in an equity calendar year - 252
/// </summary>
public override int TradingDaysPerYear
{
get { return 252; }
}
/// <summary>
/// Initializes a new instance of the <see cref="EquityExchange"/> class using market hours
/// derived from the market-hours-database for the USA Equity market
/// </summary>
public EquityExchange()
: base(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.USA, null, SecurityType.Equity))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EquityExchange"/> class using the specified
/// exchange hours to determine open/close times
/// </summary>
/// <param name="exchangeHours">Contains the weekly exchange schedule plus holidays</param>
public EquityExchange(SecurityExchangeHours exchangeHours)
: base(exchangeHours)
{
}
}
}
+34
View File
@@ -0,0 +1,34 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Equity
{
/// <summary>
/// Holdings class for equities securities: no specific properties here but it is a placeholder for future equities specific behaviours.
/// </summary>
/// <seealso cref="SecurityHolding"/>
public class EquityHolding : SecurityHolding
{
/// <summary>
/// Constructor for equities holdings.
/// </summary>
/// <param name="security">The security being held</param>
/// <param name="currencyConverter">A currency converter instance</param>
public EquityHolding(Security security, ICurrencyConverter currencyConverter)
: base(security, currencyConverter)
{
}
}
}
@@ -0,0 +1,93 @@
/*
* 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;
namespace QuantConnect.Securities.Equity
{
/// <summary>
/// Short margin interest rate model
///
/// When shorting charges the fee rate provided by the <see cref="QuantConnect.Interfaces.IShortableProvider"/>.
/// When long adds the rebate fee provided by the <see cref="QuantConnect.Interfaces.IShortableProvider"/>.
/// </summary>
public class ShortMarginInterestRateModel : IMarginInterestRateModel
{
private bool _isShort;
private DateTime _previousTime;
/// <summary>
/// Accumulated shorting fee, negative means paid, positive earned.
///
/// Negative due to borrowing the asset to short, the fee rate.
/// Positive due to lending the asset for shorting, the rebate rate.
/// </summary>
public decimal Amount { get; set; }
/// <summary>
/// Apply margin interest rates to the portfolio
/// </summary>
/// <param name="marginInterestRateParameters">The parameters to use</param>
public void ApplyMarginInterestRate(MarginInterestRateParameters marginInterestRateParameters)
{
var security = marginInterestRateParameters.Security;
if (!security.Holdings.HoldStock)
{
// clear state
_previousTime = default;
return;
}
if (_previousTime == default || _isShort != security.Holdings.IsShort)
{
// start the clock on initial state or when changing sides
_isShort = security.Holdings.IsShort;
_previousTime = marginInterestRateParameters.Time;
return;
}
else if (marginInterestRateParameters.Time.Date == _previousTime.Date)
{
// charge once a day
return;
}
decimal? feeRate;
if (_isShort)
{
feeRate = security.ShortableProvider?.FeeRate(security.Symbol, security.LocalTime.Date);
}
else
{
feeRate = security.ShortableProvider?.RebateRate(security.Symbol, security.LocalTime.Date);
}
if (feeRate == null || feeRate.Value == 0)
{
// nothing todo
_previousTime = default;
return;
}
var dailyFeeRate = ((feeRate.Value * security.Holdings.HoldingsValue) / 360);
var fee = dailyFeeRate * (marginInterestRateParameters.Time.Date - _previousTime.Date).Days;
Amount += fee;
security.QuoteCurrency.AddAmount(fee);
// until next date
_previousTime = marginInterestRateParameters.Time;
}
}
}
@@ -0,0 +1,51 @@
/*
* 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;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides an implementation of <see cref="IPriceVariationModel"/>
/// for use in defining the minimum price variation for a given equity
/// under Regulation NMS Rule 612 (a.k.a the “sub-penny rule”)
/// </summary>
public class EquityPriceVariationModel : SecurityPriceVariationModel
{
/// <summary>
/// Get the minimum price variation from a security
/// </summary>
/// <param name="parameters">An object containing the method parameters</param>
/// <returns>Decimal minimum price variation of a given security</returns>
public override decimal GetMinimumPriceVariation(GetMinimumPriceVariationParameters parameters)
{
if (parameters.Security.Type != SecurityType.Equity)
{
throw new ArgumentException("EquityPriceVariationModel.GetMinimumPriceVariation(): " +
Messages.EquityPriceVariationModel.InvalidSecurityType(parameters.Security));
}
// If the quotation is priced less than $1.00 per share, the minimum pricing increment is $0.0001.
// Source: https://www.law.cornell.edu/cfr/text/17/242.612
if (parameters.ReferencePrice < 1m)
{
return 0.0001m;
}
return base.GetMinimumPriceVariation(parameters);
}
}
}
@@ -0,0 +1,57 @@
/*
* 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;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides an implementation of <see cref="ICurrencyConverter"/> for use in
/// tests that don't depend on this behavior.
/// </summary>
public class ErrorCurrencyConverter : ICurrencyConverter
{
/// <summary>
/// Gets account currency
/// </summary>
public string AccountCurrency
{
get
{
throw new InvalidOperationException(Messages.ErrorCurrencyConverter.AccountCurrencyUnexpectedUsage);
}
}
/// <summary>
/// Provides access to the single instance of <see cref="ErrorCurrencyConverter"/>.
/// This is done this way to ensure usage is explicit.
/// </summary>
public static ICurrencyConverter Instance = new ErrorCurrencyConverter();
private ErrorCurrencyConverter()
{
}
/// <summary>
/// Converts a cash amount to the account currency
/// </summary>
/// <param name="cashAmount">The <see cref="CashAmount"/> instance to convert</param>
/// <returns>A new <see cref="CashAmount"/> instance denominated in the account currency</returns>
public CashAmount ConvertToAccountCurrency(CashAmount cashAmount)
{
throw new InvalidOperationException(Messages.ErrorCurrencyConverter.ConvertToAccountCurrencyPurposefullyThrow);
}
}
}
+146
View File
@@ -0,0 +1,146 @@
/*
* 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.Data;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Util;
namespace QuantConnect.Securities.Forex
{
/// <summary>
/// FOREX Security Object Implementation for FOREX Assets
/// </summary>
/// <seealso cref="Security"/>
public class Forex : Security, IBaseCurrencySymbol
{
/// <summary>
/// Gets the currency acquired by going long this currency pair
/// </summary>
/// <remarks>
/// For example, the EUR/USD has a base currency of the euro, and as a result
/// of going long the EUR/USD a trader is acquiring euros in exchange for US dollars
/// </remarks>
public Cash BaseCurrency { get; protected set; }
/// <summary>
/// Constructor for the forex security
/// </summary>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="baseCurrency">The cash object that represent the base currency</param>
/// <param name="config">The subscription configuration for this security</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
public Forex(SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
Cash baseCurrency,
SubscriptionDataConfig config,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes)
: base(config,
quoteCurrency,
symbolProperties,
new ForexExchange(exchangeHours),
new ForexCache(),
new SecurityPortfolioModel(),
new ImmediateFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new SecurityMarginModel(50m),
new ForexDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
BaseCurrency = baseCurrency;
Holdings = new ForexHolding(this, currencyConverter);
}
/// <summary>
/// Constructor for the forex security
/// </summary>
/// <param name="symbol">The security's symbol</param>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="baseCurrency">The cash object that represent the base currency</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
/// <param name="securityCache">Cache for storing Security data</param>
public Forex(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
Cash baseCurrency,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache)
: base(symbol,
quoteCurrency,
symbolProperties,
new ForexExchange(exchangeHours),
securityCache,
new SecurityPortfolioModel(),
new ImmediateFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new SecurityMarginModel(50m),
new ForexDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
BaseCurrency = baseCurrency;
Holdings = new ForexHolding(this, currencyConverter);
}
/// <summary>
/// Decomposes the specified currency pair into a base and quote currency provided as out parameters
/// </summary>
/// <param name="currencyPair">The input currency pair to be decomposed, for example, "EURUSD"</param>
/// <param name="baseCurrency">The output base currency</param>
/// <param name="quoteCurrency">The output quote currency</param>
public static void DecomposeCurrencyPair(string currencyPair, out string baseCurrency, out string quoteCurrency)
{
if (!CurrencyPairUtil.IsForexDecomposable(currencyPair))
{
throw new ArgumentException($"Currency pairs must be exactly 6 characters: {currencyPair}");
}
baseCurrency = currencyPair.Substring(0, 3);
quoteCurrency = currencyPair.Substring(3);
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(Forex security) => security.Symbol;
}
}
+34
View File
@@ -0,0 +1,34 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Forex
{
/// <summary>
/// Forex specific caching support
/// </summary>
/// <remarks>Class is vitually empty and scheduled to be made obsolete. Potentially could be used for user data storage.</remarks>
/// <seealso cref="SecurityCache"/>
public class ForexCache : SecurityCache
{
/// <summary>
/// Initialize forex cache
/// </summary>
public ForexCache()
: base()
{
//Nothing to do:
}
} //End ForexCache Class
} //End Namespace
@@ -0,0 +1,48 @@
/*
* 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 QuantConnect.Data;
namespace QuantConnect.Securities.Forex
{
/// <summary>
/// Forex packet by packet data filtering mechanism for dynamically detecting bad ticks.
/// </summary>
/// <seealso cref="SecurityDataFilter"/>
public class ForexDataFilter : SecurityDataFilter
{
/// <summary>
/// Initialize forex data filter class:
/// </summary>
public ForexDataFilter()
: base()
{
}
/// <summary>
/// Forex data filter: a true value means accept the packet, a false means fail.
/// </summary>
/// <param name="data">Data object we're scanning to filter</param>
/// <param name="vehicle">Security asset</param>
public override bool Filter(Security vehicle, BaseData data)
{
//FX data is from FXCM and fairly clean already. Accept all packets.
return true;
}
} //End Filter
} //End Namespace
+52
View File
@@ -0,0 +1,52 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Forex
{
/// <summary>
/// Forex exchange class - information and helper tools for forex exchange properties
/// </summary>
/// <seealso cref="SecurityExchange"/>
public class ForexExchange : SecurityExchange
{
/// <summary>
/// Number of trading days per year for this security, used for performance statistics.
/// </summary>
public override int TradingDaysPerYear
{
// 365 - Saturdays = 313;
get { return 313; }
}
/// <summary>
/// Initializes a new instance of the <see cref="ForexExchange"/> class using market hours
/// derived from the market-hours-database for the FXCM Forex market
/// </summary>
public ForexExchange()
: base(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.FXCM, null, SecurityType.Forex))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ForexExchange"/> class using the specified
/// exchange hours to determine open/close times
/// </summary>
/// <param name="exchangeHours">Contains the weekly exchange schedule plus holidays</param>
public ForexExchange(SecurityExchangeHours exchangeHours)
: base(exchangeHours)
{
}
}
}
+48
View File
@@ -0,0 +1,48 @@
/*
* 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 static System.Math;
namespace QuantConnect.Securities.Forex
{
/// <summary>
/// FOREX holdings implementation of the base securities class
/// </summary>
/// <seealso cref="SecurityHolding"/>
public class ForexHolding : SecurityHolding
{
/// <summary>
/// Forex Holding Class
/// </summary>
/// <param name="security">The forex security being held</param>
/// <param name="currencyConverter">A currency converter instance</param>
public ForexHolding(Forex security, ICurrencyConverter currencyConverter)
: base(security, currencyConverter)
{
}
/// <summary>
/// Profit in pips if we closed the holdings right now including the approximate fees
/// </summary>
public decimal TotalCloseProfitPips()
{
var pipDecimal = Security.SymbolProperties.MinimumPriceVariation * 10;
var exchangeRate = Security.QuoteCurrency.ConversionRate;
var pipCashCurrencyValue = (pipDecimal * AbsoluteQuantity * exchangeRate);
return Round((TotalCloseProfit() / pipCashCurrencyValue), 1);
}
}
}
@@ -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.
*
*/
using System;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides a functional implementation of <see cref="IDerivativeSecurityFilter{T}"/>
/// </summary>
public class FuncSecurityDerivativeFilter<T> : IDerivativeSecurityFilter<T>
where T : IChainUniverseData
{
private readonly Func<IDerivativeSecurityFilterUniverse<T>, IDerivativeSecurityFilterUniverse<T>> _filter;
/// <summary>
/// True if this universe filter can run async in the data stack
/// </summary>
public bool Asynchronous { get; set; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="FuncSecurityDerivativeFilter{T}"/> class
/// </summary>
/// <param name="filter">The functional implementation of the <see cref="Filter"/> method</param>
public FuncSecurityDerivativeFilter(Func<IDerivativeSecurityFilterUniverse<T>, IDerivativeSecurityFilterUniverse<T>> filter)
{
_filter = filter;
}
/// <summary>
/// Filters the input set of symbols represented by the universe
/// </summary>
/// <param name="universe">Derivative symbols universe used in filtering</param>
/// <returns>The filtered set of symbols</returns>
public IDerivativeSecurityFilterUniverse<T> Filter(IDerivativeSecurityFilterUniverse<T> universe)
{
return _filter(universe);
}
}
}
@@ -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.
*
*/
using System;
using Python.Runtime;
using QuantConnect.Util;
namespace QuantConnect.Securities
{
/// <summary>
/// Provides a functional implementation of <see cref="ISecurityInitializer"/>
/// </summary>
public class FuncSecurityInitializer : ISecurityInitializer
{
private readonly Action<Security> _initializer;
/// <summary>
/// Initializes a new instance of the <see cref="FuncSecurityInitializer"/> class
/// </summary>
/// <param name="initializer">The functional implementation of <see cref="ISecurityInitializer.Initialize"/></param>
public FuncSecurityInitializer(PyObject initializer)
{
_initializer = PythonUtil.ToAction<Security>(initializer);
if (_initializer == null)
{
throw new InvalidOperationException("FuncSecurityInitializer constructor requires an action taking a single security instance as an argument");
}
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncSecurityInitializer"/> class
/// </summary>
/// <param name="initializer">The functional implementation of <see cref="ISecurityInitializer.Initialize"/></param>
public FuncSecurityInitializer(Action<Security> initializer)
{
_initializer = initializer;
}
/// <summary>
/// Initializes the specified security
/// </summary>
/// <param name="security">The security to be initialized</param>
public void Initialize(Security security)
{
_initializer(security);
}
}
}
+107
View File
@@ -0,0 +1,107 @@
/*
* 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 Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Logging;
using System.Collections.Generic;
namespace QuantConnect.Securities
{
/// <summary>
/// Seed a security price from a history function
/// </summary>
public class FuncSecuritySeeder : ISecuritySeeder
{
private readonly Func<Security, IEnumerable<BaseData>> _seedFunction;
/// <summary>
/// Constructor that takes as a parameter the security used to seed the price
/// </summary>
/// <param name="seedFunction">The seed function to use</param>
public FuncSecuritySeeder(PyObject seedFunction)
{
var result = seedFunction.SafeAs<Func<Security, object>>();
_seedFunction = security =>
{
var dataObject = result(security);
var dataPoint = dataObject as BaseData;
if (dataPoint != null)
{
return new[] { dataPoint };
}
return (IEnumerable<BaseData>)dataObject;
};
}
/// <summary>
/// Constructor that takes as a parameter the security used to seed the price
/// </summary>
/// <param name="seedFunction">The seed function to use</param>
public FuncSecuritySeeder(Func<Security, BaseData> seedFunction)
: this(security => { return new []{ seedFunction(security) }; })
{
}
/// <summary>
/// Constructor that takes as a parameter the security used to seed the price
/// </summary>
/// <param name="seedFunction">The seed function to use</param>
public FuncSecuritySeeder(Func<Security, IEnumerable<BaseData>> seedFunction)
{
_seedFunction = seedFunction;
}
/// <summary>
/// Seed the security
/// </summary>
/// <param name="security"><see cref="Security"/> being seeded</param>
/// <returns>true if the security was seeded, false otherwise</returns>
public bool SeedSecurity(Security security)
{
try
{
// Do not seed canonical symbols
if (!security.Symbol.IsCanonical())
{
var gotData = false;
foreach (var seedData in _seedFunction(security))
{
gotData = true;
security.SetMarketPrice(seedData);
Log.Debug("FuncSecuritySeeder.SeedSecurity(): " + Messages.FuncSecuritySeeder.SeededSecurityInfo(seedData));
}
if (!gotData)
{
Log.Trace("FuncSecuritySeeder.SeedSecurity(): " + Messages.FuncSecuritySeeder.UnableToSeedSecurity(security));
return false;
}
}
}
catch (Exception exception)
{
Log.Trace("FuncSecuritySeeder.SeedSecurity(): " + Messages.FuncSecuritySeeder.UnableToSecurityPrice(security) + $": {exception}");
return false;
}
return true;
}
}
}
@@ -0,0 +1,39 @@
/*
* 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.Interfaces;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// An implementation of <see cref="IFutureChainProvider"/> that always returns an empty list of contracts
/// </summary>
public class EmptyFutureChainProvider : IFutureChainProvider
{
/// <summary>
/// Gets the list of future contracts for a given underlying symbol
/// </summary>
/// <param name="symbol">The underlying symbol</param>
/// <param name="date">The date for which to request the future chain (only used in backtesting)</param>
/// <returns>The list of future contracts</returns>
public IEnumerable<Symbol> GetFutureContractList(Symbol symbol, DateTime date)
{
return Enumerable.Empty<Symbol>();
}
}
}
+269
View File
@@ -0,0 +1,269 @@
/*
* 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.Data;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using Python.Runtime;
using QuantConnect.Util;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Futures Security Object Implementation for Futures Assets
/// </summary>
/// <seealso cref="Security"/>
public class Future : Security, IContinuousSecurity
{
private bool _isTradable;
/// <summary>
/// Gets or sets whether or not this security should be considered tradable
/// </summary>
/// <remarks>Canonical futures are not tradable</remarks>
public override bool IsTradable
{
get
{
// once a future is removed it is no longer tradable
return _isTradable && !Symbol.IsCanonical();
}
set
{
_isTradable = value;
}
}
/// <summary>
/// The default number of days required to settle a futures sale
/// </summary>
public const int DefaultSettlementDays = 1;
/// <summary>
/// The default time of day for settlement
/// </summary>
public static readonly TimeSpan DefaultSettlementTime = new TimeSpan(6, 0, 0);
/// <summary>
/// Constructor for the Future security
/// </summary>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="config">The subscription configuration for this security</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
public Future(SecurityExchangeHours exchangeHours,
SubscriptionDataConfig config,
Cash quoteCurrency,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes
)
: base(config,
quoteCurrency,
symbolProperties,
new FutureExchange(exchangeHours),
new FutureCache(),
new SecurityPortfolioModel(),
new FutureFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new FutureSettlementModel(),
Securities.VolatilityModel.Null,
null,
new SecurityDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
BuyingPowerModel = new FutureMarginModel(0, this);
// for now all futures are cash settled as we don't allow underlying (Live Cattle?) to be posted on the account
SettlementType = SettlementType.Cash;
Holdings = new FutureHolding(this, currencyConverter);
ContractFilter = new EmptyContractFilter<FutureUniverse>();
}
/// <summary>
/// Constructor for the Future security
/// </summary>
/// <param name="symbol">The subscription security symbol</param>
/// <param name="exchangeHours">Defines the hours this exchange is open</param>
/// <param name="quoteCurrency">The cash object that represent the quote currency</param>
/// <param name="symbolProperties">The symbol properties for this security</param>
/// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
/// instances into units of the account currency</param>
/// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
/// <param name="securityCache">Cache to store security information</param>
public Future(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
SymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache)
: base(symbol,
quoteCurrency,
symbolProperties,
new FutureExchange(exchangeHours),
securityCache,
new SecurityPortfolioModel(),
new FutureFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new FutureSettlementModel(),
Securities.VolatilityModel.Null,
null,
new SecurityDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
Securities.MarginInterestRateModel.Null
)
{
BuyingPowerModel = new FutureMarginModel(0, this);
// for now all futures are cash settled as we don't allow underlying (Live Cattle?) to be posted on the account
SettlementType = SettlementType.Cash;
Holdings = new FutureHolding(this, currencyConverter);
ContractFilter = new EmptyContractFilter<FutureUniverse>();
}
/// <summary>
/// Returns true if this is the future chain security, false if it is a specific future contract
/// </summary>
public bool IsFutureChain => Symbol.IsCanonical();
/// <summary>
/// Returns true if this is a specific future contract security, false if it is the future chain security
/// </summary>
public bool IsFutureContract => !Symbol.IsCanonical();
/// <summary>
/// Gets the expiration date
/// </summary>
public DateTime Expiry
{
get { return Symbol.ID.Date; }
}
/// <summary>
/// Specifies if futures contract has physical or cash settlement on settlement
/// </summary>
public SettlementType SettlementType
{
get; set;
}
/// <summary>
/// Gets or sets the currently mapped symbol for the security
/// </summary>
public Symbol Mapped
{
get; set;
}
/// <summary>
/// Gets or sets the contract filter
/// </summary>
public IDerivativeSecurityFilter<FutureUniverse> ContractFilter
{
get; set;
}
/// <summary>
/// Sets the <see cref="LocalTimeKeeper"/> to be used for this <see cref="Security"/>.
/// This is the source of this instance's time.
/// </summary>
/// <param name="localTimeKeeper">The source of this <see cref="Security"/>'s time.</param>
public override void SetLocalTimeKeeper(LocalTimeKeeper localTimeKeeper)
{
base.SetLocalTimeKeeper(localTimeKeeper);
var model = SettlementModel as FutureSettlementModel;
if (model != null)
{
model.SetLocalDateTimeFrontier(LocalTime);
}
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new instance of the filter
/// using the specified expiration range values
/// </summary>
/// <param name="minExpiry">The minimum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiry">The maximum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in more than 10 days</param>
public void SetFilter(TimeSpan minExpiry, TimeSpan maxExpiry)
{
SetFilterImp(universe => universe.Expiration(minExpiry, maxExpiry));
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new instance of the filter
/// using the specified expiration range values
/// </summary>
/// <param name="minExpiryDays">The minimum time, expressed in days, until expiry to include, for example, 10
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiryDays">The maximum time, expressed in days, until expiry to include, for example, 10
/// would exclude contracts expiring in more than 10 days</param>
public void SetFilter(int minExpiryDays, int maxExpiryDays)
{
SetFilterImp(universe => universe.Expiration(minExpiryDays, maxExpiryDays));
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new universe selection function
/// </summary>
/// <param name="universeFunc">new universe selection function</param>
public void SetFilter(Func<FutureFilterUniverse, FutureFilterUniverse> universeFunc)
{
SetFilterImp(universeFunc);
ContractFilter.Asynchronous = false;
}
/// <summary>
/// Sets the <see cref="ContractFilter"/> to a new universe selection function
/// </summary>
/// <param name="universeFunc">new universe selection function</param>
public void SetFilter(PyObject universeFunc)
{
var pyUniverseFunc = PythonUtil.ToFunc<FutureFilterUniverse, FutureFilterUniverse>(universeFunc);
SetFilter(pyUniverseFunc);
}
private void SetFilterImp(Func<FutureFilterUniverse, FutureFilterUniverse> universeFunc)
{
Func<IDerivativeSecurityFilterUniverse<FutureUniverse>, IDerivativeSecurityFilterUniverse<FutureUniverse>> func = universe =>
{
var futureUniverse = universe as FutureFilterUniverse;
var result = universeFunc(futureUniverse);
return result.ApplyTypesFilter();
};
ContractFilter = new FuncSecurityDerivativeFilter<FutureUniverse>(func);
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(Future security) => security.Symbol;
}
}
+56
View File
@@ -0,0 +1,56 @@
/*
* 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 QuantConnect.Data;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Future specific caching support
/// </summary>
/// <seealso cref="SecurityCache"/>
public class FutureCache : SecurityCache
{
/// <summary>
/// The current settlement price
/// </summary>
public decimal SettlementPrice { get; set; }
/// <summary>
/// Will consume the given data point updating the cache state and it's properties
/// </summary>
/// <param name="data">The data point to process</param>
/// <param name="cacheByType">True if this data point should be cached by type</param>
protected override void ProcessDataPoint(BaseData data, bool cacheByType)
{
base.ProcessDataPoint(data, cacheByType);
SettlementPrice = Price;
}
/// <summary>
/// Stores the specified data list in the cache, updating the open interest from any chain universe data
/// </summary>
/// <param name="data">The collection of data to store in this cache</param>
/// <param name="dataType">The data type</param>
public override void StoreData(IReadOnlyList<BaseData> data, Type dataType)
{
UpdateOpenInterest(data);
base.StoreData(data, dataType);
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Future exchange class - information and helper tools for future exchange properties
/// </summary>
/// <seealso cref="SecurityExchange"/>
public class FutureExchange : SecurityExchange
{
/// <summary>
/// Number of trading days per year for this security, 252.
/// </summary>
/// <remarks>Used for performance statistics to calculate sharpe ratio accurately</remarks>
public override int TradingDaysPerYear
{
get { return 252; }
}
/// <summary>
/// Initializes a new instance of the <see cref="FutureExchange"/> class using the specified
/// exchange hours to determine open/close times
/// </summary>
/// <param name="exchangeHours">Contains the weekly exchange schedule plus holidays</param>
public FutureExchange(SecurityExchangeHours exchangeHours)
: base(exchangeHours)
{
}
}
}
@@ -0,0 +1,113 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Static class contains definitions of popular futures expiration cycles
/// </summary>
public static class FutureExpirationCycles
{
/// <summary>
/// January Cycle: Expirations in January, April, July, October (the first month of each quarter)
/// </summary>
public static readonly int[] January = { 1, 4, 7, 10 };
/// <summary>
/// February Cycle: Expirations in February, May, August, November (second month)
/// </summary>
public static readonly int[] February = { 2, 5, 8, 11 };
/// <summary>
/// March Cycle: Expirations in March, June, September, December (third month)
/// </summary>
public static readonly int[] March = { 3, 6, 9, 12 };
/// <summary>
/// December Cycle: Expirations in December
/// </summary>
public static readonly int[] December = { 12 };
/// <summary>
/// All Year Cycle: Expirations in every month of the year
/// </summary>
public static readonly int[] AllYear = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
/// <summary>
/// GJMQVZ Cycle
/// </summary>
public static readonly int[] GJMQVZ = { 2, 4, 6, 8, 10, 12 };
/// <summary>
/// GJKMNQVZ Cycle
/// </summary>
public static readonly int[] GJKMNQVZ = { 2, 4, 5, 6, 7, 8, 10, 12 };
/// <summary>
/// HMUZ Cycle
/// </summary>
public static readonly int[] HMUZ = March;
/// <summary>
/// HKNUZ Cycle
/// </summary>
public static readonly int[] HKNUZ = { 3, 5, 7, 9, 12 };
/// <summary>
/// HKNV Cycle
/// </summary>
public static readonly int[] HKNV = { 3, 5, 7, 10 };
/// <summary>
/// HKNVZ Cycle
/// </summary>
public static readonly int[] HKNVZ = { 3, 5, 7, 10, 12 };
/// <summary>
/// FHKNUX Cycle
/// </summary>
public static readonly int[] FHKNUX = { 1, 3, 5, 7, 9, 11 };
/// <summary>
/// FHJKQUVX Cycle
/// </summary>
public static readonly int[] FHJKQUVX = { 1, 3, 4, 5, 8, 9, 10, 11 };
/// <summary>
/// HKNUVZ Cycle
/// </summary>
public static readonly int[] HKNUVZ = { 3, 5, 7, 9, 10, 12 };
/// <summary>
/// FHKNQUVZ Cycle
/// </summary>
public static readonly int[] FHKNUVZ = { 1, 3, 5, 7, 9, 10, 12 };
/// <summary>
/// FHKMQUVZ Cycle
/// </summary>
public static readonly int[] FHKNQUVZ = { 1, 3, 5, 7, 8, 9, 10, 12 };
/// <summary>
/// FHKNQUX Cycle
/// </summary>
public static readonly int[] FHKNQUX = { 1, 3, 5, 7, 8, 9, 11 };
/// <summary>
/// FGHJKMNQUVXZ Cycle
/// </summary>
public static readonly int[] FGHJKMNQUVXZ = AllYear;
}
}
@@ -0,0 +1,114 @@
/*
* 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.Data.UniverseSelection;
using QuantConnect.Securities.Future;
using QuantConnect.Util;
namespace QuantConnect.Securities
{
/// <summary>
/// Represents futures symbols universe used in filtering.
/// </summary>
public class FutureFilterUniverse : ContractSecurityFilterUniverse<FutureFilterUniverse, FutureUniverse>
{
/// <summary>
/// Constructs FutureFilterUniverse
/// </summary>
public FutureFilterUniverse(IReadOnlyList<FutureUniverse> allData, DateTime localTime)
: base(allData, localTime)
{
}
/// <summary>
/// Determine if the given Future contract symbol is standard
/// </summary>
/// <returns>True if contract is standard</returns>
protected override bool IsStandard(Symbol symbol)
{
return FutureSymbol.IsStandard(symbol);
}
/// <summary>
/// Creates a new instance of the data type for the given symbol
/// </summary>
/// <returns>A data instance for the given symbol, which is just the symbol itself</returns>
protected override FutureUniverse CreateDataInstance(Symbol symbol)
{
return new FutureUniverse()
{
Symbol = symbol,
Time = LocalTime
};
}
/// <summary>
/// Applies filter selecting futures contracts based on expiration cycles. See <see cref="FutureExpirationCycles"/> for details
/// </summary>
/// <param name="months">Months to select contracts from</param>
/// <returns>Universe with filter applied</returns>
public FutureFilterUniverse ExpirationCycle(int[] months)
{
var monthHashSet = months.ToHashSet();
return this.Where(x => monthHashSet.Contains(x.ID.Date.Month));
}
}
/// <summary>
/// Extensions for Linq support
/// </summary>
public static class FutureFilterUniverseEx
{
/// <summary>
/// Filters universe
/// </summary>
/// <param name="universe">Universe to apply the filter too</param>
/// <param name="predicate">Bool function to determine which Symbol are filtered</param>
/// <returns><see cref="FutureFilterUniverse"/> with filter applied</returns>
public static FutureFilterUniverse Where(this FutureFilterUniverse universe, Func<FutureUniverse, bool> predicate)
{
universe.Data = universe.Data.Where(predicate).ToList();
return universe;
}
/// <summary>
/// Maps universe
/// </summary>
/// <param name="universe">Universe to apply the filter too</param>
/// <param name="mapFunc">Symbol function to determine which Symbols are filtered</param>
/// <returns><see cref="FutureFilterUniverse"/> with filter applied</returns>
public static FutureFilterUniverse Select(this FutureFilterUniverse universe, Func<FutureUniverse, Symbol> mapFunc)
{
universe.AllSymbols = universe.Data.Select(mapFunc).ToList();
return universe;
}
/// <summary>
/// Binds universe
/// </summary>
/// <param name="universe">Universe to apply the filter too</param>
/// <param name="mapFunc">Symbols function to determine which Symbols are filtered</param>
/// <returns><see cref="FutureFilterUniverse"/> with filter applied</returns>
public static FutureFilterUniverse SelectMany(this FutureFilterUniverse universe, Func<FutureUniverse, IEnumerable<Symbol>> mapFunc)
{
universe.AllSymbols = universe.Data.SelectMany(mapFunc).ToList();
return universe;
}
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Future holdings implementation of the base securities class
/// </summary>
/// <seealso cref="SecurityHolding"/>
public class FutureHolding : SecurityHolding
{
/// <summary>
/// The cash settled profit for the current open position
/// </summary>
public virtual decimal SettledProfit { get; set; }
/// <summary>
/// Unsettled profit for the current open position <see cref="SettledProfit"/>
/// </summary>
public virtual decimal UnsettledProfit
{
get
{
return TotalCloseProfit() - SettledProfit;
}
}
/// <summary>
/// Future Holding Class constructor
/// </summary>
/// <param name="security">The future security being held</param>
/// <param name="currencyConverter">A currency converter instance</param>
public FutureHolding(Security security, ICurrencyConverter currencyConverter)
: base(security, currencyConverter)
{
}
}
}
@@ -0,0 +1,305 @@
/*
* 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.IO;
using System.Linq;
using QuantConnect.Util;
using QuantConnect.Logging;
using System.Threading.Tasks;
using QuantConnect.Interfaces;
using QuantConnect.Orders.Fees;
using QuantConnect.Configuration;
using System.Collections.Generic;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Represents a simple margin model for margin futures. Margin file contains Initial and Maintenance margins
/// </summary>
public class FutureMarginModel : SecurityMarginModel
{
private static IDataProvider _dataProvider;
private static readonly object _locker = new();
private static Dictionary<string, MarginRequirementsEntry[]> _marginRequirementsCache = new();
// historical database of margin requirements
private int _marginCurrentIndex;
private readonly Security _security;
/// <summary>
/// True will enable usage of intraday margins.
/// </summary>
/// <remarks>Disabled by default. Note that intraday margins are less than overnight margins
/// and could lead to margin calls</remarks>
public bool EnableIntradayMargins { get; set; }
/// <summary>
/// Initial Overnight margin requirement for the contract effective from the date of change
/// </summary>
public virtual decimal InitialOvernightMarginRequirement => GetCurrentMarginRequirements(_security)?.InitialOvernight ?? 0m;
/// <summary>
/// Maintenance Overnight margin requirement for the contract effective from the date of change
/// </summary>
public virtual decimal MaintenanceOvernightMarginRequirement => GetCurrentMarginRequirements(_security)?.MaintenanceOvernight ?? 0m;
/// <summary>
/// Initial Intraday margin for the contract effective from the date of change
/// </summary>
public virtual decimal InitialIntradayMarginRequirement => GetCurrentMarginRequirements(_security)?.InitialIntraday ?? 0m;
/// <summary>
/// Maintenance Intraday margin requirement for the contract effective from the date of change
/// </summary>
public virtual decimal MaintenanceIntradayMarginRequirement => GetCurrentMarginRequirements(_security)?.MaintenanceIntraday ?? 0m;
/// <summary>
/// Initializes a new instance of the <see cref="FutureMarginModel"/>
/// </summary>
/// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required unused buying power for the account.</param>
/// <param name="security">The security that this model belongs to</param>
public FutureMarginModel(decimal requiredFreeBuyingPowerPercent = 0, Security security = null)
{
RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent;
_security = security;
}
/// <summary>
/// Gets the current leverage of the security
/// </summary>
/// <param name="security">The security to get leverage for</param>
/// <returns>The current leverage in the security</returns>
public override decimal GetLeverage(Security security)
{
return 1;
}
/// <summary>
/// Sets the leverage for the applicable securities, i.e, futures
/// </summary>
/// <remarks>
/// This is added to maintain backwards compatibility with the old margin/leverage system
/// </remarks>
/// <param name="security"></param>
/// <param name="leverage">The new leverage</param>
public override void SetLeverage(Security security, decimal leverage)
{
// Futures are leveraged products and different leverage cannot be set by user.
throw new InvalidOperationException("Futures are leveraged products and different leverage cannot be set by user");
}
/// <summary>
/// Get the maximum market order quantity to obtain a position with a given buying power percentage.
/// Will not take into account free buying power.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
public override GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(
GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters)
{
if (Math.Abs(parameters.TargetBuyingPower) > 1)
{
throw new InvalidOperationException(
"Futures do not allow specifying a leveraged target, since they are traded using margin which already is leveraged. " +
$"Possible target buying power goes from -1 to 1, target provided is: {parameters.TargetBuyingPower}");
}
return base.GetMaximumOrderQuantityForTargetBuyingPower(parameters);
}
/// <summary>
/// Gets the total margin required to execute the specified order in units of the account currency including fees
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>The total margin in terms of the currency quoted in the order</returns>
public override InitialMargin GetInitialMarginRequiredForOrder(
InitialMarginRequiredForOrderParameters parameters
)
{
//Get the order value from the non-abstract order classes (MarketOrder, LimitOrder, StopMarketOrder)
//Market order is approximated from the current security price and set in the MarketOrder Method in QCAlgorithm.
var fees = parameters.Security.FeeModel.GetOrderFee(
new OrderFeeParameters(parameters.Security,
parameters.Order)).Value;
var feesInAccountCurrency = parameters.CurrencyConverter.
ConvertToAccountCurrency(fees).Amount;
var orderMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Order.Quantity);
return new InitialMargin(orderMargin + Math.Sign(orderMargin) * feesInAccountCurrency);
}
/// <summary>
/// Gets the margin currently allotted to the specified holding
/// </summary>
/// <param name="parameters">An object containing the security</param>
/// <returns>The maintenance margin required for the </returns>
public override MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
if (parameters.Quantity == 0m)
{
return 0m;
}
var security = parameters.Security;
var marginReq = GetCurrentMarginRequirements(security);
if (marginReq == null)
{
return 0m;
}
if (EnableIntradayMargins
&& security.Exchange.ExchangeOpen
&& !security.Exchange.ClosingSoon)
{
return marginReq.MaintenanceIntraday * parameters.AbsoluteQuantity * security.QuoteCurrency.ConversionRate;
}
// margin is per contract
return marginReq.MaintenanceOvernight * parameters.AbsoluteQuantity * security.QuoteCurrency.ConversionRate;
}
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
public override InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
var security = parameters.Security;
var quantity = parameters.Quantity;
if (quantity == 0m)
{
return InitialMargin.Zero;
}
var marginReq = GetCurrentMarginRequirements(security);
if (marginReq == null)
{
return InitialMargin.Zero;
}
if (EnableIntradayMargins
&& security.Exchange.ExchangeOpen
&& !security.Exchange.ClosingSoon)
{
return new InitialMargin(marginReq.InitialIntraday * quantity * security.QuoteCurrency.ConversionRate);
}
// margin is per contract
return new InitialMargin(marginReq.InitialOvernight * quantity * security.QuoteCurrency.ConversionRate);
}
private MarginRequirementsEntry GetCurrentMarginRequirements(Security security)
{
var lastData = security?.GetLastData();
if (lastData == null)
{
return null;
}
var marginRequirementsHistory = LoadMarginRequirementsHistory(security.Symbol);
var date = lastData.Time.Date;
while (_marginCurrentIndex + 1 < marginRequirementsHistory.Length &&
marginRequirementsHistory[_marginCurrentIndex + 1].Date <= date)
{
_marginCurrentIndex++;
}
return marginRequirementsHistory[_marginCurrentIndex];
}
/// <summary>
/// Gets the sorted list of historical margin changes produced by reading in the margin requirements
/// data found in /Data/symbol-margin/
/// </summary>
/// <returns>Sorted list of historical margin changes</returns>
private static MarginRequirementsEntry[] LoadMarginRequirementsHistory(Symbol symbol)
{
if (!_marginRequirementsCache.TryGetValue(symbol.ID.Symbol, out var marginRequirementsEntries))
{
lock (_locker)
{
if (!_marginRequirementsCache.TryGetValue(symbol.ID.Symbol, out marginRequirementsEntries))
{
Dictionary<string, MarginRequirementsEntry[]> marginRequirementsCache = new(_marginRequirementsCache)
{
[symbol.ID.Symbol] = marginRequirementsEntries = FromCsvFile(symbol)
};
// we change the reference so we can read without a lock
_marginRequirementsCache = marginRequirementsCache;
}
}
}
return marginRequirementsEntries;
}
/// <summary>
/// Reads margin requirements file and returns a sorted list of historical margin changes
/// </summary>
/// <param name="symbol">The symbol to fetch margin requirements for</param>
/// <returns>Sorted list of historical margin changes</returns>
private static MarginRequirementsEntry[] FromCsvFile(Symbol symbol)
{
var file = Path.Combine(Globals.DataFolder,
symbol.SecurityType.ToLower(),
symbol.ID.Market.ToLowerInvariant(),
"margins", symbol.ID.Symbol + ".csv");
if(_dataProvider == null)
{
ClearMarginCache();
_dataProvider = Composer.Instance.GetPart<IDataProvider>();
}
// skip the first header line, also skip #'s as these are comment lines
var marginRequirementsEntries = _dataProvider.ReadLines(file)
.Where(x => !x.StartsWith("#") && !string.IsNullOrWhiteSpace(x))
.Skip(1)
.Select(MarginRequirementsEntry.Create)
.OrderBy(x => x.Date)
.ToArray();
if (marginRequirementsEntries.Length == 0)
{
Log.Error($"FutureMarginModel.FromCsvFile(): Unable to locate future margin requirements file. Defaulting to zero margin for this symbol. File: {file}");
marginRequirementsEntries = new[] {
new MarginRequirementsEntry
{
Date = DateTime.MinValue
}
};
}
return marginRequirementsEntries;
}
/// <summary>
/// For live deployments we don't want to have stale margin requirements to we refresh them every day
/// </summary>
private static void ClearMarginCache()
{
Task.Delay(Time.OneDay).ContinueWith((_) =>
{
lock (_locker)
{
_marginRequirementsCache = new();
}
ClearMarginCache();
});
}
}
}
@@ -0,0 +1,103 @@
/*
* 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.Logging;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Settlement model which can handle daily profit and loss settlement
/// </summary>
public class FutureSettlementModel : ImmediateSettlementModel
{
private DateTime _lastSettlementDate;
private decimal _settledFutureQuantity;
private decimal _settlementPrice;
/// <summary>
/// Applies unsettledContractsTodaysProfit settlement rules
/// </summary>
/// <param name="applyFundsParameters">The funds application parameters</param>
public override void ApplyFunds(ApplyFundsSettlementModelParameters applyFundsParameters)
{
if(_settledFutureQuantity != 0)
{
var fill = applyFundsParameters.Fill;
var security = applyFundsParameters.Security;
var futureHolding = (FutureHolding)security.Holdings;
var absoluteQuantityClosed = Math.Min(fill.AbsoluteFillQuantity, security.Holdings.AbsoluteQuantity);
var absoluteQuantityClosedSettled = Math.Min(absoluteQuantityClosed, Math.Abs(_settledFutureQuantity));
var quantityClosedSettled = Math.Sign(-fill.FillQuantity) * absoluteQuantityClosedSettled;
// reduce our settled future quantity proportionally too
var factor = quantityClosedSettled / _settledFutureQuantity;
_settledFutureQuantity -= quantityClosedSettled;
// the passed in cash amount will hold the complete profit/loss of the trade, so we need to substract the settled profit we were given or taken from
var removedSettledProfit = factor * futureHolding.SettledProfit;
futureHolding.SettledProfit -= removedSettledProfit;
applyFundsParameters.CashAmount = new CashAmount(applyFundsParameters.CashAmount.Amount - removedSettledProfit, applyFundsParameters.CashAmount.Currency);
}
base.ApplyFunds(applyFundsParameters);
}
/// <summary>
/// Scan for pending settlements
/// </summary>
/// <param name="settlementParameters">The settlement parameters</param>
public override void Scan(ScanSettlementModelParameters settlementParameters)
{
var security = settlementParameters.Security;
// In the futures markets, losers pay winners every day. So once a day after the settlement time has passed we will update the cash book to reflect this
if (_lastSettlementDate.Date < security.LocalTime.Date)
{
if ((_lastSettlementDate != default) && security.Invested)
{
var futureHolding = (FutureHolding)security.Holdings;
var futureCache = (FutureCache)security.Cache;
_settlementPrice = futureCache.SettlementPrice;
_settledFutureQuantity = security.Holdings.Quantity;
// We settled the daily P&L, losers pay winners
var dailyProfitLoss = futureHolding.TotalCloseProfit(includeFees: false, exitPrice: _settlementPrice) - futureHolding.SettledProfit;
if (dailyProfitLoss != 0)
{
futureHolding.SettledProfit += dailyProfitLoss;
settlementParameters.Portfolio.CashBook[security.QuoteCurrency.Symbol].AddAmount(dailyProfitLoss);
Log.Trace($"FutureSettlementModel.Scan({security.Symbol}): {security.LocalTime} Daily P&L: {dailyProfitLoss} " +
$"Quantity: {_settledFutureQuantity} Settlement: {_settlementPrice} UnrealizedProfit: {futureHolding.UnrealizedProfit}");
}
}
_lastSettlementDate = security.LocalTime.Date;
}
}
/// <summary>
/// Set the current datetime in terms of the exchange's local time zone
/// </summary>
/// <param name="newLocalTime">Current local time</param>
public void SetLocalDateTimeFrontier(DateTime newLocalTime)
{
_lastSettlementDate = newLocalTime.Date;
}
}
}
+63
View File
@@ -0,0 +1,63 @@
/*
* 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 QuantConnect.Logging;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Static class contains common utility methods specific to symbols representing the future contracts
/// </summary>
public static class FutureSymbol
{
/// <summary>
/// Determine if a given Futures contract is a standard contract.
/// </summary>
/// <param name="symbol">Future symbol</param>
/// <returns>True if symbol expiration matches standard expiration</returns>
public static bool IsStandard(Symbol symbol)
{
var contractExpirationDate = symbol.ID.Date.Date;
try
{
// Use our FutureExpiryFunctions to determine standard contracts dates.
var expiryFunction = FuturesExpiryFunctions.FuturesExpiryFunction(symbol);
var contractMonth = FuturesExpiryUtilityFunctions.GetFutureContractMonth(symbol);
var standardExpirationDate = expiryFunction(contractMonth);
// Return true if the dates match
return contractExpirationDate == standardExpirationDate.Date;
}
catch
{
Log.Error($"FutureSymbol.IsStandard(): Could not find standard date for {symbol}, will be classified as standard");
return true;
}
}
/// <summary>
/// Returns true if the future contract is a weekly contract
/// </summary>
/// <param name="symbol">Future symbol</param>
/// <returns>True if symbol is non-standard contract</returns>
public static bool IsWeekly(Symbol symbol)
{
return !IsStandard(symbol);
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,422 @@
/*
* 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 static QuantConnect.StringExtensions;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Class to implement common functions used in FuturesExpiryFunctions
/// </summary>
public static class FuturesExpiryUtilityFunctions
{
private static readonly MarketHoursDatabase MarketHoursDatabase = MarketHoursDatabase.FromDataFolder();
/// <summary>
/// Get holiday list from the MHDB given the market and the symbol of the security
/// </summary>
/// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
/// <param name="symbol">The particular symbol being traded</param>s
internal static HashSet<DateTime> GetExpirationHolidays(string market, string symbol)
{
var exchangeHours = MarketHoursDatabase.FromDataFolder()
.GetEntry(market, symbol, SecurityType.Future)
.ExchangeHours;
return exchangeHours.Holidays.Concat(exchangeHours.BankHolidays).ToHashSet();
}
/// <summary>
/// Method to retrieve n^th succeeding/preceding business day for a given day
/// </summary>
/// <param name="time">The current Time</param>
/// <param name="n">Number of business days succeeding current time. Use negative value for preceding business days</param>
/// <param name="holidays">Set of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>The date-time after adding n business days</returns>
public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> holidays)
{
if (n < 0)
{
var businessDays = -n;
var totalDays = 1;
do
{
var previousDay = time.AddDays(-totalDays);
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
if (businessDays > 0) totalDays++;
} while (businessDays > 0);
return time.AddDays(-totalDays);
}
else
{
var businessDays = n;
var totalDays = 1;
do
{
var previousDay = time.AddDays(totalDays);
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
if (businessDays > 0) totalDays++;
} while (businessDays > 0);
return time.AddDays(totalDays);
}
}
/// <summary>
/// Method to retrieve n^th succeeding/preceding business day for a given day if there was a holiday on that day
/// </summary>
/// <param name="time">The current Time</param>
/// <param name="n">Number of business days succeeding current time. Use negative value for preceding business days</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>The date-time after adding n business days</returns>
public static DateTime AddBusinessDaysIfHoliday(DateTime time, int n, HashSet<DateTime> holidayList)
{
if (holidayList.Contains(time))
{
return AddBusinessDays(time, n, holidayList);
}
else
{
return time;
}
}
/// <summary>
/// Method to retrieve the n^th last business day of the delivery month.
/// </summary>
/// <param name="time">DateTime for delivery month</param>
/// <param name="n">Number of days</param>
/// <param name="holidayList">Holidays to use while calculating n^th business day. Useful for MHDB entries</param>
/// <returns>Nth Last Business day of the month</returns>
public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<DateTime> holidayList)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
var lastDayOfMonth = new DateTime(time.Year, time.Month, daysInMonth);
var holidays = holidayList.Select(x => x.Date);
if (n > daysInMonth)
{
throw new ArgumentOutOfRangeException(nameof(n), Invariant(
$"Number of days ({n}) is larger than the size of month({daysInMonth})"
));
}
// Count the number of days in the month after the third to last business day
var businessDays = n;
var totalDays = 0;
do
{
var previousDay = lastDayOfMonth.AddDays(-totalDays);
if (NotHoliday(previousDay, holidays) && !holidays.Contains(previousDay))
{
businessDays--;
}
if (businessDays > 0) totalDays++;
} while (businessDays > 0);
return lastDayOfMonth.AddDays(-totalDays);
}
/// <summary>
/// Calculates the n^th business day of the month (includes checking for holidays)
/// </summary>
/// <param name="time">Month to calculate business day for</param>
/// <param name="nthBusinessDay">n^th business day to get</param>
/// <param name="holidayList"> Holidays to not count as business days</param>
/// <returns>Nth business day of the month</returns>
public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable<DateTime> holidayList)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
var holidays = holidayList.Select(x => x.Date);
if (nthBusinessDay > daysInMonth)
{
throw new ArgumentOutOfRangeException(Invariant(
$"Argument nthBusinessDay (${nthBusinessDay}) is larger than the amount of days in the current month (${daysInMonth})"
));
}
if (nthBusinessDay < 1)
{
throw new ArgumentOutOfRangeException(Invariant(
$"Argument nthBusinessDay (${nthBusinessDay}) is less than one. Provide a number greater than one and less than the days in month"
));
}
var calculatedTime = new DateTime(time.Year, time.Month, 1);
var daysCounted = calculatedTime.IsCommonBusinessDay() ? 1 : 0;
var i = 0;
// Check for holiday up here in case we want the first business day and it is a holiday so that we don't skip over it.
// We also want to make sure that we don't stop on a weekend.
while (daysCounted < nthBusinessDay || holidays.Contains(calculatedTime) || !calculatedTime.IsCommonBusinessDay())
{
// The asset continues trading on days contained within `USHoliday.Dates`, but
// the last trade date is affected by those holidays. We check for
// both MHDB entries and holidays to get accurate business days
if (holidays.Contains(calculatedTime))
{
// Catches edge case where first day is on a friday
if (i == 0 && calculatedTime.DayOfWeek == DayOfWeek.Friday)
{
daysCounted = 0;
}
calculatedTime = calculatedTime.AddDays(1);
if (i != 0 && calculatedTime.IsCommonBusinessDay())
{
daysCounted++;
}
i++;
continue;
}
calculatedTime = calculatedTime.AddDays(1);
if (!holidays.Contains(calculatedTime) && NotHoliday(calculatedTime, holidays))
{
daysCounted++;
}
i++;
}
return calculatedTime;
}
/// <summary>
/// Method to retrieve the 2nd Friday of the given month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>2nd Friday of given month</returns>
public static DateTime SecondFriday(DateTime time) => NthFriday(time, 2);
/// <summary>
/// Method to retrieve the 3rd Friday of the given month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>3rd Friday of given month</returns>
public static DateTime ThirdFriday(DateTime time) => NthFriday(time, 3);
/// <summary>
/// Method to retrieve the Nth Friday of the given month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="n">The order of the Friday in the period</param>
/// <returns>Nth Friday of given month</returns>
public static DateTime NthFriday(DateTime time, int n) => NthWeekday(time, n, DayOfWeek.Friday);
/// <summary>
/// Method to retrieve third Wednesday of the given month (usually Monday).
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>Third Wednesday of the given month</returns>
public static DateTime ThirdWednesday(DateTime time) => NthWeekday(time, 3, DayOfWeek.Wednesday);
/// <summary>
/// Method to retrieve the Nth Weekday of the given month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="n">The order of the Weekday in the period</param>
/// <param name="dayOfWeek">The day of the week</param>
/// <returns>Nth Weekday of given month</returns>
public static DateTime NthWeekday(DateTime time, int n, DayOfWeek dayOfWeek)
{
if (n < 1 || n > 5)
{
throw new ArgumentOutOfRangeException(nameof(n), "'n' lower than 1 or greater than 5");
}
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
return (from day in Enumerable.Range(1, daysInMonth)
where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek
select new DateTime(time.Year, time.Month, day)).ElementAt(n - 1);
}
/// <summary>
/// Method to retrieve the last weekday of any month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <param name="dayOfWeek">the last weekday to be found</param>
/// <returns>Last day of the we</returns>
public static DateTime LastWeekday(DateTime time, DayOfWeek dayOfWeek)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
return (from day in Enumerable.Range(1, daysInMonth).Reverse()
where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek
select new DateTime(time.Year, time.Month, day)).First();
}
/// <summary>
/// Method to retrieve the last Thursday of any month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>Last Thursday of the given month</returns>
public static DateTime LastThursday(DateTime time) => LastWeekday(time, DayOfWeek.Thursday);
/// <summary>
/// Method to retrieve the last Friday of any month
/// </summary>
/// <param name="time">Date from the given month</param>
/// <returns>Last Friday of the given month</returns>
public static DateTime LastFriday(DateTime time) => LastWeekday(time, DayOfWeek.Friday);
/// <summary>
/// Method to check whether a given time is holiday or not
/// </summary>
/// <param name="time">The DateTime for consideration</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>True if the time is not a holidays, otherwise returns false</returns>
public static bool NotHoliday(DateTime time, IEnumerable<DateTime> holidayList)
{
return time.IsCommonBusinessDay() && !holidayList.Contains(time.Date);
}
/// <summary>
/// This function takes Thursday as input and returns true if four weekdays preceding it are not Holidays
/// </summary>
/// <param name="thursday">DateTime of a given Thursday</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>False if DayOfWeek is not Thursday or is not preceded by four weekdays,Otherwise returns True</returns>
public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable<DateTime> holidayList)
{
if (thursday.DayOfWeek != DayOfWeek.Thursday)
{
throw new ArgumentException("Input to NotPrecededByHolidays must be a Thursday");
}
var result = true;
// for Monday, Tuesday and Wednesday
for (var i = 1; i <= 3; i++)
{
if (!NotHoliday(thursday.AddDays(-i), holidayList))
{
result = false;
}
}
// for Friday
if (!NotHoliday(thursday.AddDays(-6), holidayList))
{
result = false;
}
return result;
}
/// <summary>
/// Gets the number of months between the contract month and the expiry date.
/// </summary>
/// <param name="underlying">The future symbol ticker</param>
/// <param name="futureExpiryDate">Expiry date to use to look up contract month delta. Only used for dairy, since we need to lookup its contract month in a pre-defined table.</param>
/// <returns>The number of months between the contract month and the contract expiry</returns>
public static int GetDeltaBetweenContractMonthAndContractExpiry(string underlying, DateTime? futureExpiryDate = null)
{
return ExpiriesPriorMonth.TryGetValue(underlying, out int value) ? value : 0;
}
/// <summary>
/// Helper method to retrieve the futures contract month
/// </summary>
public static DateTime GetFutureContractMonth(Symbol symbol)
{
if (symbol.SecurityType == SecurityType.FutureOption)
{
symbol = symbol.Underlying;
}
var contractExpirationDate = symbol.ID.Date.Date;
var monthsToAdd = GetDeltaBetweenContractMonthAndContractExpiry(symbol.ID.Symbol, contractExpirationDate);
var contractMonth = contractExpirationDate.AddDays(-(contractExpirationDate.Day - 1))
.AddMonths(monthsToAdd);
return contractMonth;
}
/// <summary>
/// Helper method to resolve a future expiration from it's contract month
/// </summary>
public static DateTime GetFutureExpirationFromContractMonth(string symbol, string market, DateTime contractMonth)
{
return GetFutureExpirationFromContractMonth(Symbol.CreateFuture(symbol, market, SecurityIdentifier.DefaultDate), contractMonth);
}
/// <summary>
/// Helper method to resolve a future expiration from it's contract month
/// </summary>
public static DateTime GetFutureExpirationFromContractMonth(Symbol future, DateTime contractMonth)
{
var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(future.Canonical);
var futureExpiry = futureExpiryFunc(contractMonth);
return futureExpiry;
}
/// <summary>
/// This function returns the third Friday of the month, adjusted for holidays and weekends.
/// </summary>
public static DateTime ThirdFriday(DateTime time, Symbol contract)
{
if (contract.ID.SecurityType.IsOption())
{
return ThirdFriday(time, contract.Underlying);
}
var thirdFriday = ThirdFriday(time);
var holidays = GetExpirationHolidays(contract.ID.Market, contract.ID.Symbol);
return AddBusinessDaysIfHoliday(thirdFriday, -1, holidays);
}
/// <summary>
/// Checks if the future contract is expired.
/// </summary>
public static bool IsFutureContractExpired(Symbol symbol, DateTime currentUtcTime, MarketHoursDatabase marketHoursDatabase = null)
{
var exchangeHours = (marketHoursDatabase ?? MarketHoursDatabase).GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
var currentTimeInExchangeTz = currentUtcTime.ConvertFromUtc(exchangeHours.TimeZone);
if (currentTimeInExchangeTz >= symbol.ID.Date)
{
return true;
}
return false;
}
private static readonly Dictionary<string, int> ExpiriesPriorMonth = new Dictionary<string, int>
{
{ Futures.Energy.ArgusLLSvsWTIArgusTradeMonth, 1 },
{ Futures.Energy.ArgusPropaneSaudiAramco, 1 },
{ Futures.Energy.BrentCrude, 2 },
{ Futures.Energy.BrentLastDayFinancial, 2 },
{ Futures.Energy.CrudeOilWTI, 1 },
{ Futures.Energy.MicroCrudeOilWTI, 1 },
{ Futures.Energy.Gasoline, 1 },
{ Futures.Energy.HeatingOil, 1 },
{ Futures.Energy.MarsArgusVsWTITradeMonth, 1 },
{ Futures.Energy.NaturalGas, 1 },
{ Futures.Energy.NaturalGasHenryHubLastDayFinancial, 1 },
{ Futures.Energy.NaturalGasHenryHubPenultimateFinancial, 1 },
{ Futures.Energy.WTIHoustonArgusVsWTITradeMonth, 1 },
{ Futures.Energy.WTIHoustonCrudeOil, 1 },
{ Futures.Softs.Sugar11, 1 },
{ Futures.Softs.Sugar11CME, 1 }
};
}
}
+257
View File
@@ -0,0 +1,257 @@
/*
* 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;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Helpers for getting the futures contracts that are trading on a given date.
/// This is a substitute for the BacktestingFutureChainProvider, but
/// does not outright replace it because of missing entries. This will resolve
/// the listed contracts without having any data in place. We follow the listing rules
/// set forth by the exchange to get the <see cref="Symbol"/>s that are listed at a given date.
/// </summary>
public static class FuturesListings
{
private static readonly Symbol _zb = Symbol.Create("ZB", SecurityType.Future, Market.CBOT);
private static readonly Symbol _zc = Symbol.Create("ZC", SecurityType.Future, Market.CBOT);
private static readonly Symbol _zs = Symbol.Create("ZS", SecurityType.Future, Market.CBOT);
private static readonly Symbol _zm = Symbol.Create("ZM", SecurityType.Future, Market.CBOT);
private static readonly Symbol _zt = Symbol.Create("ZT", SecurityType.Future, Market.CBOT);
private static readonly Symbol _zl = Symbol.Create("ZL", SecurityType.Future, Market.CBOT);
private static readonly Symbol _zw = Symbol.Create("ZW", SecurityType.Future, Market.CBOT);
private static readonly Symbol _tn = Symbol.Create("TN", SecurityType.Future, Market.CBOT);
private static readonly Symbol _aud = Symbol.Create("6A", SecurityType.Future, Market.CME);
private static readonly Symbol _gbp = Symbol.Create("6B", SecurityType.Future, Market.CME);
private static readonly Symbol _mxn = Symbol.Create("6M", SecurityType.Future, Market.CME);
private static readonly Symbol _jpy = Symbol.Create("6J", SecurityType.Future, Market.CME);
private static readonly Symbol _eur = Symbol.Create("6E", SecurityType.Future, Market.CME);
private static readonly Symbol _cad = Symbol.Create("6C", SecurityType.Future, Market.CME);
private static Dictionary<string, Func<DateTime, List<Symbol>>> _futuresListingRules = new Dictionary<string, Func<DateTime, List<Symbol>>>
{
{ "ZB", t => QuarterlyContracts(_zb, t, 3) },
{ "ZC", t => MonthlyContractListings(
_zc,
t,
12,
new FuturesListingCycles(new[] { 3, 5, 9 }, 9),
new FuturesListingCycles(new[] { 7, 12 }, 8)) },
{ "ZN", t => QuarterlyContracts(_zt, t, 3) },
{ "TN", t => QuarterlyContracts(_tn, t, 3) },
{ "ZS", t => MonthlyContractListings(
_zs,
t,
11,
new FuturesListingCycles(new[] { 1, 3, 5, 8, 9 }, 15),
new FuturesListingCycles(new[] { 7, 11 }, 8)) },
{ "ZM", t => MonthlyContractListings(
_zm,
t,
12,
new FuturesListingCycles(new[] { 1, 3, 5, 8, 9 }, 15),
new FuturesListingCycles(new[] { 7, 10, 12 }, 12)) },
{ "ZL", t => MonthlyContractListings(
_zl,
t,
12,
new FuturesListingCycles(new[] { 1, 3, 5, 8, 9 }, 15),
new FuturesListingCycles(new[] { 7, 10, 12 }, 12)) },
{ "ZT", t => QuarterlyContracts(_zt, t, 3) },
{ "ZW", t => MonthlyContractListings(
_zw,
t,
7,
new FuturesListingCycles(new[] { 3, 5, 7, 9, 12 }, 15)) },
{ "6A", t => QuarterlyContracts(_aud, t, 8) },
{ "6B", t => QuarterlyContracts(_gbp, t, 8) },
{ "6M", t => QuarterlyContracts(_mxn, t, 8) },
{ "6J", t => QuarterlyContracts(_jpy, t, 8) },
{ "6E", t => QuarterlyContracts(_eur, t, 8) },
{ "6C", t => QuarterlyContracts(_cad, t, 8) },
};
/// <summary>
/// Gets the listed futures contracts on a given date
/// </summary>
/// <param name="futureTicker">Ticker of the future contract</param>
/// <param name="time">Contracts to look up that are listed at that time</param>
/// <returns>The currently trading contracts on the exchange</returns>
public static List<Symbol> ListedContracts(string futureTicker, DateTime time)
{
if (!_futuresListingRules.ContainsKey(futureTicker))
{
// No entries found. This differs from entries being returned as an empty array, where
// that would mean that no listings were found.
return null;
}
return _futuresListingRules[futureTicker](time);
}
/// <summary>
/// Gets contracts following a quarterly listing procedure, with a limit of
/// how many contracts are listed at once.
/// </summary>
/// <param name="canonicalFuture">Canonical Futures Symbol</param>
/// <param name="time">Contracts to look up that are listed at that time</param>
/// <param name="limit">Number of Symbols we get back/are listed at a given time</param>
/// <returns>Symbols that are listed at the given time</returns>
private static List<Symbol> QuarterlyContracts(Symbol canonicalFuture, DateTime time, int limit)
{
var contractMonth = new DateTime(time.Year, time.Month, 1);
var futureExpiry = DateTime.MinValue;
var expiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture);
// Skip any contracts that have already expired.
while (futureExpiry < time)
{
futureExpiry = expiryFunc(contractMonth);
contractMonth = contractMonth.AddMonths(1);
}
// Negate the last incrementation from the while loop to get the actual contract month of the future.
var firstFutureContractMonth = contractMonth.AddMonths(-1);
var quarterlyContracts = new List<Symbol>();
// Gets the next closest month from the current month in multiples of 3
var quarterlyContractMonth = (int)Math.Ceiling((double)firstFutureContractMonth.Month / 3) * 3;
for (var i = 0; i < limit; i++)
{
// We're past the expiration frontier due to the while loop above, which means
// that any contracts from here on out will be greater than the current time.
var currentContractMonth = firstFutureContractMonth.AddMonths(-firstFutureContractMonth.Month + quarterlyContractMonth);
var currentFutureExpiry = expiryFunc(currentContractMonth);
quarterlyContracts.Add(Symbol.CreateFuture(canonicalFuture.ID.Symbol, canonicalFuture.ID.Market, currentFutureExpiry));
quarterlyContractMonth += 3;
}
return quarterlyContracts;
}
/// <summary>
/// Gets Futures contracts that follow a limited cyclical pattern
/// </summary>
/// <param name="canonicalFuture">Canonical Futures Symbol</param>
/// <param name="time">Contracts to look up that are listed at that time</param>
/// <param name="contractMonthForNewListings">Contract month that results in new listings after this contract's expiry</param>
/// <param name="futureListingCycles">
/// Cycles that define the number of contracts and the months the contracts are listed on, including
/// the limit of how many contracts will be listed.
/// </param>
/// <returns>Symbols that are listed at the given time</returns>
private static List<Symbol> MonthlyContractListings(
Symbol canonicalFuture,
DateTime time,
int contractMonthForNewListings,
params FuturesListingCycles[] futureListingCycles)
{
var listings = new List<Symbol>();
var expiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture);
var yearDelta = 0;
var contractMonthForNewListingCycle = new DateTime(time.Year, contractMonthForNewListings, 1);
var contractMonthForNewListingCycleExpiry = expiryFunc(contractMonthForNewListingCycle);
if (time <= contractMonthForNewListingCycleExpiry)
{
// Go back a year if we haven't yet crossed this year's contract renewal expiration date.
contractMonthForNewListingCycleExpiry = expiryFunc(contractMonthForNewListingCycle.AddYears(-1));
yearDelta = -1;
}
foreach (var listingCycle in futureListingCycles)
{
var year = yearDelta;
var count = 0;
var initialListings = true;
while (count != listingCycle.Limit)
{
var monthStartIndex = 0;
if (initialListings)
{
// For the initial listing, we want to start counting at some month that might not be the first
// index of the collection. The index is discovered here and used as the starting point for listed contracts.
monthStartIndex = listingCycle.Cycle.Length - listingCycle.Cycle.Count(c => c > contractMonthForNewListingCycleExpiry.Month);
initialListings = false;
}
for (var m = monthStartIndex; m < listingCycle.Cycle.Length; m++)
{
// Add the future's expiration to the listings
var currentContractMonth = new DateTime(time.Year + year, listingCycle.Cycle[m], 1);
var currentFutureExpiry = expiryFunc(currentContractMonth);
if (currentFutureExpiry >= time)
{
listings.Add(Symbol.CreateFuture(canonicalFuture.ID.Symbol, canonicalFuture.ID.Market, currentFutureExpiry));
}
if (++count == listingCycle.Limit)
{
break;
}
}
year++;
}
}
return listings;
}
/// <summary>
/// Listing Cycles, i.e. the months and number of contracts that are renewed whenever
/// the specified renewal expiration contract expires.
/// </summary>
/// <remarks>
/// Example:
///
/// (from: https://www.cmegroup.com/trading/agricultural/grain-and-oilseed/wheat_contract_specifications.html)
/// "15 monthly contracts of Mar, May, Jul, Sep, Dec listed annually following the termination of trading in the July contract of the current year."
///
/// This would equate to a cycle of [3, 5, 7, 9, 12], a limit of 15, and the contract month == 7.
/// </remarks>
private class FuturesListingCycles
{
/// <summary>
/// Monthly cycles that the futures listings rule follows
/// </summary>
public int[] Cycle { get; }
/// <summary>
/// Max number of contracts returned by this rule
/// </summary>
public int Limit { get; }
/// <summary>
/// Creates a listing cycle rule
/// </summary>
/// <param name="cycle">New contract listing cycles</param>
/// <param name="limit">Max number of contracts to return in this rule</param>
public FuturesListingCycles(int[] cycle, int limit)
{
Cycle = cycle;
Limit = limit;
}
}
}
}
@@ -0,0 +1,105 @@
/*
* 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.Globalization;
using QuantConnect.Logging;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// POCO class for modeling margin requirements at given date
/// </summary>
public class MarginRequirementsEntry
{
/// <summary>
/// Date of margin requirements change
/// </summary>
public DateTime Date { get; init; }
/// <summary>
/// Initial overnight margin for the contract effective from the date of change
/// </summary>
public decimal InitialOvernight { get; init; }
/// <summary>
/// Maintenance overnight margin for the contract effective from the date of change
/// </summary>
public decimal MaintenanceOvernight { get; init; }
/// <summary>
/// Initial intraday margin for the contract effective from the date of change
/// </summary>
public decimal InitialIntraday { get; init; }
/// <summary>
/// Maintenance intraday margin for the contract effective from the date of change
/// </summary>
public decimal MaintenanceIntraday { get; init; }
/// <summary>
/// Creates a new instance of <see cref="MarginRequirementsEntry"/> from the specified csv line
/// </summary>
/// <param name="csvLine">The csv line to be parsed</param>
/// <returns>A new <see cref="MarginRequirementsEntry"/> for the specified csv line</returns>
public static MarginRequirementsEntry Create(string csvLine)
{
var line = csvLine.Split(',');
DateTime date;
if (!DateTime.TryParseExact(line[0], DateFormat.EightCharacter, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
{
Log.Trace($"Couldn't parse date/time while reading future margin requirement file. Line: {csvLine}");
}
decimal initialOvernight;
if (!decimal.TryParse(line[1], out initialOvernight))
{
Log.Trace($"Couldn't parse Initial Overnight margin requirements while reading future margin requirement file. Line: {csvLine}");
}
decimal maintenanceOvernight;
if (!decimal.TryParse(line[2], out maintenanceOvernight))
{
Log.Trace($"Couldn't parse Maintenance Overnight margin requirements while reading future margin requirement file. Line: {csvLine}");
}
// default value, if present in file we try to parse
decimal initialIntraday = initialOvernight * 0.4m;
if (line.Length >= 4
&& !decimal.TryParse(line[3], out initialIntraday))
{
Log.Trace($"Couldn't parse Initial Intraday margin requirements while reading future margin requirement file. Line: {csvLine}");
}
// default value, if present in file we try to parse
decimal maintenanceIntraday = maintenanceOvernight * 0.4m;
if (line.Length >= 5
&& !decimal.TryParse(line[4], out maintenanceIntraday))
{
Log.Trace($"Couldn't parse Maintenance Intraday margin requirements while reading future margin requirement file. Line: {csvLine}");
}
return new MarginRequirementsEntry
{
Date = date,
InitialOvernight = initialOvernight,
MaintenanceOvernight = maintenanceOvernight,
InitialIntraday = initialIntraday,
MaintenanceIntraday = maintenanceIntraday
};
}
}
}
@@ -0,0 +1,45 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Util;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// CME Option Chain Quotes API call root response
/// </summary>
public class CMEOptionChainQuotes
{
/// <summary>
/// The future options contracts with/without settlements
/// </summary>
[JsonProperty("optionContractQuotes")]
public List<CMEOptionChainQuoteEntry> Quotes { get; private set; }
}
/// <summary>
/// Option chain entry quotes, containing strike price
/// </summary>
public class CMEOptionChainQuoteEntry
{
/// <summary>
/// Strike price of the future option quote entry
/// </summary>
[JsonProperty("strikePrice"), JsonConverter(typeof(StringDecimalJsonConverter), true)]
public decimal StrikePrice { get; private set; }
}
}
@@ -0,0 +1,141 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Util;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// CME options trades, dates, and expiration list API call root response
/// </summary>
/// <remarks>Returned as a List of this class</remarks>
public class CMEOptionsTradeDatesAndExpiration
{
/// <summary>
/// Describes the type of future option this entry is
/// </summary>
[JsonProperty("label")]
public string Label { get; private set; }
/// <summary>
/// Name of the product
/// </summary>
[JsonProperty("name")]
public string Name { get; private set; }
/// <summary>
/// Option type. "AME" for American, "EUR" for European.
/// Note that there are other types such as weekly, but we
/// only support American options for now.
/// </summary>
[JsonProperty("optionType")]
public string OptionType { get; private set; }
/// <summary>
/// Product ID of the option
/// </summary>
[JsonProperty("productId")]
public int ProductId { get; private set; }
/// <summary>
/// Is Daily option
/// </summary>
[JsonProperty("daily")]
public bool Daily { get; private set; }
/// <summary>
/// ???
/// </summary>
[JsonProperty("sto")]
public bool Sto { get; private set; }
/// <summary>
/// Is weekly option
/// </summary>
[JsonProperty("weekly")]
public bool Weekly { get; private set; }
/// <summary>
/// Expirations of the future option
/// </summary>
[JsonProperty("expirations")]
public List<CMEOptionsExpiration> Expirations { get; private set; }
}
/// <summary>
/// Future options Expiration entries. These are useful because we can derive the
/// future chain from this data, since FOP and FUT share a 1-1 expiry code.
/// </summary>
public class CMEOptionsExpiration
{
/// <summary>
/// Date of expiry
/// </summary>
[JsonProperty("label")]
public string Label { get; private set; }
/// <summary>
/// Product ID of the expiring asset (usually future option)
/// </summary>
[JsonProperty("productId")]
public int ProductId { get; private set; }
/// <summary>
/// Contract ID of the asset
/// </summary>
/// <remarks>Used to search settlements for the option chain</remarks>
[JsonProperty("contractId")]
public string ContractId { get; private set; }
/// <summary>
/// Contract month code formatted as [FUTURE_MONTH_LETTER(1)][YEAR(1)]
/// </summary>
[JsonProperty("expiration")]
public CMEOptionExpirationEntry Expiration { get; private set; }
}
/// <summary>
/// Chicago Mercantile Exchange Option Expiration Entry
/// </summary>
public class CMEOptionExpirationEntry
{
/// <summary>
/// Month of expiry
/// </summary>
[JsonProperty("month")]
public int Month { get; private set; }
/// <summary>
/// Year of expiry
/// </summary>
[JsonProperty("year")]
public int Year { get; private set; }
/// <summary>
/// Expiration code (two letter)
/// </summary>
[JsonProperty("code")]
public string Code { get; private set; }
/// <summary>
/// Expiration code (three letter)
/// </summary>
[JsonProperty("twoDigitsCode")]
public string TwoDigitsCode { get; private set; }
}
}
@@ -0,0 +1,98 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// Product slate API call root response
/// </summary>
public class CMEProductSlateV2ListResponse
{
/// <summary>
/// Products matching the search criteria
/// </summary>
[JsonProperty("products")]
public List<CMEProductSlateV2ListEntry> Products { get; private set; }
}
/// <summary>
/// Product entry describing the asset matching the search criteria
/// </summary>
public class CMEProductSlateV2ListEntry
{
/// <summary>
/// CME ID for the asset
/// </summary>
[JsonProperty("id")]
public int Id { get; private set; }
/// <summary>
/// Name of the product (e.g. E-mini NASDAQ futures)
/// </summary>
[JsonProperty("name")]
public string Name { get; private set; }
/// <summary>
/// Clearing code
/// </summary>
[JsonProperty("clearing")]
public string Clearing { get; private set; }
/// <summary>
/// GLOBEX ticker
/// </summary>
[JsonProperty("globex")]
public string Globex { get; private set; }
/// <summary>
/// Is traded in the GLOBEX venue
/// </summary>
[JsonProperty("globexTraded")]
public bool GlobexTraded { get; private set; }
/// <summary>
/// Venues this asset trades on
/// </summary>
[JsonProperty("venues")]
public string Venues { get; private set; }
/// <summary>
/// Asset type this product is cleared as (i.e. "Futures", "Options")
/// </summary>
[JsonProperty("cleared")]
public string Cleared { get; private set; }
/// <summary>
/// Exchange the asset trades on (i.e. CME, NYMEX, COMEX, CBOT)
/// </summary>
[JsonProperty("exch")]
public string Exchange { get; private set; }
/// <summary>
/// Asset class group ID - describes group of asset class (e.g. equities, agriculture, etc.)
/// </summary>
[JsonProperty("groupId")]
public int GroupId { get; private set; }
/// <summary>
/// More specific ID describing product
/// </summary>
[JsonProperty("subGroupId")]
public int subGroupId { get; private set; }
}
}
@@ -0,0 +1,46 @@
/*
* 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;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Provides a means to get the scaling factor for CME's quotes API
/// </summary>
public class CMEStrikePriceScalingFactors
{
/// <summary>
/// CME's option chain quotes strike price scaling factor
/// </summary>
private static readonly IReadOnlyDictionary<string, decimal> _scalingFactors = new Dictionary<string, decimal>
{
{ "SI", 0.1m },
{ "NG", 5m }
};
/// <summary>
/// Gets the option chain strike price scaling factor for the quote response from CME
/// </summary>
/// <param name="underlyingFuture">Underlying future Symbol to normalize</param>
/// <returns>Scaling factor for the strike price</returns>
public static decimal GetScaleFactor(Symbol underlyingFuture)
{
return _scalingFactors.ContainsKey(underlyingFuture.ID.Symbol)
? _scalingFactors[underlyingFuture.ID.Symbol]
: 1m;
}
}
}
@@ -0,0 +1,75 @@
/*
* 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 QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities.Option;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Futures Options security
/// </summary>
public class FutureOption : Option.Option
{
/// <summary>
/// Constructor for the future option security
/// </summary>
/// <param name="symbol">Symbol of the future option</param>
/// <param name="exchangeHours">Exchange hours of the future option</param>
/// <param name="quoteCurrency">Quoted currency of the future option</param>
/// <param name="symbolProperties">Symbol properties of the future option</param>
/// <param name="currencyConverter">Currency converter</param>
/// <param name="registeredTypes">Provides all data types registered to the algorithm</param>
/// <param name="securityCache">Cache of security objects</param>
/// <param name="underlying">Future underlying security</param>
public FutureOption(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
OptionSymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache,
Security underlying)
: base(symbol,
quoteCurrency,
symbolProperties,
new OptionExchange(exchangeHours),
securityCache,
new OptionPortfolioModel(),
new FutureOptionFillModel(),
new InteractiveBrokersFeeModel(),
NullSlippageModel.Instance,
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
null,
new OptionDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes,
underlying,
null
)
{
BuyingPowerModel = new FuturesOptionsMarginModel(0, this);
}
/// <summary>
/// Returns the securities symbol
/// </summary>
public static implicit operator Symbol(FutureOption security) => security.Symbol;
}
}
@@ -0,0 +1,25 @@
/*
* 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.
*/
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Future option specific caching support
/// </summary>
/// <seealso cref="SecurityCache"/>
public class FutureOptionCache : Option.OptionCache
{
}
}
@@ -0,0 +1,42 @@
/*
* 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;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Static helper methods to resolve Futures Options Symbol-related tasks.
/// </summary>
public static class FutureOptionSymbol
{
/// <summary>
/// Detects if the future option contract is standard, i.e. not weekly, not short-term, not mid-sized, etc.
/// </summary>
/// <param name="_">Symbol</param>
/// <returns>true</returns>
/// <remarks>
/// We have no way of identifying the type of FOP contract based on the properties contained within the Symbol.
/// </remarks>
public static bool IsStandard(Symbol _) => true;
/// <summary>
/// Gets the last day of trading, aliased to be the Futures options' expiry
/// </summary>
/// <param name="symbol">Futures Options Symbol</param>
/// <returns>Last day of trading date</returns>
public static DateTime GetLastDayOfTrading(Symbol symbol) => symbol.ID.Date.Date;
}
}
@@ -0,0 +1,272 @@
/*
* 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 QuantConnect.Securities.Future;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Futures options expiry lookup utility class
/// </summary>
public static class FuturesOptionsExpiryFunctions
{
private static readonly Symbol _lo = Symbol.CreateCanonicalOption(Symbol.Create("CL", SecurityType.Future, Market.NYMEX));
private static readonly Symbol _on = Symbol.CreateCanonicalOption(Symbol.Create("NG", SecurityType.Future, Market.NYMEX));
private static readonly Symbol _ozm = Symbol.CreateCanonicalOption(Symbol.Create("ZM", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozb = Symbol.CreateCanonicalOption(Symbol.Create("ZB", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozc = Symbol.CreateCanonicalOption(Symbol.Create("ZC", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozn = Symbol.CreateCanonicalOption(Symbol.Create("ZN", SecurityType.Future, Market.CBOT));
private static readonly Symbol _otn = Symbol.CreateCanonicalOption(Symbol.Create("TN", SecurityType.Future, Market.CBOT));
private static readonly Symbol _oub = Symbol.CreateCanonicalOption(Symbol.Create("UB", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozo = Symbol.CreateCanonicalOption(Symbol.Create("ZO", SecurityType.Future, Market.CBOT));
private static readonly Symbol _oke = Symbol.CreateCanonicalOption(Symbol.Create("KE", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozf = Symbol.CreateCanonicalOption(Symbol.Create("ZF", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozs = Symbol.CreateCanonicalOption(Symbol.Create("ZS", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozt = Symbol.CreateCanonicalOption(Symbol.Create("ZT", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozl = Symbol.CreateCanonicalOption(Symbol.Create("ZL", SecurityType.Future, Market.CBOT));
private static readonly Symbol _ozw = Symbol.CreateCanonicalOption(Symbol.Create("ZW", SecurityType.Future, Market.CBOT));
private static readonly Symbol _oym = Symbol.CreateCanonicalOption(Symbol.Create("YM", SecurityType.Future, Market.CBOT));
private static readonly Symbol _hxe = Symbol.CreateCanonicalOption(Symbol.Create("HG", SecurityType.Future, Market.COMEX));
private static readonly Symbol _og = Symbol.CreateCanonicalOption(Symbol.Create("GC", SecurityType.Future, Market.COMEX));
private static readonly Symbol _so = Symbol.CreateCanonicalOption(Symbol.Create("SI", SecurityType.Future, Market.COMEX));
private static readonly Symbol _aud = Symbol.CreateCanonicalOption(Symbol.Create("6A", SecurityType.Future, Market.CME));
private static readonly Symbol _gbu = Symbol.CreateCanonicalOption(Symbol.Create("6B", SecurityType.Future, Market.CME));
private static readonly Symbol _cau = Symbol.CreateCanonicalOption(Symbol.Create("6C", SecurityType.Future, Market.CME));
private static readonly Symbol _euu = Symbol.CreateCanonicalOption(Symbol.Create("6E", SecurityType.Future, Market.CME));
private static readonly Symbol _jpu = Symbol.CreateCanonicalOption(Symbol.Create("6J", SecurityType.Future, Market.CME));
private static readonly Symbol _chu = Symbol.CreateCanonicalOption(Symbol.Create("6S", SecurityType.Future, Market.CME));
private static readonly Symbol _nzd = Symbol.CreateCanonicalOption(Symbol.Create("6N", SecurityType.Future, Market.CME));
private static readonly Symbol _mxn = Symbol.CreateCanonicalOption(Symbol.Create("6M", SecurityType.Future, Market.CME));
private static readonly Symbol _ead = Symbol.CreateCanonicalOption(Symbol.Create("EAD", SecurityType.Future, Market.CME));
private static readonly Symbol _ajy = Symbol.CreateCanonicalOption(Symbol.Create("AJY", SecurityType.Future, Market.CME));
private static readonly Symbol _ane = Symbol.CreateCanonicalOption(Symbol.Create("ANE", SecurityType.Future, Market.CME));
private static readonly Symbol _ecd = Symbol.CreateCanonicalOption(Symbol.Create("ECD", SecurityType.Future, Market.CME));
private static readonly Symbol _le = Symbol.CreateCanonicalOption(Symbol.Create("LE", SecurityType.Future, Market.CME));
private static readonly Symbol _he = Symbol.CreateCanonicalOption(Symbol.Create("HE", SecurityType.Future, Market.CME));
private static readonly Symbol _lbr = Symbol.CreateCanonicalOption(Symbol.Create("LBR", SecurityType.Future, Market.CME));
private static readonly Symbol _lbs = Symbol.CreateCanonicalOption(Symbol.Create("LBS", SecurityType.Future, Market.CME));
private static readonly Symbol _es = Symbol.CreateCanonicalOption(Symbol.Create("ES", SecurityType.Future, Market.CME));
private static readonly Symbol _emd = Symbol.CreateCanonicalOption(Symbol.Create("EMD", SecurityType.Future, Market.CME));
private static readonly Symbol _nq = Symbol.CreateCanonicalOption(Symbol.Create("NQ", SecurityType.Future, Market.CME));
/// <summary>
/// Futures options expiry functions lookup table, keyed by canonical future option Symbol
/// </summary>
private static readonly IReadOnlyDictionary<Symbol, Func<DateTime, DateTime>> _futuresOptionExpiryFunctions = new Dictionary<Symbol, Func<DateTime,DateTime>>
{
// Trading terminates 7 business days before the 26th calendar of the month prior to the contract month. https://www.cmegroup.com/trading/energy/crude-oil/light-sweet-crude_contractSpecs_options.html#optionProductId=190
{_lo, expiryMonth => {
var twentySixthDayOfPreviousMonthFromContractMonth = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1)).AddDays(25);
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(_lo.ID.Market, _lo.Underlying.ID.Symbol);
return FuturesExpiryUtilityFunctions.AddBusinessDays(twentySixthDayOfPreviousMonthFromContractMonth, -7, holidays);
}},
// Trading terminates on the 4th last business day of the month prior to the contract month (1 business day prior to the expiration of the underlying futures corresponding contract month).
// https://www.cmegroup.com/trading/energy/natural-gas/natural-gas_contractSpecs_options.html
// Although not stated, this follows the same rules as seen in the COMEX markets, but without Fridays. Case: Dec 2020 expiry, Last Trade Date: 24 Nov 2020
{ _on, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_on.Underlying, expiryMonth, 0, 0, noFridays: false) },
{ _ozb, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozb.Underlying, expiryMonth) },
{ _ozc, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozc.Underlying, expiryMonth) },
{ _ozn, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozn.Underlying, expiryMonth) },
{ _otn, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_otn.Underlying, expiryMonth) },
{ _oub, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_oub.Underlying, expiryMonth) },
{ _ozo, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozo.Underlying, expiryMonth) },
{ _oke, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_oke.Underlying, expiryMonth) },
{ _ozf, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozf.Underlying, expiryMonth) },
{ _ozs, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozs.Underlying, expiryMonth) },
{ _ozt, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozt.Underlying, expiryMonth) },
{ _ozw, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozw.Underlying, expiryMonth) },
{ _ozl, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozl.Underlying, expiryMonth) },
{ _ozm, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozm.Underlying, expiryMonth) },
{ _hxe, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_hxe.Underlying, expiryMonth, 12, 0) },
{ _og, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_og.Underlying, expiryMonth, 12, 30) },
{ _so, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_so.Underlying, expiryMonth, 12, 25) },
{ _aud, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_aud.Underlying, expiryMonth) },
{ _gbu, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_gbu.Underlying, expiryMonth) },
{ _cau, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_cau.Underlying, expiryMonth) },
{ _euu, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_euu.Underlying, expiryMonth) },
{ _jpu, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_jpu.Underlying, expiryMonth) },
{ _chu, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_chu.Underlying, expiryMonth) },
{ _nzd, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_nzd.Underlying, expiryMonth) },
{ _mxn, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_mxn.Underlying, expiryMonth) },
{ _ead, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_ead.Underlying, expiryMonth) },
{ _ajy, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_ajy.Underlying, expiryMonth) },
{ _ane, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_ane.Underlying, expiryMonth) },
{ _ecd, expiryMonth => SecondFridayBeforeThirdWednesdayOfContractMonth(_ecd.Underlying, expiryMonth) },
{ _le, expiryMonth => FirstFridayOfContractMonth(_le.Underlying, expiryMonth) },
{ _he, expiryMonth => TenthBusinessDayOfContractMonth(_he.Underlying, expiryMonth) },
{ _lbr, expiryMonth => LastBusinessDayInPrecedingMonthFromContractMonth(_lbr.Underlying, expiryMonth) },
{ _lbs, expiryMonth => LastBusinessDayInPrecedingMonthFromContractMonth(_lbs.Underlying, expiryMonth) },
// even though these FOPs are currently quarterly (as underlying), they had until some serial months. Expiration is the same rule for all
{ _es, expiryMonth => FuturesExpiryUtilityFunctions.ThirdFriday(expiryMonth, _es) },
{ _emd, expiryMonth => FuturesExpiryUtilityFunctions.ThirdFriday(expiryMonth, _emd) },
{ _oym, expiryMonth => FuturesExpiryUtilityFunctions.ThirdFriday(expiryMonth, _oym) },
{ _nq, expiryMonth => FuturesExpiryUtilityFunctions.ThirdFriday(expiryMonth, _nq) },
};
/// <summary>
/// Gets the Futures Options' expiry for the given contract month.
/// </summary>
/// <param name="canonicalFutureOptionSymbol">Canonical Futures Options Symbol. Will be made canonical if not provided a canonical</param>
/// <param name="futureContractMonth">Contract month of the underlying Future</param>
/// <returns>Expiry date/time</returns>
public static DateTime FuturesOptionExpiry(Symbol canonicalFutureOptionSymbol, DateTime futureContractMonth)
{
if (!canonicalFutureOptionSymbol.IsCanonical() || !canonicalFutureOptionSymbol.Underlying.IsCanonical())
{
canonicalFutureOptionSymbol = Symbol.CreateCanonicalOption(
Symbol.Create(canonicalFutureOptionSymbol.Underlying.ID.Symbol,
SecurityType.Future,
canonicalFutureOptionSymbol.Underlying.ID.Market));
}
if (!_futuresOptionExpiryFunctions.TryGetValue(canonicalFutureOptionSymbol, out var expiryFunction))
{
// No definition exists for this FOP. Let's default to futures expiry.
return FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureOptionSymbol.Underlying)(futureContractMonth);
}
return expiryFunction(futureContractMonth);
}
/// <summary>
/// Gets the Future Option's expiry from the Future Symbol provided
/// </summary>
/// <param name="futureSymbol">Future (non-canonical) Symbol</param>
/// <param name="canonicalFutureOption">The canonical Future Option Symbol</param>
/// <returns>Future Option Expiry for the Future with the same contract month</returns>
public static DateTime GetFutureOptionExpiryFromFutureExpiry(Symbol futureSymbol, Symbol canonicalFutureOption = null)
{
var futureContractMonth = FuturesExpiryUtilityFunctions.GetFutureContractMonth(futureSymbol);
if (canonicalFutureOption == null)
{
canonicalFutureOption = Symbol.CreateCanonicalOption(
Symbol.Create(futureSymbol.ID.Symbol, SecurityType.Future, futureSymbol.ID.Market));
}
return FuturesOptionExpiry(canonicalFutureOption, futureContractMonth);
}
/// <summary>
/// Expiry function for CBOT Futures Options entries.
/// Returns the Friday before the 2nd last business day of the month preceding the future contract expiry month.
/// </summary>
/// <param name="underlyingFuture">Underlying future symbol</param>
/// <param name="expiryMonth">Expiry month date</param>
/// <returns>Expiry DateTime of the Future Option</returns>
private static DateTime FridayBeforeTwoBusinessDaysBeforeEndOfMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
var expiryMonthPreceding = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1));
var fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(
expiryMonthPreceding,
2,
holidays).AddDays(-1);
while (fridayBeforeSecondLastBusinessDay.DayOfWeek != DayOfWeek.Friday)
{
fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fridayBeforeSecondLastBusinessDay, -1, holidays);
}
return fridayBeforeSecondLastBusinessDay;
}
/// <summary>
/// For Trading that terminates on the 4th last business day of the month prior to the contract month.
/// If the 4th last business day occurs on a Friday or the day before a holiday, trading terminates on the
/// prior business day. This applies to some NYMEX (with fridays), all COMEX.
/// </summary>
/// <param name="underlyingFuture">Underlying Future Symbol</param>
/// <param name="expiryMonth">Contract expiry month</param>
/// <param name="hour">Hour the contract expires at</param>
/// <param name="minutes">Minute the contract expires at</param>
/// <param name="noFridays">Exclude Friday expiration dates from consideration</param>
/// <returns>Expiry DateTime of the Future Option</returns>
private static DateTime FourthLastBusinessDayInPrecedingMonthFromContractMonth(Symbol underlyingFuture, DateTime expiryMonth, int hour, int minutes, bool noFridays = true)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
var expiryMonthPreceding = expiryMonth.AddMonths(-1);
var fourthLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(expiryMonthPreceding, 4, holidays);
if (noFridays)
{
while (fourthLastBusinessDay.DayOfWeek == DayOfWeek.Friday || holidays.Contains(fourthLastBusinessDay.AddDays(1)))
{
fourthLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fourthLastBusinessDay, -1, holidays);
}
}
return fourthLastBusinessDay.AddHours(hour).AddMinutes(minutes);
}
/// <summary>
/// Expiry function for AUD Future Options expiry.
/// Returns the second Friday before the 3rd Wednesday of contract expiry month, 9am.
/// </summary>
/// <param name="underlyingFuture">Underlying future symbol</param>
/// <param name="expiryMonth">Expiry month date</param>
/// <returns>Expiry DateTime of the Future Option</returns>
private static DateTime SecondFridayBeforeThirdWednesdayOfContractMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
var thirdWednesday = FuturesExpiryUtilityFunctions.ThirdWednesday(expiryMonth);
var secondFridayBeforeThirdWednesday = thirdWednesday.AddDays(-12);
if (holidays.Contains(secondFridayBeforeThirdWednesday))
{
secondFridayBeforeThirdWednesday = FuturesExpiryUtilityFunctions.AddBusinessDays(secondFridayBeforeThirdWednesday, -1, holidays);
}
return secondFridayBeforeThirdWednesday.AddHours(9);
}
/// <summary>
/// First friday of the contract month
/// </summary>
public static DateTime FirstFridayOfContractMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
var firstFriday = FuturesExpiryUtilityFunctions.NthFriday(expiryMonth, 1);
if (holidays.Contains(firstFriday))
{
firstFriday = FuturesExpiryUtilityFunctions.AddBusinessDays(firstFriday, -1, holidays);
}
return firstFriday.AddHours(13);
}
/// <summary>
/// Tenth business day of the month
/// </summary>
public static DateTime TenthBusinessDayOfContractMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
return FuturesExpiryUtilityFunctions.NthBusinessDay(expiryMonth, 10, holidays);
}
/// <summary>
/// Last business day of the month preceding the contract month
/// </summary>
private static DateTime LastBusinessDayInPrecedingMonthFromContractMonth(Symbol underlying, DateTime expiryMonth)
{
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlying.ID.Market, underlying.ID.Symbol);
return FuturesExpiryUtilityFunctions.NthLastBusinessDay(expiryMonth.AddMonths(-1), 1, holidays);
}
}
}
@@ -0,0 +1,165 @@
/*
* 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.Securities.Future;
namespace QuantConnect.Securities.Option
{
/// <summary>
/// Defines a margin model for future options (an option with a future as its underlying).
/// We re-use the <see cref="FutureMarginModel"/> implementation and multiply its results
/// by 1.5x to simulate the increased margins seen for future options.
/// </summary>
public class FuturesOptionsMarginModel : FutureMarginModel
{
private readonly Option _futureOption;
/// <summary>
/// Initial Overnight margin requirement for the contract effective from the date of change
/// </summary>
public override decimal InitialOvernightMarginRequirement => GetMarginRequirement(_futureOption, base.InitialOvernightMarginRequirement);
/// <summary>
/// Maintenance Overnight margin requirement for the contract effective from the date of change
/// </summary>
public override decimal MaintenanceOvernightMarginRequirement => GetMarginRequirement(_futureOption, base.MaintenanceOvernightMarginRequirement);
/// <summary>
/// Initial Intraday margin for the contract effective from the date of change
/// </summary>
public override decimal InitialIntradayMarginRequirement => GetMarginRequirement(_futureOption, base.InitialIntradayMarginRequirement);
/// <summary>
/// Maintenance Intraday margin requirement for the contract effective from the date of change
/// </summary>
public override decimal MaintenanceIntradayMarginRequirement => GetMarginRequirement(_futureOption, base.MaintenanceIntradayMarginRequirement);
/// <summary>
/// Creates an instance of FutureOptionMarginModel
/// </summary>
/// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required unused buying power for the account.</param>
/// <param name="futureOption">Option Security containing a Future security as the underlying</param>
public FuturesOptionsMarginModel(decimal requiredFreeBuyingPowerPercent = 0, Option futureOption = null) : base(requiredFreeBuyingPowerPercent, futureOption?.Underlying)
{
_futureOption = futureOption;
}
/// <summary>
/// Gets the margin currently alloted to the specified holding.
/// </summary>
/// <param name="parameters">An object containing the security</param>
/// <returns>The maintenance margin required for the option</returns>
/// <remarks>
/// We fix the option to 1.5x the maintenance because of its close coupling with the underlying.
/// The option's contract multiplier is 1x, but might be more sensitive to volatility shocks in the long
/// run when it comes to calculating the different market scenarios attempting to simulate VaR, resulting
/// in a margin greater than the underlying's margin.
/// </remarks>
public override MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
var underlyingRequirement = base.GetMaintenanceMargin(parameters.ForUnderlying(parameters.Quantity));
var positionSide = parameters.Quantity > 0 ? PositionSide.Long : PositionSide.Short;
return GetMarginRequirement(_futureOption, underlyingRequirement, positionSide);
}
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
/// <param name="parameters">An object containing the security and quantity of shares</param>
/// <returns>The initial margin required for the option (i.e. the equity required to enter a position for this option)</returns>
/// <remarks>
/// We fix the option to 1.5x the initial because of its close coupling with the underlying.
/// The option's contract multiplier is 1x, but might be more sensitive to volatility shocks in the long
/// run when it comes to calculating the different market scenarios attempting to simulate VaR, resulting
/// in a margin greater than the underlying's margin.
/// </remarks>
public override InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
var underlyingRequirement = base.GetInitialMarginRequirement(parameters.ForUnderlying()).Value;
var positionSide = parameters.Quantity > 0 ? PositionSide.Long : PositionSide.Short;
return new InitialMargin(GetMarginRequirement(_futureOption, underlyingRequirement, positionSide));
}
/// <summary>
/// Get's the margin requirement for a future option based on the underlying future margin requirement and the position side to trade.
/// FOPs margin requirement is an 'S' curve based on the underlying requirement around it's current price, see https://en.wikipedia.org/wiki/Logistic_function
/// </summary>
/// <param name="option">The future option contract to trade</param>
/// <param name="underlyingRequirement">The underlying future associated margin requirement</param>
/// <param name="positionSide">The position side to trade, long by default. This is because short positions require higher margin requirements</param>
public static int GetMarginRequirement(Option option, decimal underlyingRequirement, PositionSide positionSide = PositionSide.Long)
{
var maximumValue = underlyingRequirement;
var curveGrowthRate = -7.8m;
var underlyingPrice = option.Underlying.Price;
// If the underlying price is 0, we can't calculate a margin requirement, so return the underlying requirement.
// This could be removed after GH issue #6523 is resolved.
if (option.Underlying == null || option.Underlying.Price == 0m)
{
return 0;
}
if (positionSide == PositionSide.Short)
{
if (option.Right == OptionRight.Call)
{
// going short the curve growth rate is slower
curveGrowthRate = -4m;
// curve shifted to the right -> causes a margin requirement increase
underlyingPrice *= 1.5m;
}
else
{
// higher max requirements
maximumValue *= 1.25m;
// puts are inverter from calls
curveGrowthRate = 2.4m;
// curve shifted to the left -> causes a margin requirement increase
underlyingPrice *= 0.30m;
}
}
else
{
if (option.Right == OptionRight.Put)
{
// fastest change rate
curveGrowthRate = 9m;
}
else
{
maximumValue *= 1.20m;
}
}
// we normalize the curve growth rate by dividing by the underlyings price
// this way, contracts with different order of magnitude price and strike (like CL & ES) share this logic
var denominator = Math.Pow(Math.E, (double) (-curveGrowthRate * (option.ScaledStrikePrice - underlyingPrice) / underlyingPrice));
if (double.IsInfinity(denominator))
{
return 0;
}
if (denominator.IsNaNOrZero())
{
return (int) maximumValue;
}
return (int) (maximumValue / (1 + denominator).SafeDecimalCast());
}
}
}
@@ -0,0 +1,107 @@
/*
* 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;
namespace QuantConnect.Securities.Future
{
/// <summary>
/// Provides conversions from a GLOBEX Futures ticker to a GLOBEX Futures Options ticker
/// </summary>
public static class FuturesOptionsSymbolMappings
{
/// <summary>
/// Defines Futures GLOBEX Ticker -> Futures Options GLOBEX Ticker
/// </summary>
private static Dictionary<string, string> _futureToFutureOptionsGLOBEX = new Dictionary<string, string>
{
{ "EH", "OEH" },
{ "KE", "OKE" },
{ "TN", "OTN" },
{ "UB", "OUB" },
{ "YM", "OYM" },
{ "ZB", "OZB" },
{ "ZC", "OZC" },
{ "ZF", "OZF" },
{ "ZL", "OZL" },
{ "ZM", "OZM" },
{ "ZN", "OZN" },
{ "ZO", "OZO" },
{ "ZS", "OZS" },
{ "ZT", "OZT" },
{ "ZW", "OZW" },
{ "RTY", "RTO" },
{ "GC", "OG" },
{ "HG", "HXE" },
{ "SI", "SO" },
{ "CL", "LO" },
{ "HCL", "HCO" },
{ "HO", "OH" },
{ "NG", "ON" },
{ "PA", "PAO" },
{ "PL", "PO" },
{ "RB", "OB" },
{ "YG", "OYG" },
{ "ZG", "OZG" },
{ "ZI", "OZI" },
{ "6A", "ADU" },
{ "6B", "GBU" },
{ "6C", "CAU" },
{ "6E", "EUU" },
{ "6J", "JPU" },
{ "6S", "CHU" }
};
private static Dictionary<string, string> _futureOptionsToFutureGLOBEX = _futureToFutureOptionsGLOBEX
.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
/// <summary>
/// Returns the futures options ticker for the given futures ticker.
/// </summary>
/// <param name="futureTicker">Future GLOBEX ticker to get Future Option GLOBEX ticker for</param>
/// <returns>Future option ticker. Defaults to future ticker provided if no entry is found</returns>
public static string Map(string futureTicker)
{
futureTicker = futureTicker.ToUpperInvariant();
string result;
if (!_futureToFutureOptionsGLOBEX.TryGetValue(futureTicker, out result))
{
return futureTicker;
}
return result;
}
/// <summary>
/// Maps a futures options ticker to its underlying future's ticker
/// </summary>
/// <param name="futureOptionTicker">Future option ticker to map to the underlying</param>
/// <returns>Future ticker</returns>
public static string MapFromOption(string futureOptionTicker)
{
futureOptionTicker = futureOptionTicker.ToUpperInvariant();
string result;
if (!_futureOptionsToFutureGLOBEX.TryGetValue(futureOptionTicker, out result))
{
return futureOptionTicker;
}
return result;
}
}
}
@@ -0,0 +1,237 @@
/*
* 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.Securities.Future;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Creates the underlying Symbol that corresponds to a futures options contract
/// </summary>
/// <remarks>
/// Because there can exist futures options (FOP) contracts that have an underlying Future
/// that does not have the same contract month as FOPs contract month, we need a way to resolve
/// the underlying Symbol of the FOP to the specific future contract it belongs to.
///
/// Luckily, these FOPs all follow a pattern as to how the underlying is determined. The
/// method <see cref="GetUnderlyingFutureFromFutureOption"/> will automatically resolve the FOP contract's
/// underlying Future, and will ensure that the rules of the underlying are being followed.
///
/// An example of a contract that this happens to is Gold Futures (FUT=GC, FOP=OG). OG FOPs
/// underlying Symbols are not determined by the contract month of the FOP itself, but rather
/// by the closest contract to it in an even month.
///
/// Examples:
/// OGH21 would have an underlying of GCJ21
/// OGJ21 would have an underlying of GCJ21
/// OGK21 would have an underlying of GCM21
/// OGM21 would have an underlying of GCM21...
/// </remarks>
public static class FuturesOptionsUnderlyingMapper
{
private static readonly Dictionary<string, Func<DateTime, DateTime?, DateTime?>> _underlyingFuturesOptionsRules = new Dictionary<string, Func<DateTime, DateTime?, DateTime?>>
{
// CBOT
{ "ZB", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZB", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZC", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZC", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZN", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZN", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZS", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZS", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZM", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZM", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZT", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZT", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZW", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZW", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "ZL", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZL", SecurityType.Future, Market.CBOT), d, ld.Value) },
{ "TN", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("TN", SecurityType.Future, Market.CBOT), d, ld.Value) },
// COMEX
{ "HG", (d, _) => ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(d, true) },
{ "SI", (d, _) => ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(d, true) },
{ "GC", (d, _) => ContractMonthEvenOddMonth(d, false) },
// CME
{ "6A", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6A", SecurityType.Future, Market.CME), d, ld.Value) },
{ "6B", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6B", SecurityType.Future, Market.CME), d, ld.Value) },
{ "6M", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6M", SecurityType.Future, Market.CME), d, ld.Value) },
{ "6J", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6J", SecurityType.Future, Market.CME), d, ld.Value) },
{ "6E", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6E", SecurityType.Future, Market.CME), d, ld.Value) },
{ "6C", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("6C", SecurityType.Future, Market.CME), d, ld.Value) },
};
/// <summary>
/// The difference in months for the Futures expiry month minus the Futures Options expiry month. This assumes
/// that the underlying Future follows a 1-1 mapping between the FOP and future, i.e. this will result in incorrect
/// results, but is needed as an intermediate step to resolve the actual expiry.
/// </summary>
private static readonly IReadOnlyDictionary<string, int> _futuresOptionsExpiryDelta = new Dictionary<string, int>
{
{ "ZB", 1 },
{ "ZC", 1 },
{ "ZM", 1 },
{ "ZN", 1 },
{ "TN", 1 },
{ "ZS", 1 },
{ "ZT", 1 },
{ "ZW", 1 },
{ "ZL", 1 },
{ "HG", 1 },
{ "GC", 1 },
{ "SI", 1 },
{ "UB", 1 },
{ "ZO", 1 },
{ "KE", 1 },
{ "ZF", 1 },
{ "LBR", 1 },
{ "LBS", 1 }
};
/// <summary>
/// Gets the FOP's underlying Future. The underlying Future's contract month might not match
/// the contract month of the Future Option when providing CBOT or COMEX based FOPs contracts to this method.
/// </summary>
/// <param name="futureOptionTicker">Future option ticker</param>
/// <param name="market">Market of the Future Option</param>
/// <param name="futureOptionExpiration">Expiration date of the future option</param>
/// <param name="date">Date to search the future chain provider with. Optional, but required for CBOT based contracts</param>
/// <returns>Symbol if there is an underlying for the FOP, null if there's no underlying found for the Future Option</returns>
public static Symbol GetUnderlyingFutureFromFutureOption(string futureOptionTicker, string market, DateTime futureOptionExpiration, DateTime? date = null)
{
var futureTicker = FuturesOptionsSymbolMappings.MapFromOption(futureOptionTicker);
var canonicalFuture = Symbol.Create(futureTicker, SecurityType.Future, market);
// Get the contract month of the FOP to use when searching for the underlying.
// If the FOP and Future share the same contract month, this is reused as the future's
// contract month so that we can resolve the Future's expiry.
var contractMonth = GetFutureContractMonthNoRulesApplied(canonicalFuture, futureOptionExpiration);
if (_underlyingFuturesOptionsRules.ContainsKey(futureTicker))
{
// The provided ticker follows some sort of rule. Let's figure out the underlying's contract month.
var newFutureContractMonth = _underlyingFuturesOptionsRules[futureTicker](contractMonth, date);
if (newFutureContractMonth == null)
{
// This will only happen when we search the Futures chain for a given contract and no
// closest match could be made, i.e. there are no futures in the chain that come after the FOP's
// contract month.
return null;
}
contractMonth = newFutureContractMonth.Value;
}
var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture)(contractMonth);
return Symbol.CreateFuture(futureTicker, market, futureExpiry);
}
/// <summary>
/// Searches the futures chain for the next matching futures contract, and resolves the underlying
/// as the closest future we can find during or after the contract month.
/// </summary>
/// <param name="canonicalFutureSymbol">Canonical future Symbol</param>
/// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
/// <param name="lookupDate">The date that we'll be using to look at the Future chain</param>
/// <returns>The underlying future's contract month, or null if no closest contract was found</returns>
private static DateTime? ContractMonthSerialLookupRule(Symbol canonicalFutureSymbol, DateTime futureOptionContractMonth, DateTime lookupDate)
{
var futureChain = FuturesListings.ListedContracts(canonicalFutureSymbol.ID.Symbol, lookupDate);
if (futureChain == null)
{
// No matching contract listing rules entry was found
return null;
}
foreach (var future in futureChain.OrderBy(s => s.ID.Date))
{
// Normalize by date first, normalize to a contract month date, then we want to get the contract
// month of the Future contract so we normalize by getting the delta between the expiration
// and the contract month.
var futureContractMonth = FuturesExpiryUtilityFunctions.GetFutureContractMonth(future);
// We want a contract that is either the same as the contract month or greater
if (futureContractMonth < futureOptionContractMonth)
{
continue;
}
return futureContractMonth;
}
// No matching/closest contract was found in the futures chain.
return null;
}
/// <summary>
/// Searches for the closest future's contract month depending on whether the Future Option's contract month is
/// on an even or odd month.
/// </summary>
/// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
/// <param name="oddMonths">True if the Future Option's underlying future contract month is on odd months, false if on even months</param>
/// <returns>The underlying Future's contract month</returns>
private static DateTime ContractMonthEvenOddMonth(DateTime futureOptionContractMonth, bool oddMonths)
{
var monthEven = futureOptionContractMonth.Month % 2 == 0;
if (oddMonths && monthEven)
{
return futureOptionContractMonth.AddMonths(1);
}
if (!oddMonths && !monthEven)
{
return futureOptionContractMonth.AddMonths(1);
}
return futureOptionContractMonth;
}
/// <summary>
/// Sets the contract month to the third month for the first 3 months, then begins using the <see cref="ContractMonthEvenOddMonth"/> rule.
/// </summary>
/// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
/// <param name="oddMonths">True if the Future Option's underlying future contract month is on odd months, false if on even months. Only used for months greater than 3 months</param>
/// <returns></returns>
private static DateTime ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(DateTime futureOptionContractMonth, bool oddMonths)
{
if (futureOptionContractMonth.Month <= 3)
{
return new DateTime(futureOptionContractMonth.Year, 3, 1);
}
return ContractMonthEvenOddMonth(futureOptionContractMonth, oddMonths);
}
/// <summary>
/// Gets the theoretical (i.e. intermediate/naive) future contract month if we assumed a 1-1 mapping
/// between FOPs contract months and Futures contract months, i.e. they share the same contract month.
/// </summary>
/// <param name="canonicalFutureSymbol">Canonical future Symbol</param>
/// <param name="futureOptionExpirationDate">Future Option Expiration Date</param>
/// <returns>Contract month assuming that the Future Option and Future share the same contract month</returns>
public static DateTime GetFutureContractMonthNoRulesApplied(Symbol canonicalFutureSymbol, DateTime futureOptionExpirationDate)
{
var baseOptionExpiryMonthDate = new DateTime(futureOptionExpirationDate.Year, futureOptionExpirationDate.Month, 1);
if (!_futuresOptionsExpiryDelta.ContainsKey(canonicalFutureSymbol.ID.Symbol))
{
// For contracts like CL, they have no expiry delta between the Futures and FOPs, so we hit this path.
// However, it does have a delta between its expiry and contract month, which we adjust here before
// claiming that `baseOptionExpiryMonthDate` is the future's contract month.
var futuresExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureSymbol)(baseOptionExpiryMonthDate);
var futuresDelta = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(canonicalFutureSymbol.ID.Symbol, futuresExpiry);
return baseOptionExpiryMonthDate.AddMonths(futuresDelta);
}
return baseOptionExpiryMonthDate.AddMonths(_futuresOptionsExpiryDelta[canonicalFutureSymbol.ID.Symbol]);
}
}
}
@@ -0,0 +1,71 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Defines the parameters for <see cref="IBuyingPowerModel.GetMaximumOrderQuantityForDeltaBuyingPower"/>
/// </summary>
public class GetMaximumOrderQuantityForDeltaBuyingPowerParameters
{
/// <summary>
/// Gets the algorithm's portfolio
/// </summary>
public SecurityPortfolioManager Portfolio { get; }
/// <summary>
/// Gets the security
/// </summary>
public Security Security { get; }
/// <summary>
/// The delta buying power.
/// </summary>
/// <remarks>Sign defines the position side to apply the delta, positive long, negative short side.</remarks>
public decimal DeltaBuyingPower { get; }
/// <summary>
/// True enables the <see cref="IBuyingPowerModel"/> to skip setting <see cref="GetMaximumOrderQuantityResult.Reason"/>
/// for non error situations, for performance
/// </summary>
public bool SilenceNonErrorReasons { get; }
/// <summary>
/// Configurable minimum order margin portfolio percentage to ignore bad orders, orders with unrealistic small sizes
/// </summary>
/// <remarks>Default value is 0. This setting is useful to avoid small trading noise when using SetHoldings</remarks>
public decimal MinimumOrderMarginPortfolioPercentage { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GetMaximumOrderQuantityForDeltaBuyingPowerParameters"/> class
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security</param>
/// <param name="deltaBuyingPower">The delta buying power to apply.
/// Sign defines the position side to apply the delta</param>
/// <param name="minimumOrderMarginPortfolioPercentage">Configurable minimum order margin portfolio percentage to ignore orders with unrealistic small sizes</param>
/// <param name="silenceNonErrorReasons">True will not return <see cref="GetMaximumOrderQuantityResult.Reason"/>
/// set for non error situation, this is for performance</param>
public GetMaximumOrderQuantityForDeltaBuyingPowerParameters(SecurityPortfolioManager portfolio, Security security, decimal deltaBuyingPower,
decimal minimumOrderMarginPortfolioPercentage, bool silenceNonErrorReasons = false)
{
Portfolio = portfolio;
Security = security;
DeltaBuyingPower = deltaBuyingPower;
SilenceNonErrorReasons = silenceNonErrorReasons;
MinimumOrderMarginPortfolioPercentage = minimumOrderMarginPortfolioPercentage;
}
}
}
@@ -0,0 +1,69 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Defines the parameters for <see cref="IBuyingPowerModel.GetMaximumOrderQuantityForTargetBuyingPower"/>
/// </summary>
public class GetMaximumOrderQuantityForTargetBuyingPowerParameters
{
/// <summary>
/// Gets the algorithm's portfolio
/// </summary>
public SecurityPortfolioManager Portfolio { get; }
/// <summary>
/// Gets the security
/// </summary>
public Security Security { get; }
/// <summary>
/// Gets the target signed percentage buying power
/// </summary>
public decimal TargetBuyingPower { get; }
/// <summary>
/// True enables the <see cref="IBuyingPowerModel"/> to skip setting <see cref="GetMaximumOrderQuantityResult.Reason"/>
/// for non error situations, for performance
/// </summary>
public bool SilenceNonErrorReasons { get; }
/// <summary>
/// Configurable minimum order margin portfolio percentage to ignore bad orders, orders with unrealistic small sizes
/// </summary>
/// <remarks>Default value is 0. This setting is useful to avoid small trading noise when using SetHoldings</remarks>
public decimal MinimumOrderMarginPortfolioPercentage { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GetMaximumOrderQuantityForTargetBuyingPowerParameters"/> class
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security</param>
/// <param name="targetBuyingPower">The target percentage buying power</param>
/// <param name="minimumOrderMarginPortfolioPercentage">Configurable minimum order margin portfolio percentage to ignore orders with unrealistic small sizes</param>
/// <param name="silenceNonErrorReasons">True will not return <see cref="GetMaximumOrderQuantityResult.Reason"/>
/// set for non error situation, this is for performance</param>
public GetMaximumOrderQuantityForTargetBuyingPowerParameters(SecurityPortfolioManager portfolio, Security security,
decimal targetBuyingPower, decimal minimumOrderMarginPortfolioPercentage, bool silenceNonErrorReasons = false)
{
Portfolio = portfolio;
Security = security;
TargetBuyingPower = targetBuyingPower;
SilenceNonErrorReasons = silenceNonErrorReasons;
MinimumOrderMarginPortfolioPercentage = minimumOrderMarginPortfolioPercentage;
}
}
}
@@ -0,0 +1,64 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Contains the information returned by <see cref="IBuyingPowerModel.GetMaximumOrderQuantityForTargetBuyingPower"/>
/// and <see cref="IBuyingPowerModel.GetMaximumOrderQuantityForDeltaBuyingPower"/>
/// </summary>
public class GetMaximumOrderQuantityResult
{
/// <summary>
/// Returns the maximum quantity for the order
/// </summary>
public decimal Quantity { get; }
/// <summary>
/// Returns the reason for which the maximum order quantity is zero
/// </summary>
public string Reason { get; }
/// <summary>
/// Returns true if the zero order quantity is an error condition and will be shown to the user.
/// </summary>
public bool IsError { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GetMaximumOrderQuantityResult"/> class
/// </summary>
/// <param name="quantity">Returns the maximum quantity for the order</param>
/// <param name="reason">The reason for which the maximum order quantity is zero</param>
public GetMaximumOrderQuantityResult(decimal quantity, string reason = null)
{
Quantity = quantity;
Reason = reason ?? string.Empty;
IsError = !string.IsNullOrEmpty(Reason);
}
/// <summary>
/// Initializes a new instance of the <see cref="GetMaximumOrderQuantityResult"/> class
/// </summary>
/// <param name="quantity">Returns the maximum quantity for the order</param>
/// <param name="reason">The reason for which the maximum order quantity is zero</param>
/// <param name="isError">True if the zero order quantity is an error condition</param>
public GetMaximumOrderQuantityResult(decimal quantity, string reason, bool isError = true)
{
Quantity = quantity;
Reason = reason ?? string.Empty;
IsError = isError;
}
}
}
@@ -0,0 +1,45 @@
/*
* 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.
*
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Defines the parameters for <see cref="IPriceVariationModel.GetMinimumPriceVariation"/>
/// </summary>
public class GetMinimumPriceVariationParameters
{
/// <summary>
/// Gets the security
/// </summary>
public Security Security { get; }
/// <summary>
/// Gets the reference price to be used for the calculation
/// </summary>
public decimal ReferencePrice { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GetMinimumPriceVariationParameters"/> class
/// </summary>
/// <param name="security">The security</param>
/// <param name="referencePrice">The reference price to be used for the calculation</param>
public GetMinimumPriceVariationParameters(Security security, decimal referencePrice)
{
Security = security;
ReferencePrice = referencePrice;
}
}
}
@@ -0,0 +1,93 @@
/*
* 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 static QuantConnect.StringExtensions;
namespace QuantConnect.Securities
{
/// <summary>
/// Defines the parameters for <see cref="IBuyingPowerModel.HasSufficientBuyingPowerForOrder"/>
/// </summary>
public class HasSufficientBuyingPowerForOrderParameters
{
/// <summary>
/// Gets the algorithm's portfolio
/// </summary>
public SecurityPortfolioManager Portfolio { get; }
/// <summary>
/// Gets the security
/// </summary>
public Security Security { get; }
/// <summary>
/// Gets the order
/// </summary>
public Order Order { get; }
/// <summary>
/// Initializes a new instance of the <see cref="HasSufficientBuyingPowerForOrderParameters"/> class
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security</param>
/// <param name="order">The order</param>
public HasSufficientBuyingPowerForOrderParameters(SecurityPortfolioManager portfolio, Security security, Order order)
{
Portfolio = portfolio;
Security = security;
Order = order;
}
/// <summary>
/// Creates a new <see cref="HasSufficientBuyingPowerForOrderParameters"/> targeting the security's underlying.
/// If the security does not implement <see cref="IDerivativeSecurity"/> then an <see cref="InvalidCastException"/>
/// will be thrown. If the order's symbol does not match the underlying then an <see cref="ArgumentException"/> will
/// be thrown.
/// </summary>
/// <param name="order">The new order targeting the underlying</param>
/// <returns>New parameters instance suitable for invoking the sufficient capital method for the underlying security</returns>
public HasSufficientBuyingPowerForOrderParameters ForUnderlying(Order order)
{
var derivative = (IDerivativeSecurity) Security;
return new HasSufficientBuyingPowerForOrderParameters(Portfolio, derivative.Underlying, order);
}
/// <summary>
/// Creates a new result indicating that there is sufficient buying power for the contemplated order
/// </summary>
public HasSufficientBuyingPowerForOrderResult Sufficient()
{
return new HasSufficientBuyingPowerForOrderResult(true);
}
/// <summary>
/// Creates a new result indicating that there is insufficient buying power for the contemplated order
/// </summary>
public HasSufficientBuyingPowerForOrderResult Insufficient(string reason)
{
return new HasSufficientBuyingPowerForOrderResult(false, reason);
}
/// <summary>
/// Creates a new result indicating that there is insufficient buying power for the contemplated order
/// </summary>
public HasSufficientBuyingPowerForOrderResult Insufficient(FormattableString reason)
{
return new HasSufficientBuyingPowerForOrderResult(false, Invariant(reason));
}
}
}
@@ -0,0 +1,44 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Contains the information returned by <see cref="IBuyingPowerModel.HasSufficientBuyingPowerForOrder"/>
/// </summary>
public class HasSufficientBuyingPowerForOrderResult
{
/// <summary>
/// Gets true if there is sufficient buying power to execute an order
/// </summary>
public bool IsSufficient { get; }
/// <summary>
/// Gets the reason for insufficient buying power to execute an order
/// </summary>
public string Reason { get; }
/// <summary>
/// Initializes a new instance of the <see cref="HasSufficientBuyingPowerForOrderResult"/> class
/// </summary>
/// <param name="isSufficient">True if the order can be executed</param>
/// <param name="reason">The reason for insufficient buying power</param>
public HasSufficientBuyingPowerForOrderResult(bool isSufficient, string reason = null)
{
IsSufficient = isSufficient;
Reason = reason ?? string.Empty;
}
}
}
+32
View File
@@ -0,0 +1,32 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Interface for various currency symbols
/// </summary>
public interface IBaseCurrencySymbol
{
/// <summary>
/// Gets the currency acquired by going long this currency pair
/// </summary>
/// <remarks>
/// For example, the EUR/USD has a base currency of the euro, and as a result
/// of going long the EUR/USD a trader is acquiring euros in exchange for US dollars
/// </remarks>
public Cash BaseCurrency { get; }
}
}
+99
View File
@@ -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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Represents a security's model of buying power
/// </summary>
public interface IBuyingPowerModel
{
/// <summary>
/// Gets the current leverage of the security
/// </summary>
/// <param name="security">The security to get leverage for</param>
/// <returns>The current leverage in the security</returns>
decimal GetLeverage(Security security);
/// <summary>
/// Sets the leverage for the applicable securities, i.e, equities
/// </summary>
/// <remarks>
/// This is added to maintain backwards compatibility with the old margin/leverage system
/// </remarks>
/// <param name="security">The security to set leverage for</param>
/// <param name="leverage">The new leverage</param>
void SetLeverage(Security security, decimal leverage);
/// <summary>
/// Gets the margin currently allocated to the specified holding
/// </summary>
/// <param name="parameters">An object containing the security and holdings quantity/cost/value</param>
/// <returns>The maintenance margin required for the provided holdings quantity/cost/value</returns>
MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters);
/// <summary>
/// The margin that must be held in order to increase the position by the provided quantity
/// </summary>
/// <param name="parameters">An object containing the security and quantity</param>
/// <returns>The initial margin required for the provided security and quantity</returns>
InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters);
/// <summary>
/// Gets the total margin required to execute the specified order in units of the account currency including fees
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>The total margin in terms of the currency quoted in the order</returns>
InitialMargin GetInitialMarginRequiredForOrder(InitialMarginRequiredForOrderParameters parameters);
/// <summary>
/// Check if there is sufficient buying power to execute this order.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the order</param>
/// <returns>Returns buying power information for an order</returns>
HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters);
/// <summary>
/// Get the maximum market order quantity to obtain a position with a given buying power percentage.
/// Will not take into account free buying power.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters);
/// <summary>
/// Get the maximum market order quantity to obtain a delta in the buying power used by a security.
/// The deltas sign defines the position side to apply it to, positive long, negative short.
/// </summary>
/// <param name="parameters">An object containing the portfolio, the security and the delta buying power</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
/// <remarks>Used by the margin call model to reduce the position by a delta percent.</remarks>
GetMaximumOrderQuantityResult GetMaximumOrderQuantityForDeltaBuyingPower(GetMaximumOrderQuantityForDeltaBuyingPowerParameters parameters);
/// <summary>
/// Gets the amount of buying power reserved to maintain the specified position
/// </summary>
/// <param name="parameters">A parameters object containing the security</param>
/// <returns>The reserved buying power in account currency</returns>
ReservedBuyingPowerForPosition GetReservedBuyingPowerForPosition(ReservedBuyingPowerForPositionParameters parameters);
/// <summary>
/// Gets the buying power available for a trade
/// </summary>
/// <param name="parameters">A parameters object containing the algorithm's portfolio, security, and order direction</param>
/// <returns>The buying power available for the trade</returns>
BuyingPower GetBuyingPower(BuyingPowerParameters parameters);
}
}
+31
View File
@@ -0,0 +1,31 @@
/*
* 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 QuantConnect.Data;
namespace QuantConnect.Securities
{
/// <summary>
/// Base interface intended for chain universe data to have some of their symbol properties accessible directly.
/// </summary>
public interface IChainUniverseData : IBaseData
{
/// <summary>
/// Gets the security identifier.
/// </summary>
SecurityIdentifier ID { get; }
}
}
+28
View File
@@ -0,0 +1,28 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// A continuous security that get's mapped during his life
/// </summary>
public interface IContinuousSecurity
{
/// <summary>
/// Gets or sets the currently mapped symbol for the security
/// </summary>
Symbol Mapped { get; set; }
}
}
+35
View File
@@ -0,0 +1,35 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Provides the ability to convert cash amounts to the account currency
/// </summary>
public interface ICurrencyConverter
{
/// <summary>
/// Gets account currency
/// </summary>
string AccountCurrency { get; }
/// <summary>
/// Converts a cash amount to the account currency
/// </summary>
/// <param name="cashAmount">The <see cref="CashAmount"/> instance to convert</param>
/// <returns>A new <see cref="CashAmount"/> instance denominated in the account currency</returns>
CashAmount ConvertToAccountCurrency(CashAmount cashAmount);
}
}
+28
View File
@@ -0,0 +1,28 @@
/*
* 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.
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Defines a security as a derivative of another security
/// </summary>
public interface IDerivativeSecurity
{
/// <summary>
/// Gets or sets the underlying security for the derivative
/// </summary>
Security Underlying { get; set; }
}
}
@@ -0,0 +1,37 @@
/*
* 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.
*
*/
namespace QuantConnect.Securities
{
/// <summary>
/// Filters a set of derivative symbols using the underlying price data.
/// </summary>
public interface IDerivativeSecurityFilter<T>
where T : IChainUniverseData
{
/// <summary>
/// Filters the input set of symbols represented by the universe
/// </summary>
/// <param name="universe">derivative symbols universe used in filtering</param>
/// <returns>The filtered set of symbols</returns>
IDerivativeSecurityFilterUniverse<T> Filter(IDerivativeSecurityFilterUniverse<T> universe);
/// <summary>
/// True if this universe filter can run async in the data stack
/// </summary>
bool Asynchronous { get; set; }
}
}
@@ -0,0 +1,32 @@
/*
* 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;
namespace QuantConnect.Securities
{
/// <summary>
/// Represents derivative symbols universe used in filtering.
/// </summary>
public interface IDerivativeSecurityFilterUniverse<T> : IEnumerable<T>
where T : IChainUniverseData
{
/// <summary>
/// The number of contracts in the universe
/// </summary>
int Count { get; }
}
}
+69
View File
@@ -0,0 +1,69 @@
/*
* 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 QuantConnect.Orders;
namespace QuantConnect.Securities
{
/// <summary>
/// Represents the model responsible for picking which orders should be executed during a margin call
/// </summary>
public interface IMarginCallModel
{
/// <summary>
/// Scan the portfolio and the updated data for a potential margin call situation which may get the holdings below zero!
/// If there is a margin call, liquidate the portfolio immediately before the portfolio gets sub zero.
/// </summary>
/// <param name="issueMarginCallWarning">Set to true if a warning should be issued to the algorithm</param>
/// <returns>True for a margin call on the holdings.</returns>
List<SubmitOrderRequest> GetMarginCallOrders(out bool issueMarginCallWarning);
/// <summary>
/// Executes synchronous orders to bring the account within margin requirements.
/// </summary>
/// <param name="generatedMarginCallOrders">These are the margin call orders that were generated
/// by individual security margin models.</param>
/// <returns>The list of orders that were actually executed</returns>
List<OrderTicket> ExecuteMarginCall(IEnumerable<SubmitOrderRequest> generatedMarginCallOrders);
}
/// <summary>
/// Provides access to a null implementation for <see cref="IMarginCallModel"/>
/// </summary>
public static class MarginCallModel
{
/// <summary>
/// Gets an instance of <see cref="IMarginCallModel"/> that will always
/// return an empty list of executed orders.
/// </summary>
public static readonly IMarginCallModel Null = new NullMarginCallModel();
private sealed class NullMarginCallModel : IMarginCallModel
{
public List<SubmitOrderRequest> GetMarginCallOrders(out bool issueMarginCallWarning)
{
issueMarginCallWarning = false;
return new List<SubmitOrderRequest>();
}
public List<OrderTicket> ExecuteMarginCall(IEnumerable<SubmitOrderRequest> generatedMarginCallOrders)
{
return new List<OrderTicket>();
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More