Lean  $LEAN_TAG$
FutureMarginModel.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.IO;
18 using System.Linq;
19 using QuantConnect.Util;
20 using QuantConnect.Logging;
21 using System.Threading.Tasks;
25 using System.Collections.Generic;
26 
28 {
29  /// <summary>
30  /// Represents a simple margin model for margin futures. Margin file contains Initial and Maintenance margins
31  /// </summary>
33  {
34  private static IDataProvider _dataProvider;
35  private static readonly object _locker = new();
36  private static Dictionary<string, MarginRequirementsEntry[]> _marginRequirementsCache = new();
37 
38  // historical database of margin requirements
39  private int _marginCurrentIndex;
40 
41  private readonly Security _security;
42 
43  /// <summary>
44  /// True will enable usage of intraday margins.
45  /// </summary>
46  /// <remarks>Disabled by default. Note that intraday margins are less than overnight margins
47  /// and could lead to margin calls</remarks>
48  public bool EnableIntradayMargins { get; set; }
49 
50  /// <summary>
51  /// Initial Overnight margin requirement for the contract effective from the date of change
52  /// </summary>
53  public virtual decimal InitialOvernightMarginRequirement => GetCurrentMarginRequirements(_security)?.InitialOvernight ?? 0m;
54 
55  /// <summary>
56  /// Maintenance Overnight margin requirement for the contract effective from the date of change
57  /// </summary>
58  public virtual decimal MaintenanceOvernightMarginRequirement => GetCurrentMarginRequirements(_security)?.MaintenanceOvernight ?? 0m;
59 
60  /// <summary>
61  /// Initial Intraday margin for the contract effective from the date of change
62  /// </summary>
63  public virtual decimal InitialIntradayMarginRequirement => GetCurrentMarginRequirements(_security)?.InitialIntraday ?? 0m;
64 
65  /// <summary>
66  /// Maintenance Intraday margin requirement for the contract effective from the date of change
67  /// </summary>
68  public virtual decimal MaintenanceIntradayMarginRequirement => GetCurrentMarginRequirements(_security)?.MaintenanceIntraday ?? 0m;
69 
70  /// <summary>
71  /// Initializes a new instance of the <see cref="FutureMarginModel"/>
72  /// </summary>
73  /// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required unused buying power for the account.</param>
74  /// <param name="security">The security that this model belongs to</param>
75  public FutureMarginModel(decimal requiredFreeBuyingPowerPercent = 0, Security security = null)
76  {
77  RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent;
78  _security = security;
79  }
80 
81  /// <summary>
82  /// Gets the current leverage of the security
83  /// </summary>
84  /// <param name="security">The security to get leverage for</param>
85  /// <returns>The current leverage in the security</returns>
86  public override decimal GetLeverage(Security security)
87  {
88  return 1;
89  }
90 
91  /// <summary>
92  /// Sets the leverage for the applicable securities, i.e, futures
93  /// </summary>
94  /// <remarks>
95  /// This is added to maintain backwards compatibility with the old margin/leverage system
96  /// </remarks>
97  /// <param name="security"></param>
98  /// <param name="leverage">The new leverage</param>
99  public override void SetLeverage(Security security, decimal leverage)
100  {
101  // Futures are leveraged products and different leverage cannot be set by user.
102  throw new InvalidOperationException("Futures are leveraged products and different leverage cannot be set by user");
103  }
104 
105  /// <summary>
106  /// Get the maximum market order quantity to obtain a position with a given buying power percentage.
107  /// Will not take into account free buying power.
108  /// </summary>
109  /// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param>
110  /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
113  {
114  if (Math.Abs(parameters.TargetBuyingPower) > 1)
115  {
116  throw new InvalidOperationException(
117  "Futures do not allow specifying a leveraged target, since they are traded using margin which already is leveraged. " +
118  $"Possible target buying power goes from -1 to 1, target provided is: {parameters.TargetBuyingPower}");
119  }
120  return base.GetMaximumOrderQuantityForTargetBuyingPower(parameters);
121  }
122 
123  /// <summary>
124  /// Gets the total margin required to execute the specified order in units of the account currency including fees
125  /// </summary>
126  /// <param name="parameters">An object containing the portfolio, the security and the order</param>
127  /// <returns>The total margin in terms of the currency quoted in the order</returns>
130  )
131  {
132  //Get the order value from the non-abstract order classes (MarketOrder, LimitOrder, StopMarketOrder)
133  //Market order is approximated from the current security price and set in the MarketOrder Method in QCAlgorithm.
134 
135  var fees = parameters.Security.FeeModel.GetOrderFee(
136  new OrderFeeParameters(parameters.Security,
137  parameters.Order)).Value;
138  var feesInAccountCurrency = parameters.CurrencyConverter.
139  ConvertToAccountCurrency(fees).Amount;
140 
141  var orderMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Order.Quantity);
142 
143  return new InitialMargin(orderMargin + Math.Sign(orderMargin) * feesInAccountCurrency);
144  }
145 
146  /// <summary>
147  /// Gets the margin currently allotted to the specified holding
148  /// </summary>
149  /// <param name="parameters">An object containing the security</param>
150  /// <returns>The maintenance margin required for the </returns>
152  {
153  if (parameters.Quantity == 0m)
154  {
155  return 0m;
156  }
157 
158  var security = parameters.Security;
159  var marginReq = GetCurrentMarginRequirements(security);
160  if (marginReq == null)
161  {
162  return 0m;
163  }
164 
166  && security.Exchange.ExchangeOpen
167  && !security.Exchange.ClosingSoon)
168  {
169  return marginReq.MaintenanceIntraday * parameters.AbsoluteQuantity * security.QuoteCurrency.ConversionRate;
170  }
171 
172  // margin is per contract
173  return marginReq.MaintenanceOvernight * parameters.AbsoluteQuantity * security.QuoteCurrency.ConversionRate;
174  }
175 
176  /// <summary>
177  /// The margin that must be held in order to increase the position by the provided quantity
178  /// </summary>
180  {
181  var security = parameters.Security;
182  var quantity = parameters.Quantity;
183  if (quantity == 0m)
184  {
185  return InitialMargin.Zero;
186  }
187 
188  var marginReq = GetCurrentMarginRequirements(security);
189  if (marginReq == null)
190  {
191  return InitialMargin.Zero;
192  }
193 
195  && security.Exchange.ExchangeOpen
196  && !security.Exchange.ClosingSoon)
197  {
198  return new InitialMargin(marginReq.InitialIntraday * quantity * security.QuoteCurrency.ConversionRate);
199  }
200 
201  // margin is per contract
202  return new InitialMargin(marginReq.InitialOvernight * quantity * security.QuoteCurrency.ConversionRate);
203  }
204 
205  private MarginRequirementsEntry GetCurrentMarginRequirements(Security security)
206  {
207  var lastData = security?.GetLastData();
208  if (lastData == null)
209  {
210  return null;
211  }
212 
213  var marginRequirementsHistory = LoadMarginRequirementsHistory(security.Symbol);
214  var date = lastData.Time.Date;
215 
216  while (_marginCurrentIndex + 1 < marginRequirementsHistory.Length &&
217  marginRequirementsHistory[_marginCurrentIndex + 1].Date <= date)
218  {
219  _marginCurrentIndex++;
220  }
221 
222  return marginRequirementsHistory[_marginCurrentIndex];
223  }
224 
225  /// <summary>
226  /// Gets the sorted list of historical margin changes produced by reading in the margin requirements
227  /// data found in /Data/symbol-margin/
228  /// </summary>
229  /// <returns>Sorted list of historical margin changes</returns>
230  private static MarginRequirementsEntry[] LoadMarginRequirementsHistory(Symbol symbol)
231  {
232  if (!_marginRequirementsCache.TryGetValue(symbol.ID.Symbol, out var marginRequirementsEntries))
233  {
234  lock (_locker)
235  {
236  if (!_marginRequirementsCache.TryGetValue(symbol.ID.Symbol, out marginRequirementsEntries))
237  {
238  Dictionary<string, MarginRequirementsEntry[]> marginRequirementsCache = new(_marginRequirementsCache)
239  {
240  [symbol.ID.Symbol] = marginRequirementsEntries = FromCsvFile(symbol)
241  };
242  // we change the reference so we can read without a lock
243  _marginRequirementsCache = marginRequirementsCache;
244  }
245  }
246  }
247  return marginRequirementsEntries;
248  }
249 
250  /// <summary>
251  /// Reads margin requirements file and returns a sorted list of historical margin changes
252  /// </summary>
253  /// <param name="symbol">The symbol to fetch margin requirements for</param>
254  /// <returns>Sorted list of historical margin changes</returns>
255  private static MarginRequirementsEntry[] FromCsvFile(Symbol symbol)
256  {
257  var file = Path.Combine(Globals.DataFolder,
258  symbol.SecurityType.ToLower(),
259  symbol.ID.Market.ToLowerInvariant(),
260  "margins", symbol.ID.Symbol + ".csv");
261 
262  if(_dataProvider == null)
263  {
264  ClearMarginCache();
265  _dataProvider = Composer.Instance.GetPart<IDataProvider>();
266  }
267 
268  // skip the first header line, also skip #'s as these are comment lines
269  var marginRequirementsEntries = _dataProvider.ReadLines(file)
270  .Where(x => !x.StartsWith("#") && !string.IsNullOrWhiteSpace(x))
271  .Skip(1)
272  .Select(MarginRequirementsEntry.Create)
273  .OrderBy(x => x.Date)
274  .ToArray();
275 
276  if (marginRequirementsEntries.Length == 0)
277  {
278  Log.Error($"FutureMarginModel.FromCsvFile(): Unable to locate future margin requirements file. Defaulting to zero margin for this symbol. File: {file}");
279 
280  marginRequirementsEntries = new[] {
281  new MarginRequirementsEntry
282  {
283  Date = DateTime.MinValue
284  }
285  };
286  }
287  return marginRequirementsEntries;
288  }
289 
290  /// <summary>
291  /// For live deployments we don't want to have stale margin requirements to we refresh them every day
292  /// </summary>
293  private static void ClearMarginCache()
294  {
295  Task.Delay(Time.OneDay).ContinueWith((_) =>
296  {
297  lock (_locker)
298  {
299  _marginRequirementsCache = new();
300  }
301  ClearMarginCache();
302  });
303  }
304  }
305 }