/*
* 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();
});
}
}
}