/* * 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.Globalization; using System.IO; using System.Linq; using static QuantConnect.StringExtensions; namespace QuantConnect.Data.Custom { /// /// FXCM Real FOREX Volume and Transaction data from its clients base, available for the following pairs: /// - EURUSD, USDJPY, GBPUSD, USDCHF, EURCHF, AUDUSD, USDCAD, /// NZDUSD, EURGBP, EURJPY, GBPJPY, EURAUD, EURCAD, AUDJPY /// FXCM only provides support for FX symbols which produced over 110 million average daily volume (ADV) during 2013. /// This limit is imposed to ensure we do not highlight low volume/low ticket symbols in addition to other financial /// reporting concerns. /// /// public class FxcmVolume : BaseData { /// /// Auxiliary enum used to map the pair symbol into FXCM request code. /// private enum FxcmSymbolId { EURUSD = 1, USDJPY = 2, GBPUSD = 3, USDCHF = 4, EURCHF = 5, AUDUSD = 6, USDCAD = 7, NZDUSD = 8, EURGBP = 9, EURJPY = 10, GBPJPY = 11, EURAUD = 14, EURCAD = 15, AUDJPY = 17 } /// /// The request base URL. /// private readonly string _baseUrl = " http://marketsummary2.fxcorporate.com/ssisa/servlet?RT=SSI"; /// /// FXCM session id. /// private readonly string _sid = "quantconnect"; /// /// The columns index which should be added to obtain the transactions. /// private readonly long[] _transactionsIdx = { 27, 29, 31, 33 }; /// /// Integer representing client version. /// private readonly int _ver = 1; /// /// The columns index which should be added to obtain the volume. /// private readonly int[] _volumeIdx = { 26, 28, 30, 32 }; /// /// Sum of opening and closing Transactions for the entire time interval. /// /// /// The transactions. /// public int Transactions { get; set; } /// /// Sum of opening and closing Volume for the entire time interval. /// The volume measured in the QUOTE CURRENCY. /// /// Please remember to convert this data to a common currency before making comparison between different pairs. public long Volume { get; set; } /// /// Return the URL string source of the file. This will be converted to a stream /// /// Configuration object /// Date of this source file /// true if we're in live mode, false for backtesting mode /// /// String URL of source file. /// /// FOREX Volume data is not available in live mode, yet. public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode) { var interval = GetIntervalFromResolution(config.Resolution); var symbolId = GetFxcmIDFromSymbol(config.Symbol.Value.Split('_').First()); if (isLiveMode) { var source = Invariant($"{_baseUrl}&ver={_ver}&sid={_sid}&interval={interval}&offerID={symbolId}"); return new SubscriptionDataSource(source, SubscriptionTransportMedium.Rest, FileFormat.Csv); } else { var source = GenerateZipFilePath(config, date); return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile); } } /// /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, /// and returns a new instance of the object /// each time it is called. The returned object is assumed to be time stamped in the config.ExchangeTimeZone. /// /// Subscription data config setup object /// Line of the source document /// Date of the requested data /// true if we're in live mode, false for backtesting mode /// /// Instance of the T:BaseData object generated by this line of the CSV /// public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode) { var fxcmVolume = new FxcmVolume { DataType = MarketDataType.Base, Symbol = config.Symbol }; if (isLiveMode) { try { var obs = line.Split('\n')[2].Split(';'); var stringDate = obs[0].Substring(startIndex: 3); fxcmVolume.Time = DateTime.ParseExact(stringDate, "yyyyMMddHHmm", DateTimeFormatInfo.InvariantInfo); fxcmVolume.Volume = _volumeIdx.Select(x => Parse.Long(obs[x])).Sum(); fxcmVolume.Transactions = _transactionsIdx.Select(x => Parse.Int(obs[x])).Sum(); fxcmVolume.Value = fxcmVolume.Volume; } catch (Exception exception) { Logging.Log.Error($"Invalid data. Line: {line}. Exception: {exception.Message}"); return null; } } else { var obs = line.Split(','); if (config.Resolution == Resolution.Minute) { fxcmVolume.Time = date.Date.AddMilliseconds(Parse.Int(obs[0])); } else { fxcmVolume.Time = DateTime.ParseExact(obs[0], "yyyyMMdd HH:mm", CultureInfo.InvariantCulture); } fxcmVolume.Volume = Parse.Long(obs[1]); fxcmVolume.Transactions = obs[2].ConvertInvariant(); fxcmVolume.Value = fxcmVolume.Volume; } return fxcmVolume; } private static string GenerateZipFilePath(SubscriptionDataConfig config, DateTime date) { var source = Path.Combine(new[] { Globals.DataFolder, "forex", "fxcm", config.Resolution.ToLower() }); string filename; var symbol = config.Symbol.Value.Split('_').First().ToLowerInvariant(); if (config.Resolution == Resolution.Minute) { filename = Invariant($"{date:yyyyMMdd}_volume.zip"); source = Path.Combine(source, symbol, filename); } else { filename = $"{symbol}_volume.zip"; source = Path.Combine(source, filename); } return source; } /// /// Gets the FXCM identifier from a FOREX pair ticker. /// /// The pair ticker. /// /// Volume data is not available for the selected ticker. - ticker private int GetFxcmIDFromSymbol(string ticker) { int symbolId; try { symbolId = (int)Enum.Parse(typeof(FxcmSymbolId), ticker); } catch (ArgumentException) { throw new ArgumentOutOfRangeException(nameof(ticker), ticker, "Volume data is not available for the selected ticker."); } return symbolId; } /// /// Gets the string interval representation from the resolution. /// /// The requested resolution. /// /// /// resolution - tick or second resolution are not supported for Forex /// Volume. /// private string GetIntervalFromResolution(Resolution resolution) { string interval; switch (resolution) { case Resolution.Minute: interval = "M1"; break; case Resolution.Hour: interval = "H1"; break; case Resolution.Daily: interval = "D1"; break; default: throw new ArgumentOutOfRangeException(nameof(resolution), resolution, "Tick or second resolution are not supported for Forex Volume. Available resolutions are Minute, Hour and Daily."); } return interval; } } }