Lean  $LEAN_TAG$
FxcmVolume.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.Globalization;
18 using System.IO;
19 using System.Linq;
20 using static QuantConnect.StringExtensions;
21 
23 {
24  /// <summary>
25  /// FXCM Real FOREX Volume and Transaction data from its clients base, available for the following pairs:
26  /// - EURUSD, USDJPY, GBPUSD, USDCHF, EURCHF, AUDUSD, USDCAD,
27  /// NZDUSD, EURGBP, EURJPY, GBPJPY, EURAUD, EURCAD, AUDJPY
28  /// FXCM only provides support for FX symbols which produced over 110 million average daily volume (ADV) during 2013.
29  /// This limit is imposed to ensure we do not highlight low volume/low ticket symbols in addition to other financial
30  /// reporting concerns.
31  /// </summary>
32  /// <seealso cref="QuantConnect.Data.BaseData" />
33  public class FxcmVolume : BaseData
34  {
35  /// <summary>
36  /// Auxiliary enum used to map the pair symbol into FXCM request code.
37  /// </summary>
38  private enum FxcmSymbolId
39  {
40  EURUSD = 1,
41  USDJPY = 2,
42  GBPUSD = 3,
43  USDCHF = 4,
44  EURCHF = 5,
45  AUDUSD = 6,
46  USDCAD = 7,
47  NZDUSD = 8,
48  EURGBP = 9,
49  EURJPY = 10,
50  GBPJPY = 11,
51  EURAUD = 14,
52  EURCAD = 15,
53  AUDJPY = 17
54  }
55 
56  /// <summary>
57  /// The request base URL.
58  /// </summary>
59  private readonly string _baseUrl = " http://marketsummary2.fxcorporate.com/ssisa/servlet?RT=SSI";
60 
61  /// <summary>
62  /// FXCM session id.
63  /// </summary>
64  private readonly string _sid = "quantconnect";
65 
66  /// <summary>
67  /// The columns index which should be added to obtain the transactions.
68  /// </summary>
69  private readonly long[] _transactionsIdx = { 27, 29, 31, 33 };
70 
71  /// <summary>
72  /// Integer representing client version.
73  /// </summary>
74  private readonly int _ver = 1;
75 
76  /// <summary>
77  /// The columns index which should be added to obtain the volume.
78  /// </summary>
79  private readonly int[] _volumeIdx = { 26, 28, 30, 32 };
80 
81  /// <summary>
82  /// Sum of opening and closing Transactions for the entire time interval.
83  /// </summary>
84  /// <value>
85  /// The transactions.
86  /// </value>
87  public int Transactions { get; set; }
88 
89  /// <summary>
90  /// Sum of opening and closing Volume for the entire time interval.
91  /// The volume measured in the QUOTE CURRENCY.
92  /// </summary>
93  /// <remarks>Please remember to convert this data to a common currency before making comparison between different pairs.</remarks>
94  public long Volume { get; set; }
95 
96  /// <summary>
97  /// Return the URL string source of the file. This will be converted to a stream
98  /// </summary>
99  /// <param name="config">Configuration object</param>
100  /// <param name="date">Date of this source file</param>
101  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
102  /// <returns>
103  /// String URL of source file.
104  /// </returns>
105  /// <exception cref="System.NotImplementedException">FOREX Volume data is not available in live mode, yet.</exception>
106  public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
107  {
108  var interval = GetIntervalFromResolution(config.Resolution);
109  var symbolId = GetFxcmIDFromSymbol(config.Symbol.Value.Split('_').First());
110 
111  if (isLiveMode)
112  {
113  var source = Invariant($"{_baseUrl}&ver={_ver}&sid={_sid}&interval={interval}&offerID={symbolId}");
114  return new SubscriptionDataSource(source, SubscriptionTransportMedium.Rest, FileFormat.Csv);
115  }
116  else
117  {
118  var source = GenerateZipFilePath(config, date);
119  return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile);
120  }
121  }
122 
123  /// <summary>
124  /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method,
125  /// and returns a new instance of the object
126  /// each time it is called. The returned object is assumed to be time stamped in the config.ExchangeTimeZone.
127  /// </summary>
128  /// <param name="config">Subscription data config setup object</param>
129  /// <param name="line">Line of the source document</param>
130  /// <param name="date">Date of the requested data</param>
131  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
132  /// <returns>
133  /// Instance of the T:BaseData object generated by this line of the CSV
134  /// </returns>
135  public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
136  {
137  var fxcmVolume = new FxcmVolume { DataType = MarketDataType.Base, Symbol = config.Symbol };
138  if (isLiveMode)
139  {
140  try
141  {
142  var obs = line.Split('\n')[2].Split(';');
143  var stringDate = obs[0].Substring(startIndex: 3);
144  fxcmVolume.Time = DateTime.ParseExact(stringDate, "yyyyMMddHHmm", DateTimeFormatInfo.InvariantInfo);
145  fxcmVolume.Volume = _volumeIdx.Select(x => Parse.Long(obs[x])).Sum();
146  fxcmVolume.Transactions = _transactionsIdx.Select(x => Parse.Int(obs[x])).Sum();
147  fxcmVolume.Value = fxcmVolume.Volume;
148  }
149  catch (Exception exception)
150  {
151  Logging.Log.Error($"Invalid data. Line: {line}. Exception: {exception.Message}");
152  return null;
153  }
154  }
155  else
156  {
157  var obs = line.Split(',');
158  if (config.Resolution == Resolution.Minute)
159  {
160  fxcmVolume.Time = date.Date.AddMilliseconds(Parse.Int(obs[0]));
161  }
162  else
163  {
164  fxcmVolume.Time = DateTime.ParseExact(obs[0], "yyyyMMdd HH:mm", CultureInfo.InvariantCulture);
165  }
166  fxcmVolume.Volume = Parse.Long(obs[1]);
167  fxcmVolume.Transactions = obs[2].ConvertInvariant<int>();
168  fxcmVolume.Value = fxcmVolume.Volume;
169  }
170  return fxcmVolume;
171  }
172 
173  private static string GenerateZipFilePath(SubscriptionDataConfig config, DateTime date)
174  {
175  var source = Path.Combine(new[] { Globals.DataFolder, "forex", "fxcm", config.Resolution.ToLower() });
176  string filename;
177 
178  var symbol = config.Symbol.Value.Split('_').First().ToLowerInvariant();
179  if (config.Resolution == Resolution.Minute)
180  {
181  filename = Invariant($"{date:yyyyMMdd}_volume.zip");
182  source = Path.Combine(source, symbol, filename);
183  }
184  else
185  {
186  filename = $"{symbol}_volume.zip";
187  source = Path.Combine(source, filename);
188  }
189  return source;
190  }
191 
192  /// <summary>
193  /// Gets the FXCM identifier from a FOREX pair ticker.
194  /// </summary>
195  /// <param name="ticker">The pair ticker.</param>
196  /// <returns></returns>
197  /// <exception cref="System.ArgumentException">Volume data is not available for the selected ticker. - ticker</exception>
198  private int GetFxcmIDFromSymbol(string ticker)
199  {
200  int symbolId;
201  try
202  {
203  symbolId = (int)Enum.Parse(typeof(FxcmSymbolId), ticker);
204  }
205  catch (ArgumentException)
206  {
207  throw new ArgumentOutOfRangeException(nameof(ticker), ticker,
208  "Volume data is not available for the selected ticker.");
209  }
210  return symbolId;
211  }
212 
213  /// <summary>
214  /// Gets the string interval representation from the resolution.
215  /// </summary>
216  /// <param name="resolution">The requested resolution.</param>
217  /// <returns></returns>
218  /// <exception cref="System.ArgumentOutOfRangeException">
219  /// resolution - tick or second resolution are not supported for Forex
220  /// Volume.
221  /// </exception>
222  private string GetIntervalFromResolution(Resolution resolution)
223  {
224  string interval;
225  switch (resolution)
226  {
227  case Resolution.Minute:
228  interval = "M1";
229  break;
230 
231  case Resolution.Hour:
232  interval = "H1";
233  break;
234 
235  case Resolution.Daily:
236  interval = "D1";
237  break;
238 
239  default:
240  throw new ArgumentOutOfRangeException(nameof(resolution), resolution,
241  "Tick or second resolution are not supported for Forex Volume. Available resolutions are Minute, Hour and Daily.");
242  }
243  return interval;
244  }
245  }
246 }