Lean  $LEAN_TAG$
IntrinioEconomicData.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.Collections.Generic;
18 using System.Globalization;
19 using System.Text;
20 
22 {
23  /// <summary>
24  /// TRanformation available for the Economic data.
25  /// </summary>
27  {
28  /// <summary>
29  /// The rate of change
30  /// </summary>
31  Roc,
32 
33  /// <summary>
34  /// Rate of change from Year Ago
35  /// </summary>
36  AnnualyRoc,
37 
38  /// <summary>
39  /// The compounded annual rate of change
40  /// </summary>
42 
43  /// <summary>
44  /// The continuously compounded annual rate of change
45  /// </summary>
47 
48  /// <summary>
49  /// The continuously compounded rateof change
50  /// </summary>
51  CCRoc,
52 
53  /// <summary>
54  /// The level, no transformation.
55  /// </summary>
56  Level,
57 
58  /// <summary>
59  /// The natural log
60  /// </summary>
61  Ln,
62 
63  /// <summary>
64  /// The percent change
65  /// </summary>
66  Pc,
67 
68  /// <summary>
69  /// The percent change from year ago
70  /// </summary>
71  AnnualyPc
72  }
73 
74  /// <summary>
75  /// Access the massive repository of economic data from the Federal Reserve Economic Data system via the Intrinio API.
76  /// </summary>
77  /// <seealso cref="QuantConnect.Data.BaseData" />
79  {
80  private static DateTime _lastApiCall = DateTime.MinValue;
81  private static TimeSpan _msSinceLastCall = TimeSpan.MaxValue;
82 
83  private readonly string _baseUrl = @"https://api.intrinio.com/historical_data.csv?";
84 
85  private readonly IntrinioDataTransformation _dataTransformation;
86 
87 
88  private bool _backtestingFirstTimeCallOrLiveMode = true;
89 
90  /// <summary>
91  /// Initializes a new instance of the <see cref="IntrinioEconomicData" /> class.
92  /// </summary>
94  {
95  }
96 
97  /// <summary>
98  /// Initializes a new instance of the <see cref="IntrinioEconomicData" /> class.
99  /// </summary>
100  /// <param name="dataTransformation">The item.</param>
102  {
103  _dataTransformation = dataTransformation;
104 
105  // If the user and the password is not set then then throw error.
107  {
108  throw new
109  InvalidOperationException("Please set a valid Intrinio user and password using the 'IntrinioEconomicData.SetUserAndPassword' static method. " +
110  "For local backtesting, the user and password can be set in the 'parameters' fields from the 'config.json' file.");
111  }
112  }
113 
114 
115  /// <summary>
116  /// Return the URL string source of the file. This will be converted to a stream
117  /// </summary>
118  /// <param name="config">Configuration object</param>
119  /// <param name="date">Date of this source file</param>
120  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
121  /// <returns>
122  /// String URL of source file.
123  /// </returns>
124  /// <remarks>
125  /// Given Intrinio's API limits, we cannot make more than one CSV request per second. That's why in backtesting mode
126  /// we make sure we make just one call to retrieve all the data needed. Also, to avoid the problem of many sources
127  /// asking the data at the beginning of the algorithm, a pause of a second is added.
128  /// </remarks>
129  public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
130  {
131  SubscriptionDataSource subscription;
132 
133  // We want to make just one call with all the data in backtesting mode.
134  // Also we want to make one call per second becasue of the API limit.
135  if (_backtestingFirstTimeCallOrLiveMode)
136  {
137  // Force the engine to wait at least 1000 ms between API calls.
138  IntrinioConfig.RateGate.WaitToProceed();
139 
140  // In backtesting mode, there is only one call at the beggining with all the data
141  _backtestingFirstTimeCallOrLiveMode = false || isLiveMode;
142  subscription = GetIntrinioSubscription(config, isLiveMode);
143  }
144  else
145  {
146  subscription = new SubscriptionDataSource("", SubscriptionTransportMedium.LocalFile);
147  }
148  return subscription;
149  }
150 
151  /// <summary>
152  /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method,
153  /// and returns a new instance of the object
154  /// each time it is called. The returned object is assumed to be time stamped in the config.ExchangeTimeZone.
155  /// </summary>
156  /// <param name="config">Subscription data config setup object</param>
157  /// <param name="line">Line of the source document</param>
158  /// <param name="date">Date of the requested data</param>
159  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
160  /// <returns>
161  /// Instance of the T:BaseData object generated by this line of the CSV
162  /// </returns>
163  public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
164  {
165  var obs = line.Split(',');
166  var time = DateTime.MinValue;
167  if (!DateTime.TryParseExact(obs[0], "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None,
168  out time)) return null;
169  var value = obs[1].ToDecimal();
170  return new IntrinioEconomicData
171  {
172  Symbol = config.Symbol,
173  Time = time,
174  EndTime = time + QuantConnect.Time.OneDay,
175  Value = value
176  };
177  }
178 
179  private static string GetStringForDataTransformation(IntrinioDataTransformation dataTransformation)
180  {
181  var item = "level";
182  switch (dataTransformation)
183  {
185  item = "change";
186  break;
187  case IntrinioDataTransformation.AnnualyRoc:
188  item = "yr_change";
189  break;
190  case IntrinioDataTransformation.CompoundedAnnualRoc:
191  item = "c_annual_roc";
192  break;
193  case IntrinioDataTransformation.AnnualyCCRoc:
194  item = "cc_annual_roc";
195  break;
196  case IntrinioDataTransformation.CCRoc:
197  item = "cc_roc";
198  break;
199  case IntrinioDataTransformation.Level:
200  item = "level";
201  break;
203  item = "log";
204  break;
206  item = "percent_change";
207  break;
208  case IntrinioDataTransformation.AnnualyPc:
209  item = "yr_percent_change";
210  break;
211  }
212  return item;
213  }
214 
215  private SubscriptionDataSource GetIntrinioSubscription(SubscriptionDataConfig config, bool isLiveMode)
216  {
217  // In Live mode, we only want the last observation, in backtesitng we need the data in ascending order.
218  var order = isLiveMode ? "desc" : "asc";
219  var item = GetStringForDataTransformation(_dataTransformation);
220  var url = $"{_baseUrl}identifier={config.Symbol.Value}&item={item}&sort_order={order}";
221  var byteKey = Encoding.ASCII.GetBytes($"{IntrinioConfig.User}:{IntrinioConfig.Password}");
222  var authorizationHeaders = new List<KeyValuePair<string, string>>
223  {
224  new KeyValuePair<string, string>("Authorization",
225  $"Basic ({Convert.ToBase64String(byteKey)})")
226  };
227 
228  return new SubscriptionDataSource(url, SubscriptionTransportMedium.RemoteFile, FileFormat.Csv,
229  authorizationHeaders);
230  }
231  }
232 }