/*
* 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;
}
}
}