/* * 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 { /// /// Represents a simple margin model for margin futures. Margin file contains Initial and Maintenance margins /// public class FutureMarginModel : SecurityMarginModel { private static IDataProvider _dataProvider; private static readonly object _locker = new(); private static Dictionary _marginRequirementsCache = new(); // historical database of margin requirements private int _marginCurrentIndex; private readonly Security _security; /// /// True will enable usage of intraday margins. /// /// Disabled by default. Note that intraday margins are less than overnight margins /// and could lead to margin calls public bool EnableIntradayMargins { get; set; } /// /// Initial Overnight margin requirement for the contract effective from the date of change /// public virtual decimal InitialOvernightMarginRequirement => GetCurrentMarginRequirements(_security)?.InitialOvernight ?? 0m; /// /// Maintenance Overnight margin requirement for the contract effective from the date of change /// public virtual decimal MaintenanceOvernightMarginRequirement => GetCurrentMarginRequirements(_security)?.MaintenanceOvernight ?? 0m; /// /// Initial Intraday margin for the contract effective from the date of change /// public virtual decimal InitialIntradayMarginRequirement => GetCurrentMarginRequirements(_security)?.InitialIntraday ?? 0m; /// /// Maintenance Intraday margin requirement for the contract effective from the date of change /// public virtual decimal MaintenanceIntradayMarginRequirement => GetCurrentMarginRequirements(_security)?.MaintenanceIntraday ?? 0m; /// /// Initializes a new instance of the /// /// The percentage used to determine the required unused buying power for the account. /// The security that this model belongs to public FutureMarginModel(decimal requiredFreeBuyingPowerPercent = 0, Security security = null) { RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent; _security = security; } /// /// Gets the current leverage of the security /// /// The security to get leverage for /// The current leverage in the security public override decimal GetLeverage(Security security) { return 1; } /// /// Sets the leverage for the applicable securities, i.e, futures /// /// /// This is added to maintain backwards compatibility with the old margin/leverage system /// /// /// The new leverage 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"); } /// /// Get the maximum market order quantity to obtain a position with a given buying power percentage. /// Will not take into account free buying power. /// /// An object containing the portfolio, the security and the target signed buying power percentage /// Returns the maximum allowed market order quantity and if zero, also the reason 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); } /// /// Gets the total margin required to execute the specified order in units of the account currency including fees /// /// An object containing the portfolio, the security and the order /// The total margin in terms of the currency quoted in the order 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); } /// /// Gets the margin currently allotted to the specified holding /// /// An object containing the security /// The maintenance margin required for the 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; } /// /// The margin that must be held in order to increase the position by the provided quantity /// 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]; } /// /// Gets the sorted list of historical margin changes produced by reading in the margin requirements /// data found in /Data/symbol-margin/ /// /// Sorted list of historical margin changes 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 marginRequirementsCache = new(_marginRequirementsCache) { [symbol.ID.Symbol] = marginRequirementsEntries = FromCsvFile(symbol) }; // we change the reference so we can read without a lock _marginRequirementsCache = marginRequirementsCache; } } } return marginRequirementsEntries; } /// /// Reads margin requirements file and returns a sorted list of historical margin changes /// /// The symbol to fetch margin requirements for /// Sorted list of historical margin changes 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(); } // 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; } /// /// For live deployments we don't want to have stale margin requirements to we refresh them every day /// private static void ClearMarginCache() { Task.Delay(Time.OneDay).ContinueWith((_) => { lock (_locker) { _marginRequirementsCache = new(); } ClearMarginCache(); }); } } }