Lean  $LEAN_TAG$
LocalDiskShortableProvider.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 QuantConnect.Util;
19 using System.Threading.Tasks;
21 using System.Collections.Generic;
22 using System.Globalization;
23 
25 {
26  /// <summary>
27  /// Sources short availability data from the local disk for the given brokerage
28  /// </summary>
30  {
31  /// <summary>
32  /// The data provider instance to use
33  /// </summary>
34  protected static IDataProvider DataProvider = Composer.Instance.GetPart<IDataProvider>();
35 
36  private string _ticker;
37  private bool _scheduledCleanup;
38  private Dictionary<DateTime, ShortableData> _shortableDataPerDate;
39 
40  /// <summary>
41  /// The short availability provider
42  /// </summary>
43  protected string Brokerage { get; set; }
44 
45  /// <summary>
46  /// Creates an instance of the class. Establishes the directory to read from.
47  /// </summary>
48  /// <param name="brokerage">Brokerage to read the short availability data</param>
49  public LocalDiskShortableProvider(string brokerage)
50  {
51  Brokerage = brokerage.ToLowerInvariant();
52  }
53 
54  /// <summary>
55  /// Gets interest rate charged on borrowed shares for a given asset.
56  /// </summary>
57  /// <param name="symbol">Symbol to lookup fee rate</param>
58  /// <param name="localTime">Time of the algorithm</param>
59  /// <returns>Fee rate. Zero if the data for the brokerage/date does not exist.</returns>
60  public decimal FeeRate(Symbol symbol, DateTime localTime)
61  {
62  if (symbol != null && GetCacheData(symbol).TryGetValue(localTime.Date, out var result))
63  {
64  return result.FeeRate;
65  }
66  // Any missing entry will be considered to be zero.
67  return 0m;
68  }
69 
70  /// <summary>
71  /// Gets the Fed funds or other currency-relevant benchmark rate minus the interest rate charged on borrowed shares for a given asset.
72  /// E.g.: Interest rate - borrow fee rate = borrow rebate rate: 5.32% - 0.25% = 5.07%.
73  /// </summary>
74  /// <param name="symbol">Symbol to lookup rebate rate</param>
75  /// <param name="localTime">Time of the algorithm</param>
76  /// <returns>Rebate fee. Zero if the data for the brokerage/date does not exist.</returns>
77  public decimal RebateRate(Symbol symbol, DateTime localTime)
78  {
79  if (symbol != null && GetCacheData(symbol).TryGetValue(localTime.Date, out var result))
80  {
81  return result.RebateFee;
82  }
83  // Any missing entry will be considered to be zero.
84  return 0m;
85  }
86 
87  /// <summary>
88  /// Gets the quantity shortable for the Symbol at the given date.
89  /// </summary>
90  /// <param name="symbol">Symbol to lookup shortable quantity</param>
91  /// <param name="localTime">Time of the algorithm</param>
92  /// <returns>Quantity shortable. Null if the data for the brokerage/date does not exist.</returns>
93  public long? ShortableQuantity(Symbol symbol, DateTime localTime)
94  {
95  if (symbol != null && GetCacheData(symbol).TryGetValue(localTime.Date, out var result))
96  {
97  return result.ShortableQuantity;
98  }
99  // Any missing entry will be considered to be Shortable.
100  return null;
101  }
102 
103  /// <summary>
104  /// We cache data per ticker
105  /// </summary>
106  /// <param name="symbol">The requested symbol</param>
107  private Dictionary<DateTime, ShortableData> GetCacheData(Symbol symbol)
108  {
109  var result = _shortableDataPerDate;
110  if (_ticker == symbol.Value)
111  {
112  return result;
113  }
114 
115  if (!_scheduledCleanup)
116  {
117  // we schedule it once
118  _scheduledCleanup = true;
119  ClearCache();
120  }
121 
122  // create a new collection
123  _ticker = symbol.Value;
124  result = _shortableDataPerDate = new();
125 
126  // Implicitly trusts that Symbol.Value has been mapped and updated to the latest ticker
127  var shortableSymbolFile = Path.Combine(Globals.DataFolder, symbol.SecurityType.SecurityTypeToLower(), symbol.ID.Market,
128  "shortable", Brokerage, "symbols", $"{_ticker.ToLowerInvariant()}.csv");
129 
130  foreach (var line in DataProvider.ReadLines(shortableSymbolFile))
131  {
132  if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
133  {
134  // ignore empty or comment lines
135  continue;
136  }
137  // Data example. The rates, if available, are expressed in percentage.
138  // 20201221,2000,5.0700,0.2500
139  var csv = line.Split(',');
140  var date = Parse.DateTimeExact(csv[0], "yyyyMMdd");
141  var lenght = csv.Length;
142  var shortableQuantity = csv[1].IfNotNullOrEmpty(s => long.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture));
143  var rebateRate = csv.Length > 2 ? csv[2].IfNotNullOrEmpty(s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture)) : 0;
144  var feeRate = csv.Length > 3 ? csv[3].IfNotNullOrEmpty(s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture)) : 0;
145  result[date] = new ShortableData(shortableQuantity, rebateRate / 100, feeRate / 100);
146  }
147 
148  return result;
149  }
150 
151  /// <summary>
152  /// For live deployments we don't want to have stale short quantity so we refresh them every day
153  /// </summary>
154  private void ClearCache()
155  {
156  var now = DateTime.UtcNow;
157  var tomorrowMidnight = now.Date.AddDays(1);
158  var delayToClean = tomorrowMidnight - now;
159 
160  Task.Delay(delayToClean).ContinueWith((_) =>
161  {
162  // create new instances so we don't need to worry about locks
163  _ticker = null;
164  _shortableDataPerDate = new();
165 
166  ClearCache();
167  });
168  }
169 
170  protected record ShortableData(long? ShortableQuantity, decimal RebateFee, decimal FeeRate);
171  }
172 }