Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d94a1d09a4 | ||
|
|
2c843cae9e | ||
|
|
039fdf7e4a | ||
|
|
2c63546c37 | ||
|
|
58682e1bbd | ||
|
|
d11a375fdb |
@@ -79,19 +79,19 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
// things like manually added, auto added, internal, and any other boolean state we need to track against a single security)
|
||||
throw new Exception("The underlying equity data should NEVER be removed in this algorithm because it was manually added");
|
||||
}
|
||||
if (_expectedSecurities.AreDifferent(LinqExtensions.ToHashSet(Securities.Keys)))
|
||||
if (_expectedSecurities.AreDifferent(Securities.Keys.ToHashSet()))
|
||||
{
|
||||
var expected = string.Join(Environment.NewLine, _expectedSecurities.OrderBy(s => s.ToString()));
|
||||
var actual = string.Join(Environment.NewLine, Securities.Keys.OrderBy(s => s.ToString()));
|
||||
throw new Exception($"{Time}:: Detected differences in expected and actual securities{Environment.NewLine}Expected:{Environment.NewLine}{expected}{Environment.NewLine}Actual:{Environment.NewLine}{actual}");
|
||||
}
|
||||
if (_expectedUniverses.AreDifferent(LinqExtensions.ToHashSet(UniverseManager.Keys)))
|
||||
if (_expectedUniverses.AreDifferent(Securities.Keys.ToHashSet()))
|
||||
{
|
||||
var expected = string.Join(Environment.NewLine, _expectedUniverses.OrderBy(s => s.ToString()));
|
||||
var actual = string.Join(Environment.NewLine, UniverseManager.Keys.OrderBy(s => s.ToString()));
|
||||
throw new Exception($"{Time}:: Detected differences in expected and actual universes{Environment.NewLine}Expected:{Environment.NewLine}{expected}{Environment.NewLine}Actual:{Environment.NewLine}{actual}");
|
||||
}
|
||||
if (_expectedData.AreDifferent(LinqExtensions.ToHashSet(data.Keys)))
|
||||
if (_expectedData.AreDifferent(Securities.Keys.ToHashSet()))
|
||||
{
|
||||
var expected = string.Join(Environment.NewLine, _expectedData.OrderBy(s => s.ToString()));
|
||||
var actual = string.Join(Environment.NewLine, data.Keys.OrderBy(s => s.ToString()));
|
||||
@@ -183,7 +183,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
if (changes.RemovedSecurities
|
||||
.Where(x => x.Symbol.SecurityType == SecurityType.Option)
|
||||
.ToHashSet(s => s.Symbol)
|
||||
.AreDifferent(LinqExtensions.ToHashSet(_expectedContracts)))
|
||||
.AreDifferent(_expectedContracts.ToHashSet()))
|
||||
{
|
||||
throw new Exception("Expected removed securities to equal expected contracts added");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Algorithm.CSharp</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Algorithm.CSharp</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
@@ -63,10 +63,6 @@
|
||||
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||
<PackageReference Include="R.NET" Version="1.9.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Algorithm.Framework</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Algorithm.Framework</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
@@ -54,10 +54,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Algorithm.Python</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Algorithm.Python</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
|
||||
@@ -45,8 +45,8 @@ using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Algorithm.Selection;
|
||||
using QuantConnect.Securities.Index;
|
||||
using QuantConnect.Storage;
|
||||
using Index = QuantConnect.Securities.Index.Index;
|
||||
|
||||
namespace QuantConnect.Algorithm
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<RootNamespace>QuantConnect.Algorithm</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Algorithm</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<LangVersion>6</LangVersion>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
@@ -64,10 +64,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.AlgorithmFactory</RootNamespace>
|
||||
<AssemblyName>QuantConnect.AlgorithmFactory</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
@@ -48,10 +48,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<RootNamespace>QuantConnect.Api</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Api</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<LangVersion>6</LangVersion>
|
||||
@@ -58,12 +58,6 @@
|
||||
<PackageReference Include="RestSharp" Version="106.6.10" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using com.fxcm.fix;
|
||||
using com.fxcm.fix.pretrade;
|
||||
using NodaTime;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Brokerages.Fxcm
|
||||
{
|
||||
/// <summary>
|
||||
/// FXCM brokerage - implementation of IDataQueueHandler interface
|
||||
/// </summary>
|
||||
public partial class FxcmBrokerage
|
||||
{
|
||||
#region IDataQueueHandler implementation
|
||||
|
||||
/// <summary>
|
||||
/// Sets the job we're subscribing for
|
||||
/// </summary>
|
||||
/// <param name="job">Job we're subscribing for</param>
|
||||
public void SetJob(LiveNodePacket job)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to the specified configuration
|
||||
/// </summary>
|
||||
/// <param name="dataConfig">defines the parameters to subscribe to a data feed</param>
|
||||
/// <param name="newDataAvailableHandler">handler to be fired on new data available</param>
|
||||
/// <returns>The new enumerator for this subscription request</returns>
|
||||
public IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
|
||||
{
|
||||
if (!CanSubscribe(dataConfig.Symbol))
|
||||
{
|
||||
return Enumerable.Empty<BaseData>().GetEnumerator();
|
||||
}
|
||||
|
||||
var enumerator = _aggregator.Add(dataConfig, newDataAvailableHandler);
|
||||
_subscriptionManager.Subscribe(dataConfig);
|
||||
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified symbols to the subscription
|
||||
/// </summary>
|
||||
/// <param name="symbols">The symbols to be added keyed by SecurityType</param>
|
||||
private bool Subscribe(IEnumerable<Symbol> symbols)
|
||||
{
|
||||
var request = new MarketDataRequest();
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
TradingSecurity fxcmSecurity;
|
||||
if (_fxcmInstruments.TryGetValue(_symbolMapper.GetBrokerageSymbol(symbol), out fxcmSecurity))
|
||||
{
|
||||
request.addRelatedSymbol(fxcmSecurity);
|
||||
|
||||
// cache exchange time zone for symbol
|
||||
DateTimeZone exchangeTimeZone;
|
||||
if (!_symbolExchangeTimeZones.TryGetValue(symbol, out exchangeTimeZone))
|
||||
{
|
||||
exchangeTimeZone = MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.FXCM, symbol, symbol.SecurityType).TimeZone;
|
||||
_symbolExchangeTimeZones.Add(symbol, exchangeTimeZone);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
request.setSubscriptionRequestType(SubscriptionRequestTypeFactory.SUBSCRIBE);
|
||||
request.setMDEntryTypeSet(MarketDataRequest.MDENTRYTYPESET_ALL);
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_gateway.sendMessage(request);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified configuration
|
||||
/// </summary>
|
||||
/// <param name="dataConfig">Subscription config to be removed</param>
|
||||
public void Unsubscribe(SubscriptionDataConfig dataConfig)
|
||||
{
|
||||
_subscriptionManager.Unsubscribe(dataConfig);
|
||||
_aggregator.Remove(dataConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified symbols to the subscription
|
||||
/// </summary>
|
||||
/// <param name="symbols">The symbols to be removed keyed by SecurityType</param>
|
||||
private bool Unsubscribe(IEnumerable<Symbol> symbols)
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.Unsubscribe(): {0}", string.Join(",", symbols));
|
||||
|
||||
var request = new MarketDataRequest();
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
request.addRelatedSymbol(_fxcmInstruments[_symbolMapper.GetBrokerageSymbol(symbol)]);
|
||||
}
|
||||
request.setSubscriptionRequestType(SubscriptionRequestTypeFactory.UNSUBSCRIBE);
|
||||
request.setMDEntryTypeSet(MarketDataRequest.MDENTRYTYPESET_ALL);
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_gateway.sendMessage(request);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this brokerage supports the specified symbol
|
||||
/// </summary>
|
||||
private static bool CanSubscribe(Symbol symbol)
|
||||
{
|
||||
// ignore unsupported security types
|
||||
if (symbol.ID.SecurityType != SecurityType.Forex && symbol.ID.SecurityType != SecurityType.Cfd)
|
||||
return false;
|
||||
|
||||
// ignore universe symbols
|
||||
return !symbol.Value.Contains("-UNIVERSE-");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,548 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using com.fxcm.external.api.transport;
|
||||
using com.fxcm.fix;
|
||||
using com.fxcm.fix.admin;
|
||||
using com.fxcm.fix.other;
|
||||
using com.fxcm.fix.posttrade;
|
||||
using com.fxcm.fix.pretrade;
|
||||
using com.fxcm.fix.trade;
|
||||
using com.fxcm.messaging;
|
||||
using NodaTime;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Orders.Fees;
|
||||
|
||||
namespace QuantConnect.Brokerages.Fxcm
|
||||
{
|
||||
/// <summary>
|
||||
/// FXCM brokerage - Java API related functions and interface implementations
|
||||
/// </summary>
|
||||
public partial class FxcmBrokerage
|
||||
{
|
||||
private IGateway _gateway;
|
||||
|
||||
private readonly object _locker = new object();
|
||||
private string _currentRequest;
|
||||
private const int ResponseTimeout = 5000;
|
||||
private bool _isOrderUpdateOrCancelRejected;
|
||||
private bool _isOrderSubmitRejected;
|
||||
|
||||
private readonly Dictionary<string, TradingSecurity> _fxcmInstruments = new Dictionary<string, TradingSecurity>();
|
||||
private readonly Dictionary<string, CollateralReport> _accounts = new Dictionary<string, CollateralReport>();
|
||||
private readonly Dictionary<string, MarketDataSnapshot> _rates = new Dictionary<string, MarketDataSnapshot>();
|
||||
|
||||
private readonly Dictionary<string, ExecutionReport> _openOrders = new Dictionary<string, ExecutionReport>();
|
||||
// Map key: fxcmPositionId (can have multiple positions for the same symbol)
|
||||
private readonly Dictionary<string, PositionReport> _openPositions = new Dictionary<string, PositionReport>();
|
||||
|
||||
private readonly Dictionary<string, Order> _mapRequestsToOrders = new Dictionary<string, Order>();
|
||||
private readonly Dictionary<string, Order> _mapFxcmOrderIdsToOrders = new Dictionary<string, Order>();
|
||||
private readonly Dictionary<string, AutoResetEvent> _mapRequestsToAutoResetEvents = new Dictionary<string, AutoResetEvent>();
|
||||
private readonly HashSet<string> _pendingHistoryRequests = new HashSet<string>();
|
||||
|
||||
private void LoadInstruments()
|
||||
{
|
||||
// Note: requestTradingSessionStatus() MUST be called just after login
|
||||
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.requestTradingSessionStatus();
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.LoadInstruments(): Operation took longer than " +
|
||||
$"{((decimal)ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
}
|
||||
|
||||
private void LoadAccounts()
|
||||
{
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.requestAccounts();
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.LoadAccounts(): Operation took longer than " +
|
||||
$"{((decimal)ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
|
||||
if (!_accounts.ContainsKey(_accountId))
|
||||
throw new ArgumentException("FxcmBrokerage.LoadAccounts(): The account id is invalid: " + _accountId);
|
||||
|
||||
// Hedging MUST be disabled on the account
|
||||
if (_accounts[_accountId].getParties().getFXCMPositionMaintenance() == "Y")
|
||||
{
|
||||
throw new NotSupportedException("FxcmBrokerage.LoadAccounts(): The Lean engine does not support accounts with Hedging enabled. " +
|
||||
"Please contact FXCM Active Trader support to disable Hedging. They can be reached at https://www.fxcm.com/markets/contact-client-support/ through their Live Chat or Phone."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadOpenOrders()
|
||||
{
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.requestOpenOrders(null);
|
||||
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.LoadOpenOrders(): Operation took longer than " +
|
||||
$"{((decimal)ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
}
|
||||
|
||||
private void LoadOpenPositions()
|
||||
{
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _terminal.Equals("Demo") ?
|
||||
_gateway.requestOpenPositions(_accountId.ConvertInvariant<long>()) :
|
||||
_gateway.requestOpenPositions(_accountId);
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.LoadOpenPositions(): Operation took longer than " +
|
||||
$"{((decimal)ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides as public access to this data without requiring consumers to reference
|
||||
/// IKVM libraries
|
||||
/// </summary>
|
||||
public List<Tick> GetBidAndAsk(List<string> fxcmSymbols)
|
||||
{
|
||||
return GetQuotes(fxcmSymbols).Select(x => new Tick
|
||||
{
|
||||
Symbol = _symbolMapper.GetLeanSymbol(
|
||||
x.getInstrument().getSymbol(),
|
||||
_symbolMapper.GetBrokerageSecurityType(x.getInstrument().getSymbol()),
|
||||
Market.FXCM),
|
||||
BidPrice = (decimal)x.getBidClose(),
|
||||
AskPrice = (decimal)x.getAskClose()
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quotes for the symbol
|
||||
/// </summary>
|
||||
private List<MarketDataSnapshot> GetQuotes(List<string> fxcmSymbols)
|
||||
{
|
||||
// get current quotes for the instrument
|
||||
var request = new MarketDataRequest();
|
||||
request.setMDEntryTypeSet(MarketDataRequest.MDENTRYTYPESET_ALL);
|
||||
request.setSubscriptionRequestType(SubscriptionRequestTypeFactory.SNAPSHOT);
|
||||
foreach (var fxcmSymbol in fxcmSymbols)
|
||||
{
|
||||
request.addRelatedSymbol(_fxcmInstruments[fxcmSymbol]);
|
||||
}
|
||||
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.sendMessage(request);
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.GetQuotes(): Operation took longer than " +
|
||||
$"{((decimal)ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
|
||||
return _rates.Where(x => fxcmSymbols.Contains(x.Key)).Select(x => x.Value).ToList();
|
||||
}
|
||||
|
||||
#region IGenericMessageListener implementation
|
||||
|
||||
/// <summary>
|
||||
/// Receives generic messages from the FXCM API
|
||||
/// </summary>
|
||||
/// <param name="message">Generic message received</param>
|
||||
public void messageArrived(ITransportable message)
|
||||
{
|
||||
// Dispatch message to specific handler
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (message is TradingSessionStatus)
|
||||
OnTradingSessionStatus((TradingSessionStatus)message);
|
||||
|
||||
else if (message is CollateralReport)
|
||||
OnCollateralReport((CollateralReport)message);
|
||||
|
||||
else if (message is MarketDataSnapshot)
|
||||
OnMarketDataSnapshot((MarketDataSnapshot)message);
|
||||
|
||||
else if (message is ExecutionReport)
|
||||
OnExecutionReport((ExecutionReport)message);
|
||||
|
||||
else if (message is RequestForPositionsAck)
|
||||
OnRequestForPositionsAck((RequestForPositionsAck)message);
|
||||
|
||||
else if (message is PositionReport)
|
||||
OnPositionReport((PositionReport)message);
|
||||
|
||||
else if (message is OrderCancelReject)
|
||||
OnOrderCancelReject((OrderCancelReject)message);
|
||||
|
||||
else if (message is UserResponse || message is CollateralInquiryAck || message is Logout ||
|
||||
message is MarketDataRequestReject || message is BusinessMessageReject || message is SecurityStatus)
|
||||
{
|
||||
// Unused messages, no handler needed
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Should never get here, if it does log and ignore message
|
||||
// New messages added in future api updates should be added to the unused list above
|
||||
Log.Trace("FxcmBrokerage.messageArrived(): Unknown message: {0}", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TradingSessionStatus message handler
|
||||
/// </summary>
|
||||
private void OnTradingSessionStatus(TradingSessionStatus message)
|
||||
{
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
// load instrument list into a dictionary
|
||||
var securities = message.getSecurities();
|
||||
while (securities.hasMoreElements())
|
||||
{
|
||||
var security = (TradingSecurity)securities.nextElement();
|
||||
_fxcmInstruments[security.getSymbol()] = security;
|
||||
}
|
||||
|
||||
// get account base currency
|
||||
AccountBaseCurrency = message.getParameter("BASE_CRNCY").getValue();
|
||||
|
||||
_mapRequestsToAutoResetEvents[_currentRequest].Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CollateralReport message handler
|
||||
/// </summary>
|
||||
private void OnCollateralReport(CollateralReport message)
|
||||
{
|
||||
// add the trading account to the account list
|
||||
_accounts[message.getAccount()] = message;
|
||||
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
// set the state of the request to be completed only if this is the last collateral report requested
|
||||
if (message.isLastRptRequested())
|
||||
{
|
||||
_mapRequestsToAutoResetEvents[_currentRequest].Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MarketDataSnapshot message handler
|
||||
/// </summary>
|
||||
private void OnMarketDataSnapshot(MarketDataSnapshot message)
|
||||
{
|
||||
var instrument = message.getInstrument();
|
||||
var securityType = _symbolMapper.GetBrokerageSecurityType(instrument.getSymbol());
|
||||
var symbol = _symbolMapper.GetLeanSymbol(instrument.getSymbol(), securityType, Market.FXCM);
|
||||
|
||||
var isHistoryResponse = _pendingHistoryRequests.Contains(message.getRequestID());
|
||||
if (isHistoryResponse)
|
||||
{
|
||||
var time = FromJavaDate(message.getDate().toDate());
|
||||
|
||||
// history timestamps must be in exchange time zone
|
||||
DateTimeZone exchangeTimeZone;
|
||||
if (_symbolExchangeTimeZones.TryGetValue(symbol, out exchangeTimeZone))
|
||||
{
|
||||
time = time.ConvertFromUtc(exchangeTimeZone);
|
||||
}
|
||||
|
||||
// append ticks/bars to history
|
||||
if (message.getFXCMTimingInterval() == FXCMTimingIntervalFactory.TICK)
|
||||
{
|
||||
var bidPrice = Convert.ToDecimal(message.getBidClose());
|
||||
var askPrice = Convert.ToDecimal(message.getAskClose());
|
||||
var tick = new Tick(time, symbol, bidPrice, askPrice);
|
||||
|
||||
_lastHistoryChunk.Add(tick);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bar = new QuoteBar(
|
||||
time,
|
||||
symbol,
|
||||
new Bar(
|
||||
Convert.ToDecimal(message.getBidOpen()),
|
||||
Convert.ToDecimal(message.getBidHigh()),
|
||||
Convert.ToDecimal(message.getBidLow()),
|
||||
Convert.ToDecimal(message.getBidClose())
|
||||
),
|
||||
0,
|
||||
new Bar(
|
||||
Convert.ToDecimal(message.getAskOpen()),
|
||||
Convert.ToDecimal(message.getAskHigh()),
|
||||
Convert.ToDecimal(message.getAskLow()),
|
||||
Convert.ToDecimal(message.getAskClose())
|
||||
),
|
||||
0);
|
||||
|
||||
_lastHistoryChunk.Add(bar);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// update the current prices for the instrument
|
||||
_rates[instrument.getSymbol()] = message;
|
||||
|
||||
// if instrument is subscribed, add ticks to list
|
||||
if (_subscriptionManager.IsSubscribed(symbol, TickType.Quote))
|
||||
{
|
||||
// For some unknown reason, messages returned by SubscriptionRequestTypeFactory.SUBSCRIBE
|
||||
// have message.getDate() rounded to the second, so we use message.getMakingTime() instead
|
||||
var time = FromJavaDate(new java.util.Date(message.getMakingTime()));
|
||||
|
||||
// live ticks timestamps must be in exchange time zone
|
||||
DateTimeZone exchangeTimeZone;
|
||||
if (_symbolExchangeTimeZones.TryGetValue(symbol, out exchangeTimeZone))
|
||||
{
|
||||
time = time.ConvertFromUtc(exchangeTimeZone);
|
||||
}
|
||||
|
||||
var bidPrice = Convert.ToDecimal(message.getBidClose());
|
||||
var askPrice = Convert.ToDecimal(message.getAskClose());
|
||||
var tick = new Tick(time, symbol, bidPrice, askPrice);
|
||||
|
||||
_aggregator.Update(tick);
|
||||
}
|
||||
}
|
||||
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
if (message.getFXCMContinuousFlag() == IFixValueDefs.__Fields.FXCMCONTINUOUS_END)
|
||||
{
|
||||
_mapRequestsToAutoResetEvents[_currentRequest].Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
|
||||
if (isHistoryResponse) _pendingHistoryRequests.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ExecutionReport message handler
|
||||
/// </summary>
|
||||
private void OnExecutionReport(ExecutionReport message)
|
||||
{
|
||||
var orderId = message.getOrderID();
|
||||
var orderStatus = message.getFXCMOrdStatus();
|
||||
|
||||
if (orderId != "NONE" && message.getAccount() == _accountId)
|
||||
{
|
||||
if (_openOrders.ContainsKey(orderId) && OrderIsClosed(orderStatus.getCode()))
|
||||
{
|
||||
_openOrders.Remove(orderId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_openOrders[orderId] = message;
|
||||
}
|
||||
|
||||
Order order;
|
||||
if (_mapFxcmOrderIdsToOrders.TryGetValue(orderId, out order))
|
||||
{
|
||||
// existing order
|
||||
if (!OrderIsBeingProcessed(orderStatus.getCode()))
|
||||
{
|
||||
var status = ConvertOrderStatus(orderStatus);
|
||||
|
||||
int id;
|
||||
// if we get a Submitted status and we had placed an order update, this new event is flagged as an update
|
||||
var isUpdate = status == OrderStatus.Submitted && _orderUpdates.TryRemove(order.Id, out id);
|
||||
var security = _securityProvider.GetSecurity(order.Symbol);
|
||||
order.PriceCurrency = security.SymbolProperties.QuoteCurrency;
|
||||
|
||||
var orderEvent = new OrderEvent(order,
|
||||
DateTime.UtcNow,
|
||||
OrderFee.Zero)
|
||||
{
|
||||
Status = isUpdate ? OrderStatus.UpdateSubmitted : status,
|
||||
FillPrice = Convert.ToDecimal(message.getPrice()),
|
||||
FillQuantity = Convert.ToInt32(message.getSide() == SideFactory.BUY ? message.getLastQty() : -message.getLastQty()),
|
||||
};
|
||||
|
||||
// we're catching the first fill so we apply the fees only once
|
||||
if ((int)message.getCumQty() == (int)message.getLastQty() && message.getLastQty() > 0)
|
||||
{
|
||||
orderEvent.OrderFee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, order));
|
||||
}
|
||||
|
||||
_orderEventQueue.Enqueue(orderEvent);
|
||||
}
|
||||
}
|
||||
else if (_mapRequestsToOrders.TryGetValue(message.getRequestID(), out order))
|
||||
{
|
||||
_mapFxcmOrderIdsToOrders[orderId] = order;
|
||||
order.BrokerId.Add(orderId);
|
||||
order.PriceCurrency = _securityProvider.GetSecurity(order.Symbol).SymbolProperties.QuoteCurrency;
|
||||
|
||||
// new order
|
||||
var orderEvent = new OrderEvent(order,
|
||||
DateTime.UtcNow,
|
||||
OrderFee.Zero)
|
||||
{
|
||||
Status = ConvertOrderStatus(orderStatus)
|
||||
};
|
||||
|
||||
_orderEventQueue.Enqueue(orderEvent);
|
||||
}
|
||||
}
|
||||
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
if (message.isLastRptRequested())
|
||||
{
|
||||
if (orderId == "NONE" && orderStatus.getCode() == IFixValueDefs.__Fields.FXCMORDSTATUS_REJECTED)
|
||||
{
|
||||
if (message.getSide() != SideFactory.UNDISCLOSED)
|
||||
{
|
||||
var messageText = message.getFXCMErrorDetails().Replace("\n", "");
|
||||
Log.Trace("FxcmBrokerage.OnExecutionReport(): " + messageText);
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderSubmitReject", messageText));
|
||||
}
|
||||
|
||||
_isOrderSubmitRejected = true;
|
||||
}
|
||||
|
||||
AutoResetEvent autoResetEvent;
|
||||
if (_mapRequestsToAutoResetEvents.TryGetValue(_currentRequest, out autoResetEvent))
|
||||
{
|
||||
autoResetEvent.Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RequestForPositionsAck message handler
|
||||
/// </summary>
|
||||
private void OnRequestForPositionsAck(RequestForPositionsAck message)
|
||||
{
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
if (message.getTotalNumPosReports() == 0)
|
||||
{
|
||||
_mapRequestsToAutoResetEvents[_currentRequest].Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PositionReport message handler
|
||||
/// </summary>
|
||||
private void OnPositionReport(PositionReport message)
|
||||
{
|
||||
if (message.getAccount() == _accountId)
|
||||
{
|
||||
var fxcmPositionId = message.getFXCMPosID();
|
||||
if (_openPositions.ContainsKey(fxcmPositionId) && message is ClosedPositionReport)
|
||||
{
|
||||
_openPositions.Remove(fxcmPositionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_openPositions[fxcmPositionId] = message;
|
||||
}
|
||||
}
|
||||
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
AutoResetEvent autoResetEvent;
|
||||
if (message.isLastRptRequested() && _mapRequestsToAutoResetEvents.TryGetValue(_currentRequest, out autoResetEvent))
|
||||
{
|
||||
autoResetEvent.Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OrderCancelReject message handler
|
||||
/// </summary>
|
||||
private void OnOrderCancelReject(OrderCancelReject message)
|
||||
{
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
var messageText = message.getFXCMErrorDetails().Replace("\n", "");
|
||||
Log.Trace("FxcmBrokerage.OnOrderCancelReject(): " + messageText);
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderUpdateOrCancelReject", messageText));
|
||||
|
||||
_isOrderUpdateOrCancelRejected = true;
|
||||
|
||||
_mapRequestsToAutoResetEvents[_currentRequest].Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IStatusMessageListener implementation
|
||||
|
||||
/// <summary>
|
||||
/// Receives status messages from the FXCM API
|
||||
/// </summary>
|
||||
/// <param name="message">Status message received</param>
|
||||
public void messageArrived(ISessionStatus message)
|
||||
{
|
||||
switch (message.getStatusCode())
|
||||
{
|
||||
case ISessionStatus.__Fields.STATUSCODE_READY:
|
||||
lock (_lockerConnectionMonitor)
|
||||
{
|
||||
_lastReadyMessageTime = DateTime.UtcNow;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
/*
|
||||
* 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 com.fxcm.entity;
|
||||
using com.fxcm.fix;
|
||||
using com.fxcm.fix.posttrade;
|
||||
using com.fxcm.fix.trade;
|
||||
using com.sun.rowset;
|
||||
using java.util;
|
||||
using NodaTime;
|
||||
using QuantConnect.Orders;
|
||||
|
||||
namespace QuantConnect.Brokerages.Fxcm
|
||||
{
|
||||
/// <summary>
|
||||
/// FXCM brokerage - private helper functions
|
||||
/// </summary>
|
||||
public partial class FxcmBrokerage
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an FXCM order to a QuantConnect order.
|
||||
/// </summary>
|
||||
/// <param name="fxcmOrder">The FXCM order</param>
|
||||
private Order ConvertOrder(ExecutionReport fxcmOrder)
|
||||
{
|
||||
Order order;
|
||||
|
||||
if (fxcmOrder.getOrdType() == OrdTypeFactory.LIMIT)
|
||||
{
|
||||
order = new LimitOrder
|
||||
{
|
||||
LimitPrice = Convert.ToDecimal(fxcmOrder.getPrice())
|
||||
};
|
||||
}
|
||||
|
||||
else if (fxcmOrder.getOrdType() == OrdTypeFactory.MARKET)
|
||||
{
|
||||
order = new MarketOrder();
|
||||
}
|
||||
|
||||
else if (fxcmOrder.getOrdType() == OrdTypeFactory.STOP)
|
||||
{
|
||||
order = new StopMarketOrder
|
||||
{
|
||||
StopPrice = Convert.ToDecimal(fxcmOrder.getPrice())
|
||||
};
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("FxcmBrokerage.ConvertOrder(): The FXCM order type " + fxcmOrder.getOrdType() + " is not supported.");
|
||||
}
|
||||
|
||||
var securityType = _symbolMapper.GetBrokerageSecurityType(fxcmOrder.getInstrument().getSymbol());
|
||||
order.Symbol = _symbolMapper.GetLeanSymbol(fxcmOrder.getInstrument().getSymbol(), securityType, Market.FXCM);
|
||||
order.Quantity = Convert.ToInt32(fxcmOrder.getOrderQty() * (fxcmOrder.getSide() == SideFactory.BUY ? +1 : -1));
|
||||
order.Status = ConvertOrderStatus(fxcmOrder.getFXCMOrdStatus());
|
||||
order.BrokerId.Add(fxcmOrder.getOrderID());
|
||||
order.Properties.TimeInForce = ConvertTimeInForce(fxcmOrder.getTimeInForce());
|
||||
order.Time = FromJavaDate(fxcmOrder.getTransactTime().toDate());
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an FXCM order time in force to QuantConnect order time in force
|
||||
/// </summary>
|
||||
private static TimeInForce ConvertTimeInForce(ITimeInForce timeInForce)
|
||||
{
|
||||
if (timeInForce == TimeInForceFactory.GOOD_TILL_CANCEL)
|
||||
return TimeInForce.GoodTilCanceled;
|
||||
|
||||
if (timeInForce == TimeInForceFactory.DAY)
|
||||
return TimeInForce.Day;
|
||||
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an FXCM position to a QuantConnect holding.
|
||||
/// </summary>
|
||||
/// <param name="fxcmPosition">The FXCM position</param>
|
||||
private Holding ConvertHolding(PositionReport fxcmPosition)
|
||||
{
|
||||
var securityType = _symbolMapper.GetBrokerageSecurityType(fxcmPosition.getInstrument().getSymbol());
|
||||
|
||||
return new Holding
|
||||
{
|
||||
Symbol = _symbolMapper.GetLeanSymbol(fxcmPosition.getInstrument().getSymbol(), securityType, Market.FXCM),
|
||||
Type = securityType,
|
||||
AveragePrice = Convert.ToDecimal(fxcmPosition.getSettlPrice()),
|
||||
CurrencySymbol = "$",
|
||||
Quantity = Convert.ToDecimal(fxcmPosition.getPositionQty().getLongQty() > 0
|
||||
? fxcmPosition.getPositionQty().getLongQty()
|
||||
: -fxcmPosition.getPositionQty().getShortQty())
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an FXCM OrderStatus to a QuantConnect <see cref="OrderStatus"/>
|
||||
/// </summary>
|
||||
/// <param name="status"></param>
|
||||
/// <returns></returns>
|
||||
private static OrderStatus ConvertOrderStatus(ICode status)
|
||||
{
|
||||
var result = OrderStatus.None;
|
||||
|
||||
switch (status.getCode())
|
||||
{
|
||||
case IFixValueDefs.__Fields.FXCMORDSTATUS_INPROCESS:
|
||||
case IFixValueDefs.__Fields.FXCMORDSTATUS_WAITING:
|
||||
case IFixValueDefs.__Fields.FXCMORDSTATUS_EXECUTING:
|
||||
result = OrderStatus.Submitted;
|
||||
break;
|
||||
|
||||
case IFixValueDefs.__Fields.FXCMORDSTATUS_EXECUTED:
|
||||
result = OrderStatus.Filled;
|
||||
break;
|
||||
|
||||
case IFixValueDefs.__Fields.FXCMORDSTATUS_CANCELLED:
|
||||
case IFixValueDefs.__Fields.FXCMORDSTATUS_EXPIRED:
|
||||
result = OrderStatus.Canceled;
|
||||
break;
|
||||
|
||||
case IFixValueDefs.__Fields.FXCMORDSTATUS_REJECTED:
|
||||
result = OrderStatus.Invalid;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified order is considered open, otherwise false
|
||||
/// </summary>
|
||||
private static bool OrderIsOpen(string orderStatus)
|
||||
{
|
||||
return orderStatus == IFixValueDefs.__Fields.FXCMORDSTATUS_WAITING;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified order is considered close, otherwise false
|
||||
/// </summary>
|
||||
protected static bool OrderIsClosed(string orderStatus)
|
||||
{
|
||||
return orderStatus == IFixValueDefs.__Fields.FXCMORDSTATUS_EXECUTED
|
||||
|| orderStatus == IFixValueDefs.__Fields.FXCMORDSTATUS_CANCELLED
|
||||
|| orderStatus == IFixValueDefs.__Fields.FXCMORDSTATUS_EXPIRED
|
||||
|| orderStatus == IFixValueDefs.__Fields.FXCMORDSTATUS_REJECTED;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified order is being processed, otherwise false
|
||||
/// </summary>
|
||||
private static bool OrderIsBeingProcessed(string orderStatus)
|
||||
{
|
||||
return !OrderIsOpen(orderStatus) && !OrderIsClosed(orderStatus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Java Date value to a UTC DateTime value
|
||||
/// </summary>
|
||||
/// <param name="javaDate">The Java date</param>
|
||||
/// <returns></returns>
|
||||
private static DateTime FromJavaDate(Date javaDate)
|
||||
{
|
||||
// Convert javaDate to UTC Instant (Epoch)
|
||||
var instant = Instant.FromUnixTimeMilliseconds(javaDate.getTime());
|
||||
|
||||
// Convert to .Net UTC DateTime
|
||||
return instant.ToDateTimeUtc();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a LEAN Resolution to an IFXCMTimingInterval
|
||||
/// </summary>
|
||||
/// <param name="resolution">The resolution to convert</param>
|
||||
/// <returns></returns>
|
||||
public static IFXCMTimingInterval ToFxcmInterval(Resolution resolution)
|
||||
{
|
||||
IFXCMTimingInterval interval = null;
|
||||
|
||||
switch (resolution)
|
||||
{
|
||||
case Resolution.Tick:
|
||||
interval = FXCMTimingIntervalFactory.TICK;
|
||||
|
||||
break;
|
||||
case Resolution.Second:
|
||||
interval = FXCMTimingIntervalFactory.SEC10;
|
||||
|
||||
break;
|
||||
case Resolution.Minute:
|
||||
interval = FXCMTimingIntervalFactory.MIN1;
|
||||
|
||||
break;
|
||||
case Resolution.Hour:
|
||||
interval = FXCMTimingIntervalFactory.HOUR1;
|
||||
|
||||
break;
|
||||
case Resolution.Daily:
|
||||
interval = FXCMTimingIntervalFactory.DAY1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Java Date value to a UTC DateTime value
|
||||
/// </summary>
|
||||
/// <param name="utcDateTime">The UTC DateTime value</param>
|
||||
/// <returns>A UTC Java Date value</returns>
|
||||
public static Date ToJavaDateUtc(DateTime utcDateTime)
|
||||
{
|
||||
var cal = Calendar.getInstance();
|
||||
cal.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
|
||||
|
||||
cal.set(Calendar.YEAR, utcDateTime.Year);
|
||||
cal.set(Calendar.MONTH, utcDateTime.Month - 1);
|
||||
cal.set(Calendar.DAY_OF_MONTH, utcDateTime.Day);
|
||||
cal.set(Calendar.HOUR_OF_DAY, utcDateTime.Hour);
|
||||
cal.set(Calendar.MINUTE, utcDateTime.Minute);
|
||||
cal.set(Calendar.SECOND, utcDateTime.Second);
|
||||
cal.set(Calendar.MILLISECOND, utcDateTime.Millisecond);
|
||||
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
//
|
||||
// So it turns out that in order to properly load the QuantConnect.Brokerages
|
||||
// dll we need the IKVM.OpenJdk.Jdbc referenced in other projects that use
|
||||
// this. By placing a hard reference to an IKVM.OpenJdk.Jdbc type, the compiler
|
||||
// will properly copy the required dlls into other project bin directories.
|
||||
// Without this, consuming projects would need to hard refernce the IKVM dlls,
|
||||
// which is less than perfect. This seems to be the better of two evils
|
||||
//
|
||||
private static void ManageIKVMDependency()
|
||||
{
|
||||
var rowset = new CachedRowSetImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,781 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using com.fxcm.external.api.transport;
|
||||
using com.fxcm.external.api.transport.listeners;
|
||||
using com.fxcm.external.api.util;
|
||||
using com.fxcm.fix;
|
||||
using com.fxcm.fix.pretrade;
|
||||
using com.fxcm.fix.trade;
|
||||
using com.fxcm.messaging.util;
|
||||
using NodaTime;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Brokerages.Fxcm
|
||||
{
|
||||
/// <summary>
|
||||
/// FXCM brokerage - implementation of IBrokerage interface
|
||||
/// </summary>
|
||||
[BrokerageFactory(typeof(FxcmBrokerageFactory))]
|
||||
public partial class FxcmBrokerage : Brokerage, IDataQueueHandler, IGenericMessageListener, IStatusMessageListener
|
||||
{
|
||||
private readonly IOrderProvider _orderProvider;
|
||||
private readonly ISecurityProvider _securityProvider;
|
||||
private readonly IDataAggregator _aggregator;
|
||||
private readonly string _server;
|
||||
private readonly string _terminal;
|
||||
private readonly string _userName;
|
||||
private readonly string _password;
|
||||
private readonly string _accountId;
|
||||
|
||||
private Thread _orderEventThread;
|
||||
private Thread _connectionMonitorThread;
|
||||
|
||||
private readonly object _lockerConnectionMonitor = new object();
|
||||
private DateTime _lastReadyMessageTime;
|
||||
private volatile bool _connectionLost;
|
||||
|
||||
// tracks requested order updates, so we can flag Submitted order events as updates
|
||||
private readonly ConcurrentDictionary<int, int> _orderUpdates = new ConcurrentDictionary<int, int>();
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly ConcurrentQueue<OrderEvent> _orderEventQueue = new ConcurrentQueue<OrderEvent>();
|
||||
private readonly FxcmSymbolMapper _symbolMapper = new FxcmSymbolMapper();
|
||||
private readonly EventBasedDataQueueHandlerSubscriptionManager _subscriptionManager;
|
||||
|
||||
private readonly IList<BaseData> _lastHistoryChunk = new List<BaseData>();
|
||||
|
||||
private readonly Dictionary<Symbol, DateTimeZone> _symbolExchangeTimeZones = new Dictionary<Symbol, DateTimeZone>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets a timeout for history requests (in milliseconds)
|
||||
/// </summary>
|
||||
public int HistoryResponseTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the maximum number of retries for a history request
|
||||
/// </summary>
|
||||
public int MaximumHistoryRetryAttempts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets a value to enable only history requests to this brokerage
|
||||
/// Set to true in parallel downloaders to avoid loading accounts, orders, positions etc. at connect time
|
||||
/// </summary>
|
||||
public bool EnableOnlyHistoryRequests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor for the <see cref="FxcmBrokerage"/> class
|
||||
/// </summary>
|
||||
static FxcmBrokerage()
|
||||
{
|
||||
// FXCM requires TLS 1.2 since 6/16/2019
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="FxcmBrokerage"/> class
|
||||
/// </summary>
|
||||
/// <param name="orderProvider">The order provider</param>
|
||||
/// <param name="securityProvider">The holdings provider</param>
|
||||
/// <param name="aggregator">Consolidate ticks</param>
|
||||
/// <param name="server">The url of the server</param>
|
||||
/// <param name="terminal">The terminal name</param>
|
||||
/// <param name="userName">The user name (login id)</param>
|
||||
/// <param name="password">The user password</param>
|
||||
/// <param name="accountId">The account id</param>
|
||||
public FxcmBrokerage(IOrderProvider orderProvider, ISecurityProvider securityProvider, IDataAggregator aggregator, string server, string terminal, string userName, string password, string accountId)
|
||||
: base("FXCM Brokerage")
|
||||
{
|
||||
_orderProvider = orderProvider;
|
||||
_securityProvider = securityProvider;
|
||||
_aggregator = aggregator;
|
||||
_server = server;
|
||||
_terminal = terminal;
|
||||
_userName = userName;
|
||||
_password = password;
|
||||
_accountId = accountId;
|
||||
|
||||
_subscriptionManager = new EventBasedDataQueueHandlerSubscriptionManager();
|
||||
_subscriptionManager.SubscribeImpl += (s, t) => Subscribe(s);
|
||||
_subscriptionManager.UnsubscribeImpl += (s, t) => Unsubscribe(s);
|
||||
|
||||
HistoryResponseTimeout = 5000;
|
||||
MaximumHistoryRetryAttempts = 1;
|
||||
}
|
||||
|
||||
#region IBrokerage implementation
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if we're currently connected to the broker
|
||||
/// </summary>
|
||||
public override bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
return _gateway != null && _gateway.isConnected() && !_connectionLost;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects the client to the broker's remote servers
|
||||
/// </summary>
|
||||
public override void Connect()
|
||||
{
|
||||
if (IsConnected) return;
|
||||
|
||||
Log.Trace("FxcmBrokerage.Connect()");
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
// create new thread to fire order events in queue
|
||||
if (!EnableOnlyHistoryRequests)
|
||||
{
|
||||
_orderEventThread = new Thread(() =>
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
OrderEvent orderEvent;
|
||||
if (!_orderEventQueue.TryDequeue(out orderEvent))
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
OnOrderEvent(orderEvent);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception);
|
||||
}
|
||||
}
|
||||
}) { IsBackground = true };
|
||||
_orderEventThread.Start();
|
||||
while (!_orderEventThread.IsAlive)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
// create the gateway
|
||||
_gateway = GatewayFactory.createGateway();
|
||||
|
||||
// register the message listeners with the gateway
|
||||
_gateway.registerGenericMessageListener(this);
|
||||
_gateway.registerStatusMessageListener(this);
|
||||
|
||||
// create local login properties
|
||||
var loginProperties = new FXCMLoginProperties(_userName, _password, _terminal, _server);
|
||||
loginProperties.addProperty(IConnectionManager.APP_INFO, "QuantConnect");
|
||||
|
||||
// log in
|
||||
try
|
||||
{
|
||||
_gateway.login(loginProperties);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
var message =
|
||||
err.Message.Contains("ORA-20101") ? "Incorrect login credentials" :
|
||||
err.Message.Contains("ORA-20003") ? "Contact api@fxcm.com to enable API access, below is a template email. " + Environment.NewLine +
|
||||
"Email: api@fxcm.com " + Environment.NewLine +
|
||||
"Template: " + Environment.NewLine +
|
||||
"Hello FXCM staff, " + Environment.NewLine +
|
||||
"Please enable Java API for all accounts which are associated with this email address. " + Environment.NewLine +
|
||||
"Also, please respond to this email address once Java API has been enabled, letting me know that the change was done successfully." :
|
||||
err.Message;
|
||||
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
throw new BrokerageException(message, err.InnerException);
|
||||
}
|
||||
|
||||
// create new thread to manage disconnections and reconnections
|
||||
if (!EnableOnlyHistoryRequests)
|
||||
{
|
||||
_connectionMonitorThread = new Thread(() =>
|
||||
{
|
||||
_lastReadyMessageTime = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
TimeSpan elapsed;
|
||||
lock (_lockerConnectionMonitor)
|
||||
{
|
||||
elapsed = DateTime.UtcNow - _lastReadyMessageTime;
|
||||
}
|
||||
|
||||
if (!_connectionLost && elapsed > TimeSpan.FromSeconds(10))
|
||||
{
|
||||
_connectionLost = true;
|
||||
|
||||
OnMessage(BrokerageMessageEvent.Disconnected("Connection with FXCM server lost. " +
|
||||
"This could be because of internet connectivity issues. "));
|
||||
}
|
||||
else if (_connectionLost && IsWithinTradingHours())
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.ConnectionMonitorThread(): Attempting reconnection...");
|
||||
|
||||
try
|
||||
{
|
||||
// log out
|
||||
try
|
||||
{
|
||||
_gateway.logout();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
// remove the message listeners
|
||||
_gateway.removeGenericMessageListener(this);
|
||||
_gateway.removeStatusMessageListener(this);
|
||||
|
||||
// register the message listeners with the gateway
|
||||
_gateway.registerGenericMessageListener(this);
|
||||
_gateway.registerStatusMessageListener(this);
|
||||
|
||||
// log in
|
||||
_gateway.login(loginProperties);
|
||||
|
||||
// load instruments, accounts, orders, positions
|
||||
LoadInstruments();
|
||||
if (!EnableOnlyHistoryRequests)
|
||||
{
|
||||
LoadAccounts();
|
||||
LoadOpenOrders();
|
||||
LoadOpenPositions();
|
||||
}
|
||||
|
||||
_connectionLost = false;
|
||||
|
||||
OnMessage(BrokerageMessageEvent.Reconnected("Connection with FXCM server restored."));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.ConnectionMonitorThread(): reconnect failed.");
|
||||
Log.Error(exception);
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception);
|
||||
}
|
||||
}) { IsBackground = true };
|
||||
_connectionMonitorThread.Start();
|
||||
while (!_connectionMonitorThread.IsAlive)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
// load instruments, accounts, orders, positions
|
||||
LoadInstruments();
|
||||
if (!EnableOnlyHistoryRequests)
|
||||
{
|
||||
LoadAccounts();
|
||||
LoadOpenOrders();
|
||||
LoadOpenPositions();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if we are within FXCM trading hours
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static bool IsWithinTradingHours()
|
||||
{
|
||||
var time = DateTime.UtcNow.ConvertFromUtc(TimeZones.EasternStandard);
|
||||
|
||||
// FXCM Trading Hours: http://help.fxcm.com/us/Trading-Basics/New-to-Forex/38757093/What-are-the-Trading-Hours.htm
|
||||
|
||||
return !(time.DayOfWeek == DayOfWeek.Friday && time.TimeOfDay > new TimeSpan(16, 55, 0) ||
|
||||
time.DayOfWeek == DayOfWeek.Saturday ||
|
||||
time.DayOfWeek == DayOfWeek.Sunday && time.TimeOfDay < new TimeSpan(17, 0, 0) ||
|
||||
time.Month == 12 && time.Day == 25 ||
|
||||
time.Month == 1 && time.Day == 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the client from the broker's remote servers
|
||||
/// </summary>
|
||||
public override void Disconnect()
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.Disconnect()");
|
||||
|
||||
if (_gateway != null)
|
||||
{
|
||||
// log out
|
||||
try
|
||||
{
|
||||
if (_gateway.isConnected())
|
||||
_gateway.logout();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
// remove the message listeners
|
||||
_gateway.removeGenericMessageListener(this);
|
||||
_gateway.removeStatusMessageListener(this);
|
||||
}
|
||||
|
||||
// request and wait for thread to stop
|
||||
if (_cancellationTokenSource != null) _cancellationTokenSource.Cancel();
|
||||
|
||||
if (!EnableOnlyHistoryRequests)
|
||||
{
|
||||
if (_orderEventThread != null) _orderEventThread.Join();
|
||||
if (_connectionMonitorThread != null) _connectionMonitorThread.Join();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all open orders on the account.
|
||||
/// NOTE: The order objects returned do not have QC order IDs.
|
||||
/// </summary>
|
||||
/// <returns>The open orders returned from FXCM</returns>
|
||||
public override List<Order> GetOpenOrders()
|
||||
{
|
||||
Log.Trace($"FxcmBrokerage.GetOpenOrders(): Located {_openOrders.Count.ToStringInvariant()} orders");
|
||||
var orders = _openOrders.Values.ToList()
|
||||
.Where(x => OrderIsOpen(x.getFXCMOrdStatus().getCode()))
|
||||
.Select(ConvertOrder)
|
||||
.ToList();
|
||||
return orders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all holdings for the account
|
||||
/// </summary>
|
||||
/// <returns>The current holdings from the account</returns>
|
||||
public override List<Holding> GetAccountHoldings()
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.GetAccountHoldings()");
|
||||
|
||||
// FXCM maintains multiple positions per symbol, so we aggregate them by symbol.
|
||||
// The average price for the aggregated position is the quantity weighted average price.
|
||||
var holdings = _openPositions.Values
|
||||
.Select(ConvertHolding)
|
||||
.Where(x => x.Quantity != 0)
|
||||
.GroupBy(x => x.Symbol)
|
||||
.Select(group => new Holding
|
||||
{
|
||||
Symbol = group.Key,
|
||||
Type = group.First().Type,
|
||||
AveragePrice = group.Sum(x => x.AveragePrice * x.Quantity) / group.Sum(x => x.Quantity),
|
||||
CurrencySymbol = group.First().CurrencySymbol,
|
||||
Quantity = group.Sum(x => x.Quantity)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Set MarketPrice in each Holding
|
||||
var fxcmSymbols = holdings
|
||||
.Select(x => _symbolMapper.GetBrokerageSymbol(x.Symbol))
|
||||
.ToList();
|
||||
|
||||
if (fxcmSymbols.Count > 0)
|
||||
{
|
||||
var quotes = GetQuotes(fxcmSymbols).ToDictionary(x => x.getInstrument().getSymbol());
|
||||
foreach (var holding in holdings)
|
||||
{
|
||||
MarketDataSnapshot quote;
|
||||
if (quotes.TryGetValue(_symbolMapper.GetBrokerageSymbol(holding.Symbol), out quote))
|
||||
{
|
||||
holding.MarketPrice = Convert.ToDecimal((quote.getBidClose() + quote.getAskClose()) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return holdings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current cash balance for each currency held in the brokerage account
|
||||
/// </summary>
|
||||
/// <returns>The current cash balance for each currency available for trading</returns>
|
||||
public override List<CashAmount> GetCashBalance()
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.GetCashBalance()");
|
||||
var cashBook = new List<CashAmount>();
|
||||
|
||||
//Adds the account currency to the cashbook.
|
||||
cashBook.Add(new CashAmount(Convert.ToDecimal(_accounts[_accountId].getCashOutstanding()),
|
||||
AccountBaseCurrency));
|
||||
|
||||
// include cash balances from currency swaps for open Forex positions
|
||||
foreach (var trade in _openPositions.Values)
|
||||
{
|
||||
var brokerageSymbol = trade.getInstrument().getSymbol();
|
||||
var ticker = FxcmSymbolMapper.ConvertFxcmSymbolToLeanSymbol(brokerageSymbol);
|
||||
var securityType = _symbolMapper.GetBrokerageSecurityType(brokerageSymbol);
|
||||
|
||||
if (securityType == SecurityType.Forex)
|
||||
{
|
||||
//settlement price for the trade
|
||||
var settlementPrice = Convert.ToDecimal(trade.getSettlPrice());
|
||||
//direction of trade
|
||||
var direction = trade.getPositionQty().getLongQty() > 0 ? 1 : -1;
|
||||
//quantity of the asset
|
||||
var quantity = Convert.ToDecimal(trade.getPositionQty().getQty());
|
||||
//quantity of base currency
|
||||
var baseQuantity = direction * quantity;
|
||||
//quantity of quote currency
|
||||
var quoteQuantity = -direction * quantity * settlementPrice;
|
||||
//base currency
|
||||
var baseCurrency = trade.getCurrency();
|
||||
//quote currency
|
||||
var quoteCurrency = ticker.Substring(ticker.Length - 3);
|
||||
|
||||
var baseCurrencyAmount = cashBook.FirstOrDefault(x => x.Currency == baseCurrency);
|
||||
//update the value of the base currency
|
||||
if (baseCurrencyAmount != default(CashAmount))
|
||||
{
|
||||
cashBook.Remove(baseCurrencyAmount);
|
||||
cashBook.Add(new CashAmount(baseQuantity + baseCurrencyAmount.Amount, baseCurrency));
|
||||
}
|
||||
else
|
||||
{
|
||||
//add the base currency if not present
|
||||
cashBook.Add(new CashAmount(baseQuantity, baseCurrency));
|
||||
}
|
||||
|
||||
var quoteCurrencyAmount = cashBook.Find(x => x.Currency == quoteCurrency);
|
||||
//update the value of the quote currency
|
||||
if (quoteCurrencyAmount != default(CashAmount))
|
||||
{
|
||||
cashBook.Remove(quoteCurrencyAmount);
|
||||
cashBook.Add(new CashAmount(quoteQuantity + quoteCurrencyAmount.Amount, quoteCurrency));
|
||||
}
|
||||
else
|
||||
{
|
||||
//add the quote currency if not present
|
||||
cashBook.Add(new CashAmount(quoteQuantity, quoteCurrency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cashBook;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Places a new order and assigns a new broker ID to the order
|
||||
/// </summary>
|
||||
/// <param name="order">The order to be placed</param>
|
||||
/// <returns>True if the request for a new order has been placed, false otherwise</returns>
|
||||
public override bool PlaceOrder(Order order)
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.PlaceOrder(): {0}", order);
|
||||
|
||||
if (!IsConnected)
|
||||
throw new InvalidOperationException("FxcmBrokerage.PlaceOrder(): Unable to place order while not connected.");
|
||||
|
||||
if (order.Direction != OrderDirection.Buy && order.Direction != OrderDirection.Sell)
|
||||
throw new ArgumentException("FxcmBrokerage.PlaceOrder(): Invalid Order Direction");
|
||||
|
||||
var fxcmSymbol = _symbolMapper.GetBrokerageSymbol(order.Symbol);
|
||||
var orderSide = order.Direction == OrderDirection.Buy ? SideFactory.BUY : SideFactory.SELL;
|
||||
var quantity = (double)order.AbsoluteQuantity;
|
||||
|
||||
OrderSingle orderRequest;
|
||||
switch (order.Type)
|
||||
{
|
||||
case OrderType.Market:
|
||||
orderRequest = MessageGenerator.generateMarketOrder(_accountId, quantity, orderSide, fxcmSymbol, "");
|
||||
break;
|
||||
|
||||
case OrderType.Limit:
|
||||
var limitPrice = (double)((LimitOrder)order).LimitPrice;
|
||||
orderRequest = MessageGenerator.generateOpenOrder(limitPrice, _accountId, quantity, orderSide, fxcmSymbol, "");
|
||||
orderRequest.setOrdType(OrdTypeFactory.LIMIT);
|
||||
orderRequest.setTimeInForce(TimeInForceFactory.GOOD_TILL_CANCEL);
|
||||
break;
|
||||
|
||||
case OrderType.StopMarket:
|
||||
var stopPrice = (double)((StopMarketOrder)order).StopPrice;
|
||||
orderRequest = MessageGenerator.generateOpenOrder(stopPrice, _accountId, quantity, orderSide, fxcmSymbol, "");
|
||||
orderRequest.setOrdType(OrdTypeFactory.STOP);
|
||||
orderRequest.setTimeInForce(TimeInForceFactory.GOOD_TILL_CANCEL);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("FxcmBrokerage.PlaceOrder(): Order type " + order.Type + " is not supported.");
|
||||
}
|
||||
|
||||
_isOrderSubmitRejected = false;
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.sendMessage(orderRequest);
|
||||
_mapRequestsToOrders[_currentRequest] = order;
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.PlaceOrder(): Operation took longer than " +
|
||||
$"{((decimal) ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
|
||||
return !_isOrderSubmitRejected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the order with the same id
|
||||
/// </summary>
|
||||
/// <param name="order">The new order information</param>
|
||||
/// <returns>True if the request was made for the order to be updated, false otherwise</returns>
|
||||
public override bool UpdateOrder(Order order)
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.UpdateOrder(): {0}", order);
|
||||
|
||||
if (!IsConnected)
|
||||
throw new InvalidOperationException("FxcmBrokerage.UpdateOrder(): Unable to update order while not connected.");
|
||||
|
||||
if (!order.BrokerId.Any())
|
||||
{
|
||||
// we need the brokerage order id in order to perform an update
|
||||
Log.Trace("FxcmBrokerage.UpdateOrder(): Unable to update order without BrokerId.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var fxcmOrderId = order.BrokerId[0].ToStringInvariant();
|
||||
|
||||
ExecutionReport fxcmOrder;
|
||||
if (!_openOrders.TryGetValue(fxcmOrderId, out fxcmOrder))
|
||||
throw new ArgumentException("FxcmBrokerage.UpdateOrder(): FXCM order id not found: " + fxcmOrderId);
|
||||
|
||||
double price;
|
||||
switch (order.Type)
|
||||
{
|
||||
case OrderType.Limit:
|
||||
price = (double)((LimitOrder)order).LimitPrice;
|
||||
break;
|
||||
|
||||
case OrderType.StopMarket:
|
||||
price = (double)((StopMarketOrder)order).StopPrice;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("FxcmBrokerage.UpdateOrder(): Invalid order type.");
|
||||
}
|
||||
|
||||
_isOrderUpdateOrCancelRejected = false;
|
||||
var orderReplaceRequest = MessageGenerator.generateOrderReplaceRequest("", fxcmOrder.getOrderID(), fxcmOrder.getSide(), fxcmOrder.getOrdType(), price, fxcmOrder.getAccount());
|
||||
orderReplaceRequest.setInstrument(fxcmOrder.getInstrument());
|
||||
orderReplaceRequest.setOrderQty((double)order.AbsoluteQuantity);
|
||||
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_orderUpdates[order.Id] = order.Id;
|
||||
_currentRequest = _gateway.sendMessage(orderReplaceRequest);
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.UpdateOrder(): Operation took longer than " +
|
||||
$"{((decimal) ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
|
||||
return !_isOrderUpdateOrCancelRejected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the order with the specified ID
|
||||
/// </summary>
|
||||
/// <param name="order">The order to cancel</param>
|
||||
/// <returns>True if the request was made for the order to be canceled, false otherwise</returns>
|
||||
public override bool CancelOrder(Order order)
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.CancelOrder(): {0}", order);
|
||||
|
||||
if (!IsConnected)
|
||||
throw new InvalidOperationException("FxcmBrokerage.UpdateOrder(): Unable to cancel order while not connected.");
|
||||
|
||||
if (!order.BrokerId.Any())
|
||||
{
|
||||
// we need the brokerage order id in order to perform a cancellation
|
||||
Log.Trace("FxcmBrokerage.CancelOrder(): Unable to cancel order without BrokerId.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var fxcmOrderId = order.BrokerId[0].ToStringInvariant();
|
||||
|
||||
ExecutionReport fxcmOrder;
|
||||
if (!_openOrders.TryGetValue(fxcmOrderId, out fxcmOrder))
|
||||
throw new ArgumentException("FxcmBrokerage.CancelOrder(): FXCM order id not found: " + fxcmOrderId);
|
||||
|
||||
_isOrderUpdateOrCancelRejected = false;
|
||||
var orderCancelRequest = MessageGenerator.generateOrderCancelRequest("", fxcmOrder.getOrderID(), fxcmOrder.getSide(), fxcmOrder.getAccount());
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.sendMessage(orderCancelRequest);
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.CancelOrder(): Operation took longer than " +
|
||||
$"{((decimal) ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
|
||||
return !_isOrderUpdateOrCancelRejected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the history for the requested security
|
||||
/// </summary>
|
||||
/// <param name="request">The historical data request</param>
|
||||
/// <returns>An enumerable of bars covering the span specified in the request</returns>
|
||||
public override IEnumerable<BaseData> GetHistory(HistoryRequest request)
|
||||
{
|
||||
if (!_symbolMapper.IsKnownLeanSymbol(request.Symbol))
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.GetHistory(): Invalid symbol: {0}, no history returned", request.Symbol.Value);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// cache exchange time zone for symbol
|
||||
DateTimeZone exchangeTimeZone;
|
||||
if (!_symbolExchangeTimeZones.TryGetValue(request.Symbol, out exchangeTimeZone))
|
||||
{
|
||||
exchangeTimeZone = MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.FXCM, request.Symbol, request.Symbol.SecurityType).TimeZone;
|
||||
_symbolExchangeTimeZones.Add(request.Symbol, exchangeTimeZone);
|
||||
}
|
||||
|
||||
var interval = ToFxcmInterval(request.Resolution);
|
||||
|
||||
// download data
|
||||
var history = new List<BaseData>();
|
||||
var lastEndTime = DateTime.MinValue;
|
||||
|
||||
var end = request.EndTimeUtc;
|
||||
|
||||
var attempt = 1;
|
||||
while (end > request.StartTimeUtc)
|
||||
{
|
||||
Log.Debug($"FxcmBrokerage.GetHistory(): Requesting {end.ToIso8601Invariant()} to {request.StartTimeUtc.ToIso8601Invariant()}");
|
||||
_lastHistoryChunk.Clear();
|
||||
|
||||
var mdr = new MarketDataRequest();
|
||||
mdr.setSubscriptionRequestType(SubscriptionRequestTypeFactory.SNAPSHOT);
|
||||
mdr.setResponseFormat(IFixMsgTypeDefs.__Fields.MSGTYPE_FXCMRESPONSE);
|
||||
mdr.setFXCMTimingInterval(interval);
|
||||
mdr.setMDEntryTypeSet(MarketDataRequest.MDENTRYTYPESET_ALL);
|
||||
|
||||
mdr.setFXCMStartDate(new UTCDate(ToJavaDateUtc(request.StartTimeUtc)));
|
||||
mdr.setFXCMStartTime(new UTCTimeOnly(ToJavaDateUtc(request.StartTimeUtc)));
|
||||
mdr.setFXCMEndDate(new UTCDate(ToJavaDateUtc(end)));
|
||||
mdr.setFXCMEndTime(new UTCTimeOnly(ToJavaDateUtc(end)));
|
||||
mdr.addRelatedSymbol(_fxcmInstruments[_symbolMapper.GetBrokerageSymbol(request.Symbol)]);
|
||||
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.sendMessage(mdr);
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
_pendingHistoryRequests.Add(_currentRequest);
|
||||
}
|
||||
|
||||
if (!autoResetEvent.WaitOne(HistoryResponseTimeout))
|
||||
{
|
||||
// No response can mean genuine timeout or the history data has ended.
|
||||
|
||||
// 90% of the time no response because no data; widen the search net to 5m if we don't get a response:
|
||||
if (request.StartTimeUtc.AddSeconds(300) >= end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// 5% of the time its because the data ends at a specific, repeatible time not close to our desired endtime:
|
||||
if (end == lastEndTime)
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.GetHistory(): Request for {0} ended at {1:O}", request.Symbol.Value, end);
|
||||
break;
|
||||
}
|
||||
|
||||
// 5% of the time its because of an internet / time of day / api settings / timeout: throw if this is the *second* attempt.
|
||||
if (EnableOnlyHistoryRequests && lastEndTime != DateTime.MinValue)
|
||||
{
|
||||
throw new TimeoutException("FxcmBrokerage.GetHistory(): History operation ending in {end:O} took longer than " +
|
||||
$"{((decimal) HistoryResponseTimeout / 1000).ToStringInvariant()} seconds. This may be because there is no data, retrying..."
|
||||
);
|
||||
}
|
||||
|
||||
// Assuming Timeout: If we've already retried quite a few times, lets bail.
|
||||
if (++attempt > MaximumHistoryRetryAttempts)
|
||||
{
|
||||
Log.Trace("FxcmBrokerage.GetHistory(): Maximum attempts reached for: " + request.Symbol.Value);
|
||||
break;
|
||||
}
|
||||
|
||||
// Assuming Timeout: Save end time and if have the same endtime next time, break since its likely there's no data after that time.
|
||||
lastEndTime = end;
|
||||
Log.Trace($"FxcmBrokerage.GetHistory(): Attempt {attempt.ToStringInvariant()} for: " +
|
||||
$"{request.Symbol.Value} ended at {lastEndTime.ToIso8601Invariant()}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add data
|
||||
lock (_locker)
|
||||
{
|
||||
history.InsertRange(0, _lastHistoryChunk);
|
||||
}
|
||||
|
||||
var firstDateUtc = _lastHistoryChunk[0].Time.ConvertToUtc(exchangeTimeZone);
|
||||
if (end != firstDateUtc)
|
||||
{
|
||||
// new end date = first datapoint date.
|
||||
end = request.Resolution == Resolution.Tick ? firstDateUtc.AddMilliseconds(-1) : firstDateUtc.AddSeconds(-1);
|
||||
|
||||
if (request.StartTimeUtc.AddSeconds(10) >= end)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var data in history)
|
||||
{
|
||||
yield return data;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Brokerages.Fxcm
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an implementation of <see cref="IBrokerageFactory"/> that produces a <see cref="FxcmBrokerage"/>
|
||||
/// </summary>
|
||||
public class FxcmBrokerageFactory : BrokerageFactory
|
||||
{
|
||||
private const string DefaultServer = "http://www.fxcorporate.com/Hosts.jsp";
|
||||
private const string DefaultTerminal = "Demo";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FxcmBrokerageFactory"/> class
|
||||
/// </summary>
|
||||
public FxcmBrokerageFactory()
|
||||
: base(typeof(FxcmBrokerage))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the brokerage data required to run the brokerage from configuration/disk
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The implementation of this property will create the brokerage data dictionary required for
|
||||
/// running live jobs. See <see cref="IJobQueueHandler.NextJob"/>
|
||||
/// </remarks>
|
||||
public override Dictionary<string, string> BrokerageData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{ "fxcm-server", Config.Get("fxcm-server", DefaultServer) },
|
||||
{ "fxcm-terminal", Config.Get("fxcm-terminal", DefaultTerminal) },
|
||||
{ "fxcm-user-name", Config.Get("fxcm-user-name") },
|
||||
{ "fxcm-password", Config.Get("fxcm-password") },
|
||||
{ "fxcm-account-id", Config.Get("fxcm-account-id") }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new instance of the <see cref="FxcmBrokerageModel"/>
|
||||
/// </summary>
|
||||
/// <param name="orderProvider">The order provider</param>
|
||||
public override IBrokerageModel GetBrokerageModel(IOrderProvider orderProvider) => new FxcmBrokerageModel();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IBrokerage"/> instance
|
||||
/// </summary>
|
||||
/// <param name="job">The job packet to create the brokerage for</param>
|
||||
/// <param name="algorithm">The algorithm instance</param>
|
||||
/// <returns>A new brokerage instance</returns>
|
||||
public override IBrokerage CreateBrokerage(LiveNodePacket job, IAlgorithm algorithm)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
// read values from the brokerage data
|
||||
var server = Read<string>(job.BrokerageData, "fxcm-server", errors);
|
||||
var terminal = Read<string>(job.BrokerageData, "fxcm-terminal", errors);
|
||||
var userName = Read<string>(job.BrokerageData, "fxcm-user-name", errors);
|
||||
var password = Read<string>(job.BrokerageData, "fxcm-password", errors);
|
||||
var accountId = Read<string>(job.BrokerageData, "fxcm-account-id", errors);
|
||||
|
||||
if (errors.Count != 0)
|
||||
{
|
||||
// if we had errors then we can't create the instance
|
||||
throw new Exception(string.Join(Environment.NewLine, errors));
|
||||
}
|
||||
|
||||
var brokerage = new FxcmBrokerage(
|
||||
algorithm.Transactions,
|
||||
algorithm.Portfolio,
|
||||
Composer.Instance.GetExportedValueByTypeName<IDataAggregator>(Config.Get("data-aggregator", "QuantConnect.Lean.Engine.DataFeeds.AggregationManager")),
|
||||
server,
|
||||
terminal,
|
||||
userName,
|
||||
password,
|
||||
accountId);
|
||||
Composer.Instance.AddPart<IDataQueueHandler>(brokerage);
|
||||
|
||||
return brokerage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a brokerage message handler
|
||||
/// </summary>
|
||||
public override IBrokerageMessageHandler CreateBrokerageMessageHandler(IAlgorithm algorithm, AlgorithmNodePacket job, IApi api)
|
||||
{
|
||||
//We have chosen a timespan of negative 30 beacause FXCM market hours don't always open on time.
|
||||
return new DefaultBrokerageMessageHandler(algorithm, job, api, openThreshold: TimeSpan.FromMinutes(-30));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Brokerages.Fxcm
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the mapping between Lean symbols and FXCM symbols.
|
||||
/// </summary>
|
||||
public class FxcmSymbolMapper : ISymbolMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// List of all known symbols for FXCM
|
||||
/// </summary>
|
||||
public static List<Symbol> KnownSymbols
|
||||
{
|
||||
get
|
||||
{
|
||||
var symbols = new List<Symbol>();
|
||||
var mapper = new FxcmSymbolMapper();
|
||||
foreach (var tp in FxcmSymbolMappings)
|
||||
{
|
||||
symbols.Add(mapper.GetLeanSymbol(tp.Item1, mapper.GetBrokerageSecurityType(tp.Item1), Market.FXCM));
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to allow collection initializer on a List of tuples
|
||||
/// </summary>
|
||||
private class TupleList<T1, T2> : List<Tuple<T1, T2>>
|
||||
{
|
||||
public void Add(T1 item1, T2 item2)
|
||||
{
|
||||
Add(new Tuple<T1, T2>(item1, item2));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of mappings from FXCM symbols to Lean symbols.
|
||||
/// </summary>
|
||||
/// <remarks>T1 is FXCM symbol, T2 is Lean symbol</remarks>
|
||||
private static readonly TupleList<string, string> FxcmSymbolMappings = new TupleList<string, string>
|
||||
{
|
||||
{ "AUD/CAD", "AUDCAD" },
|
||||
{ "AUD/CHF", "AUDCHF" },
|
||||
{ "AUD/JPY", "AUDJPY" },
|
||||
{ "AUD/NZD", "AUDNZD" },
|
||||
{ "AUD/USD", "AUDUSD" },
|
||||
{ "AUS200", "AU200AUD" },
|
||||
{ "Bund", "DE10YBEUR" },
|
||||
{ "CAD/CHF", "CADCHF" },
|
||||
{ "CAD/JPY", "CADJPY" },
|
||||
{ "CHF/JPY", "CHFJPY" },
|
||||
{ "Copper", "XCUUSD" },
|
||||
{ "ESP35", "ES35EUR" },
|
||||
{ "EUR/AUD", "EURAUD" },
|
||||
{ "EUR/CAD", "EURCAD" },
|
||||
{ "EUR/CHF", "EURCHF" },
|
||||
{ "EUR/GBP", "EURGBP" },
|
||||
{ "EUR/JPY", "EURJPY" },
|
||||
{ "EUR/NOK", "EURNOK" },
|
||||
{ "EUR/NZD", "EURNZD" },
|
||||
{ "EUR/SEK", "EURSEK" },
|
||||
{ "EUR/TRY", "EURTRY" },
|
||||
{ "EUR/USD", "EURUSD" },
|
||||
{ "EUSTX50", "EU50EUR" },
|
||||
{ "FRA40", "FR40EUR" },
|
||||
{ "GBP/AUD", "GBPAUD" },
|
||||
{ "GBP/CAD", "GBPCAD" },
|
||||
{ "GBP/CHF", "GBPCHF" },
|
||||
{ "GBP/JPY", "GBPJPY" },
|
||||
{ "GBP/NZD", "GBPNZD" },
|
||||
{ "GBP/USD", "GBPUSD" },
|
||||
{ "GER30", "DE30EUR" },
|
||||
{ "HKG33", "HK33HKD" },
|
||||
{ "ITA40", "IT40EUR" },
|
||||
{ "JPN225", "JP225JPY" },
|
||||
{ "NAS100", "NAS100USD" },
|
||||
{ "NGAS", "NATGASUSD" },
|
||||
{ "NZD/CAD", "NZDCAD" },
|
||||
{ "NZD/CHF", "NZDCHF" },
|
||||
{ "NZD/JPY", "NZDJPY" },
|
||||
{ "NZD/USD", "NZDUSD" },
|
||||
{ "SPX500", "SPX500USD" },
|
||||
{ "SUI20", "CH20CHF" },
|
||||
{ "TRY/JPY", "TRYJPY" },
|
||||
{ "UK100", "UK100GBP" },
|
||||
{ "UKOil", "BCOUSD" },
|
||||
{ "US30", "US30USD" },
|
||||
{ "USD/MXN", "USDMXN" },
|
||||
{ "USDOLLAR", "DXYUSD" },
|
||||
{ "USD/CAD", "USDCAD" },
|
||||
{ "USD/CHF", "USDCHF" },
|
||||
{ "USD/CNH", "USDCNH" },
|
||||
{ "USD/HKD", "USDHKD" },
|
||||
{ "USD/JPY", "USDJPY" },
|
||||
{ "USD/NOK", "USDNOK" },
|
||||
{ "USD/SEK", "USDSEK" },
|
||||
{ "USD/TRY", "USDTRY" },
|
||||
{ "USD/ZAR", "USDZAR" },
|
||||
{ "USOil", "WTICOUSD" },
|
||||
{ "XAG/USD", "XAGUSD" },
|
||||
{ "XAU/USD", "XAUUSD" },
|
||||
{ "XPD/USD", "XPDUSD" },
|
||||
{ "XPT/USD", "XPTUSD" },
|
||||
{ "ZAR/JPY", "ZARJPY" }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> MapFxcmToLean = new Dictionary<string, string>();
|
||||
private static readonly Dictionary<string, string> MapLeanToFxcm = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// The list of known FXCM currencies.
|
||||
/// </summary>
|
||||
private static readonly HashSet<string> KnownCurrencies = new HashSet<string>
|
||||
{
|
||||
"AUD", "CAD", "CHF", "CNH", "EUR", "GBP", "HKD", "JPY", "MXN", "NOK", "NZD", "SEK", "TRY", "USD", "ZAR"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor for the <see cref="FxcmSymbolMapper"/> class
|
||||
/// </summary>
|
||||
static FxcmSymbolMapper()
|
||||
{
|
||||
foreach (var mapping in FxcmSymbolMappings)
|
||||
{
|
||||
MapFxcmToLean[mapping.Item1] = mapping.Item2;
|
||||
MapLeanToFxcm[mapping.Item2] = mapping.Item1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Lean symbol instance to an FXCM symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">A Lean symbol instance</param>
|
||||
/// <returns>The FXCM symbol</returns>
|
||||
public string GetBrokerageSymbol(Symbol symbol)
|
||||
{
|
||||
if (symbol == null || string.IsNullOrWhiteSpace(symbol.Value))
|
||||
throw new ArgumentException("Invalid symbol: " + (symbol == null ? "null" : symbol.ToString()));
|
||||
|
||||
if (symbol.ID.SecurityType != SecurityType.Forex && symbol.ID.SecurityType != SecurityType.Cfd)
|
||||
throw new ArgumentException("Invalid security type: " + symbol.ID.SecurityType);
|
||||
|
||||
var brokerageSymbol = ConvertLeanSymbolToFxcmSymbol(symbol.Value);
|
||||
|
||||
if (!IsKnownBrokerageSymbol(brokerageSymbol))
|
||||
throw new ArgumentException("Unknown symbol: " + symbol.Value);
|
||||
|
||||
return brokerageSymbol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an FXCM symbol to a Lean symbol instance
|
||||
/// </summary>
|
||||
/// <param name="brokerageSymbol">The FXCM symbol</param>
|
||||
/// <param name="securityType">The security type</param>
|
||||
/// <param name="market">The market</param>
|
||||
/// <param name="expirationDate">Expiration date of the security(if applicable)</param>
|
||||
/// <param name="strike">The strike of the security (if applicable)</param>
|
||||
/// <param name="optionRight">The option right of the security (if applicable)</param>
|
||||
/// <returns>A new Lean Symbol instance</returns>
|
||||
public Symbol GetLeanSymbol(string brokerageSymbol, SecurityType securityType, string market, DateTime expirationDate = default(DateTime), decimal strike = 0, OptionRight optionRight = 0)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(brokerageSymbol))
|
||||
throw new ArgumentException("Invalid FXCM symbol: " + brokerageSymbol);
|
||||
|
||||
if (!IsKnownBrokerageSymbol(brokerageSymbol))
|
||||
throw new ArgumentException("Unknown FXCM symbol: " + brokerageSymbol);
|
||||
|
||||
if (securityType != SecurityType.Forex && securityType != SecurityType.Cfd)
|
||||
throw new ArgumentException("Invalid security type: " + securityType);
|
||||
|
||||
if (market != Market.FXCM)
|
||||
throw new ArgumentException("Invalid market: " + market);
|
||||
|
||||
return Symbol.Create(ConvertFxcmSymbolToLeanSymbol(brokerageSymbol), GetBrokerageSecurityType(brokerageSymbol), Market.FXCM);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the security type for an FXCM symbol
|
||||
/// </summary>
|
||||
/// <param name="brokerageSymbol">The FXCM symbol</param>
|
||||
/// <returns>The security type</returns>
|
||||
public SecurityType GetBrokerageSecurityType(string brokerageSymbol)
|
||||
{
|
||||
var tokens = brokerageSymbol.Split('/');
|
||||
return tokens.Length == 2 && KnownCurrencies.Contains(tokens[0]) && KnownCurrencies.Contains(tokens[1])
|
||||
? SecurityType.Forex
|
||||
: SecurityType.Cfd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the security type for a Lean symbol
|
||||
/// </summary>
|
||||
/// <param name="leanSymbol">The Lean symbol</param>
|
||||
/// <returns>The security type</returns>
|
||||
public SecurityType GetLeanSecurityType(string leanSymbol)
|
||||
{
|
||||
string fxcmSymbol;
|
||||
if (!MapLeanToFxcm.TryGetValue(leanSymbol, out fxcmSymbol))
|
||||
throw new ArgumentException("Unknown Lean symbol: " + leanSymbol);
|
||||
|
||||
return GetBrokerageSecurityType(fxcmSymbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the symbol is supported by FXCM
|
||||
/// </summary>
|
||||
/// <param name="brokerageSymbol">The FXCM symbol</param>
|
||||
/// <returns>True if FXCM supports the symbol</returns>
|
||||
public bool IsKnownBrokerageSymbol(string brokerageSymbol)
|
||||
{
|
||||
return brokerageSymbol != null && MapFxcmToLean.ContainsKey(brokerageSymbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the symbol is supported by FXCM
|
||||
/// </summary>
|
||||
/// <param name="symbol">The Lean symbol</param>
|
||||
/// <returns>True if FXCM supports the symbol</returns>
|
||||
public bool IsKnownLeanSymbol(Symbol symbol)
|
||||
{
|
||||
if (symbol == null || string.IsNullOrWhiteSpace(symbol.Value))
|
||||
return false;
|
||||
|
||||
var fxcmSymbol = ConvertLeanSymbolToFxcmSymbol(symbol.Value);
|
||||
|
||||
return MapFxcmToLean.ContainsKey(fxcmSymbol) && GetBrokerageSecurityType(fxcmSymbol) == symbol.ID.SecurityType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an FXCM symbol to a Lean symbol string
|
||||
/// </summary>
|
||||
public static string ConvertFxcmSymbolToLeanSymbol(string fxcmSymbol)
|
||||
{
|
||||
string leanSymbol;
|
||||
return MapFxcmToLean.TryGetValue(fxcmSymbol, out leanSymbol) ? leanSymbol : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Lean symbol string to an FXCM symbol
|
||||
/// </summary>
|
||||
private static string ConvertLeanSymbolToFxcmSymbol(string leanSymbol)
|
||||
{
|
||||
string fxcmSymbol;
|
||||
return MapLeanToFxcm.TryGetValue(leanSymbol, out fxcmSymbol) ? fxcmSymbol : string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
BIN
Brokerages/InteractiveBrokers/QuantConnect.IBAutomater.dll
Normal file
BIN
Brokerages/InteractiveBrokers/QuantConnect.IBAutomater.dll
Normal file
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Brokerages</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Brokerages</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<DefineConstants>NET45</DefineConstants>
|
||||
@@ -32,7 +32,6 @@
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="19.0.0" />
|
||||
<PackageReference Include="IKVM" Version="8.1.5717.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@@ -52,15 +51,6 @@
|
||||
<Reference Include="CSharpAPI">
|
||||
<HintPath>InteractiveBrokers\CSharpAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="QuantConnect.Fxcm, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>Fxcm\QuantConnect.Fxcm.dll</HintPath>
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
@@ -81,9 +71,4 @@
|
||||
<ProjectReference Include="..\Configuration\QuantConnect.Configuration.csproj" />
|
||||
<ProjectReference Include="..\Logging\QuantConnect.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fxcm\QuantConnect.Fxcm.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Brokerages
|
||||
{
|
||||
|
||||
@@ -18,7 +18,6 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Securities.Future;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Data.Market
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<AssemblyName>QuantConnect.Common</AssemblyName>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
@@ -100,24 +100,6 @@
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
<PackageReference Include="System.ServiceModel.Primitives" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.IdentityModel" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IdentityModel" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Numerics" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Configuration\QuantConnect.Configuration.csproj" />
|
||||
<ProjectReference Include="..\Logging\QuantConnect.Logging.csproj" />
|
||||
|
||||
@@ -203,7 +203,7 @@ namespace QuantConnect.Securities.Option.StrategyMatcher
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public OptionPosition GetUnderlyingPosition()
|
||||
=> LinqExtensions.GetValueOrDefault(_positions, Underlying, new OptionPosition(Underlying, 0));
|
||||
=> _positions.GetValueOrDefault(Underlying, new OptionPosition(Underlying, 0));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="OptionPositionCollection"/> from the specified enumerable of <paramref name="positions"/>
|
||||
|
||||
@@ -61,17 +61,6 @@ namespace QuantConnect.Util
|
||||
return new ReadOnlyDictionary<K, V>(enumerable.ToDictionary());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HashSet{T}"/> from the elements in the specified enumerable
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type in the hash set</typeparam>
|
||||
/// <param name="enumerable">The items to be placed into the enumerable</param>
|
||||
/// <returns>A new <see cref="HashSet{T}"/> containing the items in the enumerable</returns>
|
||||
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> enumerable)
|
||||
{
|
||||
return new HashSet<T>(enumerable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HashSet{T}"/> from the elements in the specified enumerable
|
||||
/// </summary>
|
||||
@@ -347,21 +336,6 @@ namespace QuantConnect.Util
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key or provided default value if key is not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="K">The key type</typeparam>
|
||||
/// <typeparam name="V">The value type</typeparam>
|
||||
/// <param name="dictionary">The dictionary instance</param>
|
||||
/// <param name="key">Lookup key</param>
|
||||
/// <param name="defaultValue">Default value</param>
|
||||
/// <returns>Value associated with the specified key or default value</returns>
|
||||
public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dictionary, K key, V defaultValue = default(V))
|
||||
{
|
||||
V obj;
|
||||
return dictionary.TryGetValue(key, out obj) ? obj : defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an action for each element in collection source
|
||||
/// </summary>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Compression</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Compression</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
@@ -37,11 +37,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Configuration</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Configuration</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<LangVersion>6</LangVersion>
|
||||
@@ -38,10 +38,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<RootNamespace>QuantConnect.Lean.Engine</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Lean.Engine</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
@@ -108,13 +108,6 @@
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Runtime.Caching" />
|
||||
<Reference Include="System.Web" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Indicators</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Indicators</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
@@ -49,10 +49,6 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>QuantConnect.Lean.Launcher</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Lean.Launcher</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
@@ -37,7 +37,6 @@
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DynamicInterop" Version="0.9.1" />
|
||||
<PackageReference Include="IKVM" Version="8.1.5717.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@@ -45,11 +44,6 @@
|
||||
<PackageReference Include="R.NET" Version="1.9.0" />
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<RootNamespace>QuantConnect.Logging</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Logging</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<LangVersion>6</LangVersion>
|
||||
@@ -57,10 +57,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Messaging</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Messaging</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
@@ -40,12 +40,6 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="RestSharp" Version="106.6.10" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>QuantConnect.Optimizer.Launcher</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Optimizer.Launcher</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
@@ -38,11 +38,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Optimizer</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Optimizer</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<LangVersion>6</LangVersion>
|
||||
@@ -40,11 +40,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Queues</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Queues</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
@@ -36,10 +36,6 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>QuantConnect.Report</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Report</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
@@ -67,10 +67,6 @@
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Research</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Research</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
@@ -52,10 +52,6 @@
|
||||
<PackageReference Include="NodaTime" Version="3.0.5" />
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Algorithm.Framework\QuantConnect.Algorithm.Framework.csproj" />
|
||||
<ProjectReference Include="..\Algorithm\QuantConnect.Algorithm.csproj" />
|
||||
|
||||
@@ -24,7 +24,7 @@ using QuantConnect.Securities.Future;
|
||||
using QuantConnect.Securities.Option;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using System;
|
||||
using QuantConnect.Securities.Index;
|
||||
using Index = QuantConnect.Securities.Index.Index;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Brokerages.Fxcm;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Logging;
|
||||
|
||||
namespace QuantConnect.Tests.Brokerages.Fxcm
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class FxcmBrokerageTests
|
||||
{
|
||||
[Test]
|
||||
public void GetsTickData()
|
||||
{
|
||||
var brokerage = (FxcmBrokerage)Brokerage;
|
||||
var cancelationToken = new CancellationTokenSource();
|
||||
|
||||
var configs = new SubscriptionDataConfig[] {
|
||||
GetSubscriptionDataConfig<TradeBar>(Symbols.USDJPY, Resolution.Second),
|
||||
GetSubscriptionDataConfig<TradeBar>(Symbols.EURGBP, Resolution.Second)
|
||||
};
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
ProcessFeed(
|
||||
brokerage.Subscribe(config, (s, e) => { }),
|
||||
cancelationToken,
|
||||
(tick) => {
|
||||
if (tick != null)
|
||||
{
|
||||
Log.Trace("{0}: {1} - {2} / {3}", tick.Time.ToStringInvariant("yyyy-MM-dd HH:mm:ss.fff"), tick.Symbol, (tick as Tick)?.BidPrice, (tick as Tick)?.AskPrice);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Thread.Sleep(5000);
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
brokerage.Unsubscribe(config);
|
||||
}
|
||||
|
||||
Thread.Sleep(20000);
|
||||
|
||||
cancelationToken.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Brokerages.Fxcm;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Tests.Brokerages.Fxcm
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class FxcmBrokerageTests
|
||||
{
|
||||
private static TestCaseData[] TestParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
// valid parameters
|
||||
new TestCaseData(Symbols.EURUSD, Resolution.Tick, TimeSpan.FromSeconds(15), false),
|
||||
new TestCaseData(Symbols.EURUSD, Resolution.Second, Time.OneMinute, false),
|
||||
new TestCaseData(Symbols.EURUSD, Resolution.Minute, Time.OneHour, false),
|
||||
new TestCaseData(Symbols.EURUSD, Resolution.Hour, Time.OneDay, false),
|
||||
new TestCaseData(Symbols.EURUSD, Resolution.Daily, TimeSpan.FromDays(15), false),
|
||||
|
||||
// invalid period, no error, empty result
|
||||
new TestCaseData(Symbols.EURUSD, Resolution.Daily, TimeSpan.FromDays(-15), false),
|
||||
|
||||
// invalid symbol, throws "System.ArgumentException : Unknown symbol: XYZ"
|
||||
new TestCaseData(Symbol.Create("XYZ", SecurityType.Forex, Market.FXCM), Resolution.Daily, TimeSpan.FromDays(15), true),
|
||||
|
||||
// invalid security type, throws "System.ArgumentException : Invalid security type: Equity"
|
||||
new TestCaseData(Symbols.AAPL, Resolution.Daily, TimeSpan.FromDays(15), true),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(TestParameters))]
|
||||
public void GetsHistory(Symbol symbol, Resolution resolution, TimeSpan period, bool throwsException)
|
||||
{
|
||||
TestDelegate test = () =>
|
||||
{
|
||||
var brokerage = (FxcmBrokerage) Brokerage;
|
||||
|
||||
var historyProvider = new BrokerageHistoryProvider();
|
||||
historyProvider.SetBrokerage(brokerage);
|
||||
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, null, null, null, null, false, null));
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var requests = new[]
|
||||
{
|
||||
new HistoryRequest(now.Add(-period),
|
||||
now,
|
||||
typeof(QuoteBar),
|
||||
symbol,
|
||||
resolution,
|
||||
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
|
||||
DateTimeZone.Utc,
|
||||
Resolution.Minute,
|
||||
false,
|
||||
false,
|
||||
DataNormalizationMode.Adjusted,
|
||||
TickType.Quote)
|
||||
};
|
||||
|
||||
var history = historyProvider.GetHistory(requests, TimeZones.Utc);
|
||||
|
||||
foreach (var slice in history)
|
||||
{
|
||||
if (resolution == Resolution.Tick)
|
||||
{
|
||||
foreach (var tick in slice.Ticks[symbol])
|
||||
{
|
||||
Log.Trace("{0}: {1} - {2} / {3}", tick.Time.ToStringInvariant("yyyy-MM-dd HH:mm:ss.fff"), tick.Symbol, tick.BidPrice, tick.AskPrice);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var bar = slice.Bars[symbol];
|
||||
|
||||
Log.Trace("{0}: {1} - O={2}, H={3}, L={4}, C={5}", bar.Time, bar.Symbol, bar.Open, bar.High, bar.Low, bar.Close);
|
||||
}
|
||||
}
|
||||
|
||||
Log.Trace("Data points retrieved: " + historyProvider.DataPointCount);
|
||||
};
|
||||
|
||||
if (throwsException)
|
||||
{
|
||||
Assert.Throws<ArgumentException>(test);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.DoesNotThrow(test);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetsFullDayTickHistory()
|
||||
{
|
||||
var symbol = Symbols.EURUSD;
|
||||
const Resolution resolution = Resolution.Tick;
|
||||
|
||||
var startDate = new DateTime(2016, 11, 1);
|
||||
var endDate = startDate.AddDays(1);
|
||||
|
||||
var brokerage = (FxcmBrokerage)Brokerage;
|
||||
|
||||
var historyProvider = new BrokerageHistoryProvider();
|
||||
historyProvider.SetBrokerage(brokerage);
|
||||
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, null, null, null, null, false, null));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
var requests = new[]
|
||||
{
|
||||
new HistoryRequest(startDate,
|
||||
endDate,
|
||||
typeof(QuoteBar),
|
||||
symbol,
|
||||
resolution,
|
||||
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
|
||||
DateTimeZone.Utc,
|
||||
Resolution.Minute,
|
||||
false,
|
||||
false,
|
||||
DataNormalizationMode.Adjusted,
|
||||
TickType.Quote)
|
||||
};
|
||||
|
||||
var history = historyProvider.GetHistory(requests, TimeZones.Utc);
|
||||
var tickCount = history.SelectMany(slice => slice.Ticks[symbol]).Count();
|
||||
|
||||
stopwatch.Stop();
|
||||
Log.Trace("Download time: " + stopwatch.Elapsed);
|
||||
|
||||
Log.Trace("History ticks returned: " + tickCount);
|
||||
Log.Trace("Data points retrieved: " + historyProvider.DataPointCount);
|
||||
|
||||
Assert.AreEqual(tickCount, historyProvider.DataPointCount);
|
||||
Assert.AreEqual(241233, tickCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Brokerages.Fxcm;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Tests.Brokerages.Fxcm
|
||||
{
|
||||
[TestFixture, Ignore("These tests require a configured and active FXCM practice account")]
|
||||
public partial class FxcmBrokerageTests : BrokerageTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the brokerage under test
|
||||
/// </summary>
|
||||
/// <returns>A connected brokerage instance</returns>
|
||||
protected override IBrokerage CreateBrokerage(IOrderProvider orderProvider, ISecurityProvider securityProvider)
|
||||
{
|
||||
var server = Config.Get("fxcm-server");
|
||||
var terminal = Config.Get("fxcm-terminal");
|
||||
var userName = Config.Get("fxcm-user-name");
|
||||
var password = Config.Get("fxcm-password");
|
||||
var accountId = Config.Get("fxcm-account-id");
|
||||
|
||||
return new FxcmBrokerage(orderProvider, securityProvider, new AggregationManager(), server, terminal, userName, password, accountId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the brokerage and any external resources started in order to create it
|
||||
/// </summary>
|
||||
/// <param name="brokerage">The brokerage instance to be disposed of</param>
|
||||
protected override void DisposeBrokerage(IBrokerage brokerage)
|
||||
{
|
||||
brokerage.Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the data required to test each order type in various cases
|
||||
/// </summary>
|
||||
public static TestCaseData[] OrderParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new TestCaseData(new MarketOrderTestParameters(Symbols.EURUSD)).SetName("MarketOrder"),
|
||||
new TestCaseData(new FxcmLimitOrderTestParameters(Symbols.EURUSD, 1.5m, 0.7m)).SetName("LimitOrder"),
|
||||
new TestCaseData(new FxcmStopMarketOrderTestParameters(Symbols.EURUSD, 1.5m, 0.7m)).SetName("StopMarketOrder"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the symbol to be traded, must be shortable
|
||||
/// </summary>
|
||||
protected override Symbol Symbol => Symbols.EURUSD;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the security type associated with the <see cref="BrokerageTests.Symbol"/>
|
||||
/// </summary>
|
||||
protected override SecurityType SecurityType => SecurityType.Forex;
|
||||
|
||||
/// <summary>
|
||||
/// Returns wether or not the brokers order methods implementation are async
|
||||
/// </summary>
|
||||
protected override bool IsAsync()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current market price of the specified security
|
||||
/// </summary>
|
||||
protected override decimal GetAskPrice(Symbol symbol)
|
||||
{
|
||||
// not used, we use bid/ask prices
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default order quantity
|
||||
/// </summary>
|
||||
protected override decimal GetDefaultQuantity()
|
||||
{
|
||||
// FXCM requires a multiple of 1000 for Forex instruments
|
||||
return 1000;
|
||||
}
|
||||
|
||||
[Test, Ignore("This test requires disconnecting the internet to test for connection resiliency")]
|
||||
public void ClientReconnectsAfterInternetDisconnect()
|
||||
{
|
||||
var brokerage = Brokerage;
|
||||
Assert.IsTrue(brokerage.IsConnected);
|
||||
|
||||
var tenMinutes = TimeSpan.FromMinutes(10);
|
||||
|
||||
Log.Trace("------");
|
||||
Log.Trace("Waiting for internet disconnection ");
|
||||
Log.Trace("------");
|
||||
|
||||
// spin while we manually disconnect the internet
|
||||
while (brokerage.IsConnected)
|
||||
{
|
||||
Thread.Sleep(2500);
|
||||
Console.Write(".");
|
||||
}
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
Log.Trace("------");
|
||||
Log.Trace("Trying to reconnect ");
|
||||
Log.Trace("------");
|
||||
|
||||
// spin until we're reconnected
|
||||
while (!brokerage.IsConnected && stopwatch.Elapsed < tenMinutes)
|
||||
{
|
||||
Thread.Sleep(2500);
|
||||
Console.Write(".");
|
||||
}
|
||||
|
||||
Assert.IsTrue(brokerage.IsConnected);
|
||||
}
|
||||
|
||||
[TestCase("EURGBP", SecurityType.Forex, Market.FXCM, 50000)]
|
||||
[TestCase("EURGBP", SecurityType.Forex, Market.FXCM, -50000)]
|
||||
[TestCase("DE30EUR", SecurityType.Cfd, Market.FXCM, 10)]
|
||||
[TestCase("DE30EUR", SecurityType.Cfd, Market.FXCM, -10)]
|
||||
public void GetCashBalanceIncludesCurrencySwapsForOpenPositions(string ticker, SecurityType securityType, string market, decimal quantity)
|
||||
{
|
||||
// This test requires a practice account with USD account currency
|
||||
|
||||
var brokerage = Brokerage;
|
||||
Assert.IsTrue(brokerage.IsConnected);
|
||||
|
||||
var symbol = Symbol.Create(ticker, securityType, market);
|
||||
var order = new MarketOrder(symbol, quantity, DateTime.UtcNow);
|
||||
PlaceOrderWaitForStatus(order);
|
||||
|
||||
var holdings = brokerage.GetAccountHoldings();
|
||||
var balances = brokerage.GetCashBalance();
|
||||
|
||||
Assert.IsTrue(holdings.Count == 1);
|
||||
|
||||
// account currency
|
||||
Assert.IsTrue(balances.Any(x => x.Currency == "USD"));
|
||||
|
||||
if (securityType == SecurityType.Forex)
|
||||
{
|
||||
// base currency
|
||||
var baseCurrencyCash = balances.Single(x => x.Currency == ticker.Substring(0, 3));
|
||||
Assert.AreEqual(quantity, baseCurrencyCash.Amount);
|
||||
|
||||
// quote currency
|
||||
var quoteCurrencyCash = balances.Single(x => x.Currency == ticker.Substring(3));
|
||||
Assert.AreEqual(-Math.Sign(quantity), Math.Sign(quoteCurrencyCash.Amount));
|
||||
}
|
||||
else if (securityType == SecurityType.Cfd)
|
||||
{
|
||||
Assert.AreEqual(1, balances.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(OrderParameters))]
|
||||
public override void CancelOrders(OrderTestParameters parameters)
|
||||
{
|
||||
base.CancelOrders(parameters);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(OrderParameters))]
|
||||
public override void LongFromZero(OrderTestParameters parameters)
|
||||
{
|
||||
base.LongFromZero(parameters);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(OrderParameters))]
|
||||
public override void CloseFromLong(OrderTestParameters parameters)
|
||||
{
|
||||
base.CloseFromLong(parameters);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(OrderParameters))]
|
||||
public override void ShortFromZero(OrderTestParameters parameters)
|
||||
{
|
||||
base.ShortFromZero(parameters);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(OrderParameters))]
|
||||
public override void CloseFromShort(OrderTestParameters parameters)
|
||||
{
|
||||
base.CloseFromShort(parameters);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(OrderParameters))]
|
||||
public override void ShortFromLong(OrderTestParameters parameters)
|
||||
{
|
||||
base.ShortFromLong(parameters);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(OrderParameters))]
|
||||
public override void LongFromShort(OrderTestParameters parameters)
|
||||
{
|
||||
base.LongFromShort(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Brokerages.Fxcm;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
|
||||
namespace QuantConnect.Tests.Brokerages.Fxcm
|
||||
{
|
||||
public class FxcmLimitOrderTestParameters : LimitOrderTestParameters
|
||||
{
|
||||
public FxcmLimitOrderTestParameters(Symbol symbol, decimal highLimit, decimal lowLimit)
|
||||
: base(symbol, highLimit, lowLimit)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ModifyOrderToFill(IBrokerage brokerage, Order order, decimal lastMarketPrice)
|
||||
{
|
||||
// FXCM Buy Limit orders will be rejected if the limit price is above the market price
|
||||
// FXCM Sell Limit orders will be rejected if the limit price is below the market price
|
||||
|
||||
var limit = (LimitOrder)order;
|
||||
var previousLimit = limit.LimitPrice;
|
||||
|
||||
var fxcmBrokerage = (FxcmBrokerage)brokerage;
|
||||
var quotes = fxcmBrokerage.GetBidAndAsk(new List<string> { new FxcmSymbolMapper().GetBrokerageSymbol(order.Symbol) });
|
||||
|
||||
if (order.Quantity > 0)
|
||||
{
|
||||
// for limit buys we need to increase the limit price
|
||||
// buy limit price must be at bid price or below
|
||||
var bidPrice = Convert.ToDecimal(quotes.Single().BidPrice);
|
||||
Log.Trace("FxcmLimitOrderTestParameters.ModifyOrderToFill(): Bid: " + bidPrice);
|
||||
limit.LimitPrice = Math.Max(previousLimit, Math.Min(bidPrice, limit.LimitPrice * 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
// for limit sells we need to decrease the limit price
|
||||
// sell limit price must be at ask price or above
|
||||
var askPrice = Convert.ToDecimal(quotes.Single().AskPrice);
|
||||
Log.Trace("FxcmLimitOrderTestParameters.ModifyOrderToFill(): Ask: " + askPrice);
|
||||
limit.LimitPrice = Math.Min(previousLimit, Math.Max(askPrice, limit.LimitPrice / 2));
|
||||
}
|
||||
|
||||
return limit.LimitPrice != previousLimit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Brokerages.Fxcm;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
|
||||
namespace QuantConnect.Tests.Brokerages.Fxcm
|
||||
{
|
||||
public class FxcmStopMarketOrderTestParameters : StopMarketOrderTestParameters
|
||||
{
|
||||
public FxcmStopMarketOrderTestParameters(Symbol symbol, decimal highLimit, decimal lowLimit)
|
||||
: base(symbol, highLimit, lowLimit)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ModifyOrderToFill(IBrokerage brokerage, Order order, decimal lastMarketPrice)
|
||||
{
|
||||
// FXCM Buy StopMarket orders will be rejected if the stop price is below the market price
|
||||
// FXCM Sell StopMarket orders will be rejected if the stop price is above the market price
|
||||
|
||||
var stop = (StopMarketOrder)order;
|
||||
var previousStop = stop.StopPrice;
|
||||
|
||||
var fxcmBrokerage = (FxcmBrokerage)brokerage;
|
||||
var quotes = fxcmBrokerage.GetBidAndAsk(new List<string> { new FxcmSymbolMapper().GetBrokerageSymbol(order.Symbol) });
|
||||
|
||||
if (order.Quantity > 0)
|
||||
{
|
||||
// for stop buys we need to decrease the stop price
|
||||
// buy stop price must be strictly above ask price
|
||||
var askPrice = Convert.ToDecimal(quotes.Single().AskPrice);
|
||||
Log.Trace("FxcmStopMarketOrderTestParameters.ModifyOrderToFill(): Ask: " + askPrice);
|
||||
stop.StopPrice = Math.Min(previousStop, Math.Max(askPrice, stop.StopPrice / 2) + 0.00001m);
|
||||
}
|
||||
else
|
||||
{
|
||||
// for stop sells we need to increase the stop price
|
||||
// sell stop price must be strictly below bid price
|
||||
var bidPrice = Convert.ToDecimal(quotes.Single().BidPrice);
|
||||
Log.Trace("FxcmStopMarketOrderTestParameters.ModifyOrderToFill(): Bid: " + bidPrice);
|
||||
stop.StopPrice = Math.Max(previousStop, Math.Min(bidPrice, stop.StopPrice * 2) - 0.00001m);
|
||||
}
|
||||
|
||||
return stop.StopPrice != previousStop;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using QuantConnect.Brokerages.Fxcm;
|
||||
|
||||
namespace QuantConnect.Tests.Brokerages.Fxcm
|
||||
{
|
||||
[TestFixture]
|
||||
public class FxcmSymbolMapperTests
|
||||
{
|
||||
[Test]
|
||||
public void ReturnsCorrectLeanSymbol()
|
||||
{
|
||||
var mapper = new FxcmSymbolMapper();
|
||||
|
||||
var symbol = mapper.GetLeanSymbol("EUR/USD", SecurityType.Forex, Market.FXCM);
|
||||
Assert.AreEqual("EURUSD", symbol.Value);
|
||||
Assert.AreEqual(SecurityType.Forex, symbol.ID.SecurityType);
|
||||
Assert.AreEqual(Market.FXCM, symbol.ID.Market);
|
||||
|
||||
symbol = mapper.GetLeanSymbol("GER30", SecurityType.Cfd, Market.FXCM);
|
||||
Assert.AreEqual("DE30EUR", symbol.Value);
|
||||
Assert.AreEqual(SecurityType.Cfd, symbol.ID.SecurityType);
|
||||
Assert.AreEqual(Market.FXCM, symbol.ID.Market);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReturnsCorrectBrokerageSymbol()
|
||||
{
|
||||
var mapper = new FxcmSymbolMapper();
|
||||
|
||||
var symbol = Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM);
|
||||
var brokerageSymbol = mapper.GetBrokerageSymbol(symbol);
|
||||
Assert.AreEqual("EUR/USD", brokerageSymbol);
|
||||
|
||||
symbol = Symbol.Create("DE30EUR", SecurityType.Cfd, Market.FXCM);
|
||||
brokerageSymbol = mapper.GetBrokerageSymbol(symbol);
|
||||
Assert.AreEqual("GER30", brokerageSymbol);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsOnNullOrEmptySymbol()
|
||||
{
|
||||
var mapper = new FxcmSymbolMapper();
|
||||
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetLeanSymbol(null, SecurityType.Forex, Market.FXCM));
|
||||
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetLeanSymbol("", SecurityType.Forex, Market.FXCM));
|
||||
|
||||
var symbol = Symbol.Empty;
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetBrokerageSymbol(symbol));
|
||||
|
||||
symbol = null;
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetBrokerageSymbol(symbol));
|
||||
|
||||
symbol = Symbol.Create("", SecurityType.Forex, Market.FXCM);
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetBrokerageSymbol(symbol));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsOnUnknownSymbol()
|
||||
{
|
||||
var mapper = new FxcmSymbolMapper();
|
||||
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetLeanSymbol("ABC/USD", SecurityType.Forex, Market.FXCM));
|
||||
|
||||
var symbol = Symbol.Create("ABCUSD", SecurityType.Forex, Market.FXCM);
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetBrokerageSymbol(symbol));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsOnInvalidSecurityType()
|
||||
{
|
||||
var mapper = new FxcmSymbolMapper();
|
||||
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetLeanSymbol("AAPL", SecurityType.Equity, Market.FXCM));
|
||||
|
||||
var symbol = Symbol.Create("AAPL", SecurityType.Equity, Market.FXCM);
|
||||
Assert.Throws<ArgumentException>(() => mapper.GetBrokerageSymbol(symbol));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChecksForKnownSymbols()
|
||||
{
|
||||
#pragma warning disable 0618 // This test requires implicit operators
|
||||
var mapper = new FxcmSymbolMapper();
|
||||
|
||||
Assert.IsFalse(mapper.IsKnownBrokerageSymbol(null));
|
||||
Assert.IsFalse(mapper.IsKnownBrokerageSymbol(""));
|
||||
Assert.IsTrue(mapper.IsKnownBrokerageSymbol("EUR/USD"));
|
||||
Assert.IsTrue(mapper.IsKnownBrokerageSymbol("GER30"));
|
||||
Assert.IsFalse(mapper.IsKnownBrokerageSymbol("ABC/USD"));
|
||||
|
||||
Assert.IsFalse(mapper.IsKnownLeanSymbol(null));
|
||||
Assert.IsFalse(mapper.IsKnownLeanSymbol(""));
|
||||
Assert.IsFalse(mapper.IsKnownLeanSymbol(Symbol.Empty));
|
||||
Assert.IsTrue(mapper.IsKnownLeanSymbol(Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM)));
|
||||
Assert.IsFalse(mapper.IsKnownLeanSymbol(Symbol.Create("ABCUSD", SecurityType.Forex, Market.FXCM)));
|
||||
Assert.IsFalse(mapper.IsKnownLeanSymbol(Symbol.Create("EURUSD", SecurityType.Cfd, Market.FXCM)));
|
||||
Assert.IsFalse(mapper.IsKnownLeanSymbol(Symbol.Create("DE30EUR", SecurityType.Forex, Market.FXCM)));
|
||||
#pragma warning restore 0618
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Data.Consolidators;
|
||||
using QuantConnect.Data.Market;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<RootNamespace>QuantConnect.Tests</RootNamespace>
|
||||
<AssemblyName>QuantConnect.Tests</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
@@ -55,7 +55,6 @@
|
||||
<PackageReference Include="Common.Logging.Core" Version="3.4.1" />
|
||||
<PackageReference Include="Deedle" Version="2.1.0" />
|
||||
<PackageReference Include="DotNetZip" Version="1.13.3" />
|
||||
<PackageReference Include="IKVM" Version="8.1.5717.0" />
|
||||
<PackageReference Include="LaunchDarkly.EventSource" Version="3.3.2" />
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.6.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
|
||||
@@ -108,13 +107,6 @@
|
||||
<HintPath>..\Brokerages\Fxcm\QuantConnect.Fxcm.dll</HintPath>
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.IdentityModel" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Common\Properties\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using QuantConnect.Data.Custom;
|
||||
using QuantConnect.ToolBox;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace QuantConnect.Tests.ToolBox
|
||||
{
|
||||
/// <summary>
|
||||
/// The rationality is to test the main functionalities:
|
||||
/// - Data is correctly parsed.
|
||||
/// - Data is correctly saved.
|
||||
/// </summary>
|
||||
[TestFixture, Ignore("FXCM API goes down on weekends")]
|
||||
public class FxcmVolumeDownloaderTest
|
||||
{
|
||||
private string _dataDirectory;
|
||||
private List<string> _testingTempFolders = new List<string>();
|
||||
|
||||
private FxcmVolumeDownloader _downloader;
|
||||
private readonly Symbol _eurusd = Symbol.Create("EURUSD", SecurityType.Base, Market.FXCM);
|
||||
|
||||
[SetUp]
|
||||
public void SetUpTemporatyFolder()
|
||||
{
|
||||
var randomFolder = Guid.NewGuid().ToStringInvariant("N").Substring(startIndex: 0, length: 8);
|
||||
var assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
_dataDirectory = Path.Combine(assemblyFolder, randomFolder);
|
||||
_downloader = new FxcmVolumeDownloader(_dataDirectory);
|
||||
_testingTempFolders.Add(_dataDirectory);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void CleanTemporaryFolder()
|
||||
{
|
||||
foreach (var testingTempFolder in _testingTempFolders)
|
||||
{
|
||||
if (Directory.Exists(testingTempFolder))
|
||||
{
|
||||
Directory.Delete(testingTempFolder, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("./TestData/fxVolumeDaily.csv", "EURUSD", Resolution.Daily, "2016-12-01", "2017-01-30")]
|
||||
[TestCase("./TestData/fxVolumeHourly.csv", "USDJPY", Resolution.Hour, "2014-12-24", "2015-01-05")]
|
||||
[TestCase("./TestData/fxVolumeMinute.csv", "EURUSD", Resolution.Minute, "2012-11-23", "2012-11-27")]
|
||||
public void DataIsCorrectlyParsed(string testingFilePath, string ticker, Resolution resolution, string startDate, string endDate)
|
||||
{
|
||||
//Arrange
|
||||
var expectedData = File.ReadAllLines(testingFilePath)
|
||||
.Skip(count: 1) // Skip headers.
|
||||
.Select(x => x.Split(','))
|
||||
.ToArray();
|
||||
var symbol = Symbol.Create(ticker, SecurityType.Base, Market.FXCM);
|
||||
var startUtc = Parse.DateTimeExact(startDate, "yyyy-MM-dd");
|
||||
var endUtc = Parse.DateTimeExact(endDate, "yyyy-MM-dd");
|
||||
//Act
|
||||
var actualData = _downloader.Get(symbol, resolution, startUtc,
|
||||
endUtc).Cast<FxcmVolume>().ToArray();
|
||||
//Assert
|
||||
Assert.AreEqual(expectedData.Length, actualData.Length);
|
||||
for (var i = 0; i < expectedData.Length - 1; i++)
|
||||
{
|
||||
Assert.AreEqual(expectedData[i][0], actualData[i].Time.ToStringInvariant("yyyy/MM/dd HH:mm"));
|
||||
Assert.AreEqual(expectedData[i][1], actualData[i].Value.ToString(CultureInfo.InvariantCulture));
|
||||
Assert.AreEqual(expectedData[i][2], actualData[i].Transactions.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("GBPUSD", Resolution.Daily, "2015-11-27", 20)]
|
||||
[TestCase("USDCAD", Resolution.Hour, "2016-09-15", 5)]
|
||||
[TestCase("EURJPY", Resolution.Minute, "2015-01-26", 2)]
|
||||
public void ParsedDataIsCorrectlySaved(string ticker, Resolution resolution, string startDate, int requestLength)
|
||||
{
|
||||
// Arrange
|
||||
var symbol = Symbol.Create(ticker, SecurityType.Base, Market.FXCM);
|
||||
var startUtc = Parse.DateTimeExact(startDate, "yyyy-MM-dd");
|
||||
var endUtc = startUtc.AddDays(requestLength);
|
||||
var data = _downloader.Get(symbol, resolution, startUtc, endUtc);
|
||||
// Act
|
||||
var writer = new FxcmVolumeWriter(resolution, symbol, _dataDirectory);
|
||||
writer.Write(data);
|
||||
// Assert
|
||||
var expectedData = data.Cast<FxcmVolume>().ToArray();
|
||||
var expectedFolder = Path.Combine(_dataDirectory, $"forex/fxcm/{resolution.ToLower()}");
|
||||
if (resolution == Resolution.Minute)
|
||||
{
|
||||
expectedFolder = Path.Combine(expectedFolder, symbol.Value.ToLowerInvariant());
|
||||
}
|
||||
Assert.True(Directory.Exists(expectedFolder));
|
||||
|
||||
if (resolution == Resolution.Minute)
|
||||
{
|
||||
var zipFiles = Directory.GetFiles(expectedFolder, "*_volume.zip").Length;
|
||||
// Minus one because the Downloader starts one day earlier.
|
||||
Assert.AreEqual(requestLength + 1, zipFiles);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expectedFilename = $"{symbol.Value.ToLowerInvariant()}_volume.zip";
|
||||
Assert.True(File.Exists(Path.Combine(expectedFolder, expectedFilename)));
|
||||
}
|
||||
|
||||
var actualdata = FxcmVolumeAuxiliaryMethods.ReadZipFolderData(expectedFolder);
|
||||
Assert.AreEqual(expectedData.Length, actualdata.Count);
|
||||
|
||||
var lines = actualdata.Count;
|
||||
for (var i = 0; i < lines - 1; i++)
|
||||
{
|
||||
Assert.AreEqual(expectedData[i].Value, Parse.Long(actualdata[i][1]));
|
||||
Assert.AreEqual(expectedData[i].Transactions, Parse.Int(actualdata[i][2]));
|
||||
}
|
||||
}
|
||||
|
||||
[Ignore("Long test")]
|
||||
[Test]
|
||||
public void RequestWithMoreThan10KMinuteObservationIsCorrectlySaved()
|
||||
{
|
||||
// Arrange
|
||||
var resolution = Resolution.Minute;
|
||||
var startDate = new DateTime(year: 2013, month: 04, day: 01);
|
||||
var endDate = startDate.AddMonths(months: 1);
|
||||
// Act
|
||||
_downloader.Run(_eurusd, resolution, startDate, endDate);
|
||||
// Assert
|
||||
var outputFolder = Path.Combine(_dataDirectory, "forex/fxcm/minute");
|
||||
var files = Directory.GetFiles(outputFolder, "*_volume.zip", SearchOption.AllDirectories);
|
||||
Assert.AreEqual(expected: 27, actual: files.Length);
|
||||
}
|
||||
|
||||
[Ignore("Long test")]
|
||||
[Test]
|
||||
public void RequestWithMoreThan10KHourlyObservationIsCorrectlySaved()
|
||||
{
|
||||
// Arrange
|
||||
var resolution = Resolution.Hour;
|
||||
var startDate = new DateTime(year: 2014, month: 01, day: 01);
|
||||
var endDate = startDate.AddYears(value: 3);
|
||||
// Act
|
||||
_downloader.Run(_eurusd, resolution, startDate, endDate);
|
||||
// Assert
|
||||
var outputFile = Path.Combine(_dataDirectory, "forex/fxcm/hour/eurusd_volume.zip");
|
||||
var observationsCount = FxcmVolumeAuxiliaryMethods.ReadZipFileData(outputFile).Count;
|
||||
// 3 years x 52 weeks x 5 days x 24 hours = 18720 hours at least.
|
||||
Assert.True(observationsCount >= 18720, $"Actual observations: {observationsCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using com.fxcm.external.api.transport;
|
||||
using com.fxcm.external.api.transport.listeners;
|
||||
using com.fxcm.fix;
|
||||
using com.fxcm.fix.pretrade;
|
||||
using com.fxcm.messaging;
|
||||
using java.util;
|
||||
using QuantConnect.Brokerages.Fxcm;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using TimeZone = java.util.TimeZone;
|
||||
|
||||
namespace QuantConnect.ToolBox.FxcmDownloader
|
||||
{
|
||||
/// <summary>
|
||||
/// FXCM Data Downloader class
|
||||
/// </summary>
|
||||
public class FxcmDataDownloader : IDataDownloader, IGenericMessageListener, IStatusMessageListener
|
||||
{
|
||||
private readonly FxcmSymbolMapper _symbolMapper = new FxcmSymbolMapper();
|
||||
private readonly string _server;
|
||||
private readonly string _terminal;
|
||||
private readonly string _userName;
|
||||
private readonly string _password;
|
||||
|
||||
|
||||
private IGateway _gateway;
|
||||
private readonly object _locker = new object();
|
||||
private string _currentRequest;
|
||||
private const int ResponseTimeout = 2500;
|
||||
private readonly Dictionary<string, AutoResetEvent> _mapRequestsToAutoResetEvents = new Dictionary<string, AutoResetEvent>();
|
||||
private readonly Dictionary<string, TradingSecurity> _fxcmInstruments = new Dictionary<string, TradingSecurity>();
|
||||
private readonly IList<BaseData> _currentBaseData = new List<BaseData>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FxcmDataDownloader"/> class
|
||||
/// </summary>
|
||||
public FxcmDataDownloader(string server, string terminal, string userName, string password)
|
||||
{
|
||||
_server = server;
|
||||
_terminal = terminal;
|
||||
_userName = userName;
|
||||
_password = password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Java Date value to a UTC DateTime value
|
||||
/// </summary>
|
||||
/// <param name="javaDate">The Java date</param>
|
||||
/// <returns>A UTC DateTime value</returns>
|
||||
private static DateTime FromJavaDateUtc(Date javaDate)
|
||||
{
|
||||
var cal = Calendar.getInstance();
|
||||
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
cal.setTime(javaDate);
|
||||
|
||||
// note that the Month component of java.util.Date
|
||||
// from 0-11 (i.e. Jan == 0)
|
||||
return new DateTime(cal.get(Calendar.YEAR),
|
||||
cal.get(Calendar.MONTH) + 1,
|
||||
cal.get(Calendar.DAY_OF_MONTH),
|
||||
cal.get(Calendar.HOUR_OF_DAY),
|
||||
cal.get(Calendar.MINUTE),
|
||||
cal.get(Calendar.SECOND),
|
||||
cal.get(Calendar.MILLISECOND));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if downloader can get the data for the Lean symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">The Lean symbol</param>
|
||||
/// <returns>Returns true if the symbol is available</returns>
|
||||
public bool HasSymbol(string symbol)
|
||||
{
|
||||
return _symbolMapper.IsKnownLeanSymbol(Symbol.Create(symbol, GetSecurityType(symbol), Market.FXCM));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the security type for the specified Lean symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">The Lean symbol</param>
|
||||
/// <returns>The security type</returns>
|
||||
public SecurityType GetSecurityType(string symbol)
|
||||
{
|
||||
return _symbolMapper.GetLeanSecurityType(symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get historical data enumerable for a single symbol, type and resolution given this start and end time (in UTC).
|
||||
/// </summary>
|
||||
/// <param name="symbol">Symbol for the data we're looking for.</param>
|
||||
/// <param name="resolution">Resolution of the data request</param>
|
||||
/// <param name="startUtc">Start time of the data in UTC</param>
|
||||
/// <param name="endUtc">End time of the data in UTC</param>
|
||||
/// <returns>Enumerable of base data for this symbol</returns>
|
||||
public IEnumerable<BaseData> Get(Symbol symbol, Resolution resolution, DateTime startUtc, DateTime endUtc)
|
||||
{
|
||||
if (!_symbolMapper.IsKnownLeanSymbol(symbol))
|
||||
throw new ArgumentException("Invalid symbol requested: " + symbol.Value);
|
||||
|
||||
if (symbol.ID.SecurityType != SecurityType.Forex && symbol.ID.SecurityType != SecurityType.Cfd)
|
||||
throw new NotSupportedException("SecurityType not available: " + symbol.ID.SecurityType);
|
||||
|
||||
if (endUtc <= startUtc)
|
||||
throw new ArgumentException("The end date must be greater than the start date.");
|
||||
|
||||
Console.WriteLine("Logging in...");
|
||||
|
||||
// create the gateway
|
||||
_gateway = GatewayFactory.createGateway();
|
||||
|
||||
// register the message listeners with the gateway
|
||||
_gateway.registerGenericMessageListener(this);
|
||||
_gateway.registerStatusMessageListener(this);
|
||||
|
||||
// create local login properties
|
||||
var loginProperties = new FXCMLoginProperties(_userName, _password, _terminal, _server);
|
||||
|
||||
// log in
|
||||
_gateway.login(loginProperties);
|
||||
|
||||
// initialize session
|
||||
RequestTradingSessionStatus();
|
||||
|
||||
Console.WriteLine($"Downloading {resolution.ToStringInvariant()} data from {startUtc.ToStringInvariant("yyyyMMdd HH:mm:ss")} to {endUtc.ToStringInvariant("yyyyMMdd HH:mm:ss")}...");
|
||||
|
||||
// Find best FXCM parameters
|
||||
var interval = FxcmBrokerage.ToFxcmInterval(resolution);
|
||||
|
||||
var totalTicks = (endUtc - startUtc).Ticks;
|
||||
|
||||
// download data
|
||||
var totalBaseData = new List<BaseData>();
|
||||
|
||||
var end = endUtc;
|
||||
|
||||
do //
|
||||
{
|
||||
//show progress
|
||||
progressBar(Math.Abs((end - endUtc).Ticks), totalTicks, Console.WindowWidth / 2,'█');
|
||||
_currentBaseData.Clear();
|
||||
|
||||
var mdr = new MarketDataRequest();
|
||||
mdr.setSubscriptionRequestType(SubscriptionRequestTypeFactory.SNAPSHOT);
|
||||
mdr.setResponseFormat(IFixMsgTypeDefs.__Fields.MSGTYPE_FXCMRESPONSE);
|
||||
mdr.setFXCMTimingInterval(interval);
|
||||
mdr.setMDEntryTypeSet(MarketDataRequest.MDENTRYTYPESET_ALL);
|
||||
|
||||
mdr.setFXCMStartDate(new UTCDate(FxcmBrokerage.ToJavaDateUtc(startUtc)));
|
||||
mdr.setFXCMStartTime(new UTCTimeOnly(FxcmBrokerage.ToJavaDateUtc(startUtc)));
|
||||
mdr.setFXCMEndDate(new UTCDate(FxcmBrokerage.ToJavaDateUtc(end)));
|
||||
mdr.setFXCMEndTime(new UTCTimeOnly(FxcmBrokerage.ToJavaDateUtc(end)));
|
||||
mdr.addRelatedSymbol(_fxcmInstruments[_symbolMapper.GetBrokerageSymbol(symbol)]);
|
||||
|
||||
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.sendMessage(mdr);
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(1000 * 5))
|
||||
{
|
||||
// no response, exit
|
||||
break;
|
||||
}
|
||||
|
||||
// Add data
|
||||
totalBaseData.InsertRange(0, _currentBaseData.Where(x => x.Time.Date >= startUtc.Date));
|
||||
|
||||
if (end != _currentBaseData[0].Time)
|
||||
{
|
||||
// new end date = first datapoint date.
|
||||
end = _currentBaseData[0].Time;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} while (end > startUtc);
|
||||
|
||||
|
||||
Console.WriteLine("\nLogging out...");
|
||||
|
||||
// log out
|
||||
_gateway.logout();
|
||||
|
||||
// remove the message listeners
|
||||
_gateway.removeGenericMessageListener(this);
|
||||
_gateway.removeStatusMessageListener(this);
|
||||
|
||||
return totalBaseData.ToList();
|
||||
|
||||
}
|
||||
|
||||
private void RequestTradingSessionStatus()
|
||||
{
|
||||
// Note: requestTradingSessionStatus() MUST be called just after login
|
||||
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.requestTradingSessionStatus();
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
if (!autoResetEvent.WaitOne(ResponseTimeout))
|
||||
throw new TimeoutException("FxcmBrokerage.LoadInstruments(): Operation took " +
|
||||
$"longer than {((decimal) ResponseTimeout / 1000).ToStringInvariant()} seconds."
|
||||
);
|
||||
}
|
||||
|
||||
#region IGenericMessageListener implementation
|
||||
|
||||
/// <summary>
|
||||
/// Receives generic messages from the FXCM API
|
||||
/// </summary>
|
||||
/// <param name="message">Generic message received</param>
|
||||
public void messageArrived(ITransportable message)
|
||||
{
|
||||
// Dispatch message to specific handler
|
||||
lock (_locker)
|
||||
{
|
||||
if (message is TradingSessionStatus)
|
||||
OnTradingSessionStatus((TradingSessionStatus)message);
|
||||
|
||||
else if (message is MarketDataSnapshot)
|
||||
OnMarketDataSnapshot((MarketDataSnapshot)message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TradingSessionStatus message handler
|
||||
/// </summary>
|
||||
private void OnTradingSessionStatus(TradingSessionStatus message)
|
||||
{
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
// load instrument list into a dictionary
|
||||
var securities = message.getSecurities();
|
||||
while (securities.hasMoreElements())
|
||||
{
|
||||
var security = (TradingSecurity)securities.nextElement();
|
||||
_fxcmInstruments[security.getSymbol()] = security;
|
||||
}
|
||||
|
||||
_mapRequestsToAutoResetEvents[_currentRequest].Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MarketDataSnapshot message handler
|
||||
/// </summary>
|
||||
private void OnMarketDataSnapshot(MarketDataSnapshot message)
|
||||
{
|
||||
if (message.getRequestID() == _currentRequest)
|
||||
{
|
||||
var securityType = _symbolMapper.GetBrokerageSecurityType(message.getInstrument().getSymbol());
|
||||
var symbol = _symbolMapper.GetLeanSymbol(message.getInstrument().getSymbol(), securityType, Market.FXCM);
|
||||
var time = FromJavaDateUtc(message.getDate().toDate());
|
||||
|
||||
|
||||
if (message.getFXCMTimingInterval() == FXCMTimingIntervalFactory.TICK)
|
||||
{
|
||||
var bid = Convert.ToDecimal(message.getBidClose());
|
||||
var ask = Convert.ToDecimal(message.getAskClose());
|
||||
|
||||
var tick = new Tick(time, symbol, bid, ask);
|
||||
|
||||
//Add tick
|
||||
_currentBaseData.Add(tick);
|
||||
|
||||
}
|
||||
else // it bars
|
||||
{
|
||||
var open = Convert.ToDecimal((message.getBidOpen() + message.getAskOpen()) / 2);
|
||||
var high = Convert.ToDecimal((message.getBidHigh() + message.getAskHigh()) / 2);
|
||||
var low = Convert.ToDecimal((message.getBidLow() + message.getAskLow()) / 2);
|
||||
var close = Convert.ToDecimal((message.getBidClose() + message.getAskClose()) / 2);
|
||||
|
||||
var bar = new TradeBar(time, symbol, open, high, low, close, 0);
|
||||
|
||||
// add bar to list
|
||||
_currentBaseData.Add(bar);
|
||||
}
|
||||
|
||||
if (message.getFXCMContinuousFlag() == IFixValueDefs.__Fields.FXCMCONTINUOUS_END)
|
||||
{
|
||||
_mapRequestsToAutoResetEvents[_currentRequest].Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region IStatusMessageListener implementation
|
||||
|
||||
/// <summary>
|
||||
/// Receives status messages from the FXCM API
|
||||
/// </summary>
|
||||
/// <param name="message">Status message received</param>
|
||||
public void messageArrived(ISessionStatus message)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Aggregates a list of ticks at the requested resolution
|
||||
/// </summary>
|
||||
/// <param name="symbol"></param>
|
||||
/// <param name="ticks"></param>
|
||||
/// <param name="resolution"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<TradeBar> AggregateTicks(Symbol symbol, IEnumerable<Tick> ticks, TimeSpan resolution)
|
||||
{
|
||||
return
|
||||
(from t in ticks
|
||||
group t by t.Time.RoundDown(resolution)
|
||||
into g
|
||||
select new TradeBar
|
||||
{
|
||||
Symbol = symbol,
|
||||
Time = g.Key,
|
||||
Open = g.First().LastPrice,
|
||||
High = g.Max(t => t.LastPrice),
|
||||
Low = g.Min(t => t.LastPrice),
|
||||
Close = g.Last().LastPrice
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#region Console Helper
|
||||
|
||||
/// <summary>
|
||||
/// Draw a progress bar
|
||||
/// </summary>
|
||||
/// <param name="complete"></param>
|
||||
/// <param name="maxVal"></param>
|
||||
/// <param name="barSize"></param>
|
||||
/// <param name="progressCharacter"></param>
|
||||
private static void progressBar(long complete, long maxVal, long barSize, char progressCharacter)
|
||||
{
|
||||
|
||||
decimal p = (decimal)complete / (decimal)maxVal;
|
||||
int chars = (int)Math.Floor(p / ((decimal)1 / (decimal)barSize));
|
||||
string bar = string.Empty;
|
||||
bar = bar.PadLeft(chars, progressCharacter);
|
||||
bar = bar.PadRight(Convert.ToInt32(barSize)-1);
|
||||
|
||||
Console.Write($"\r[{bar}] {(p * 100).ToStringInvariant("N2")}%");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using org.apache.log4j;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.ToolBox.FxcmDownloader
|
||||
{
|
||||
public static class FxcmDownloaderProgram
|
||||
{
|
||||
/// <summary>
|
||||
/// Primary entry point to the program
|
||||
/// </summary>
|
||||
public static void FxcmDownloader(IList<string> tickers, string resolution, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
if (resolution.IsNullOrEmpty() || tickers.IsNullOrEmpty())
|
||||
{
|
||||
Console.WriteLine("FxcmDownloader ERROR: '--tickers=' or '--resolution=' parameter is missing");
|
||||
Console.WriteLine("--tickers=eg EURUSD,USDJPY");
|
||||
Console.WriteLine("--resolution=Second/Minute/Hour/Daily/All");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
Logger.getRootLogger().setLevel(Level.ERROR);
|
||||
BasicConfigurator.configure(new FileAppender(new SimpleLayout(), "FxcmDownloader.log", append: false));
|
||||
|
||||
var allResolutions = resolution.ToLowerInvariant() == "all";
|
||||
var castResolution = allResolutions ? Resolution.Tick : (Resolution)Enum.Parse(typeof(Resolution), resolution);
|
||||
endDate = endDate.AddDays(1).AddMilliseconds(-1);
|
||||
|
||||
|
||||
// Load settings from config.json
|
||||
var dataDirectory = Config.Get("data-directory", "../../../Data");
|
||||
var server = Config.Get("fxcm-server", "http://www.fxcorporate.com/Hosts.jsp");
|
||||
var terminal = Config.Get("fxcm-terminal", "Demo");
|
||||
var userName = Config.Get("fxcm-user-name", "username");
|
||||
var password = Config.Get("fxcm-password", "password");
|
||||
|
||||
// Download the data
|
||||
const string market = Market.FXCM;
|
||||
var downloader = new FxcmDataDownloader(server, terminal, userName, password);
|
||||
|
||||
foreach (var ticker in tickers)
|
||||
{
|
||||
if (!downloader.HasSymbol(ticker))
|
||||
throw new ArgumentException("The ticker " + ticker + " is not available.");
|
||||
}
|
||||
|
||||
foreach (var ticker in tickers)
|
||||
{
|
||||
var securityType = downloader.GetSecurityType(ticker);
|
||||
var symbol = Symbol.Create(ticker, securityType, market);
|
||||
|
||||
var data = downloader.Get(symbol, castResolution, startDate, endDate);
|
||||
|
||||
if (allResolutions)
|
||||
{
|
||||
var ticks = data.Cast<Tick>().ToList();
|
||||
|
||||
// Save the data (second resolution)
|
||||
var writer = new LeanDataWriter(castResolution, symbol, dataDirectory);
|
||||
writer.Write(ticks);
|
||||
|
||||
// Save the data (other resolutions)
|
||||
foreach (var res in new[] { Resolution.Second, Resolution.Minute, Resolution.Hour, Resolution.Daily })
|
||||
{
|
||||
var resData = FxcmDataDownloader.AggregateTicks(symbol, ticks, res.ToTimeSpan());
|
||||
|
||||
writer = new LeanDataWriter(res, symbol, dataDirectory);
|
||||
writer.Write(resData);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save the data (single resolution)
|
||||
var writer = new LeanDataWriter(castResolution, symbol, dataDirectory);
|
||||
writer.Write(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Log.Error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Ionic.Zip;
|
||||
using QuantConnect.Data.Custom;
|
||||
|
||||
namespace QuantConnect.ToolBox
|
||||
{
|
||||
public static class FxcmVolumeAuxiliaryMethods
|
||||
{
|
||||
public static IEnumerable<FxcmVolume> GetFxcmVolumeFromZip(string dataZipFile)
|
||||
{
|
||||
var output = new List<FxcmVolume>();
|
||||
var data = ReadZipFileData(dataZipFile);
|
||||
foreach (var obs in data)
|
||||
{
|
||||
var time = DateTime.ParseExact(obs[0], "yyyyMMdd HH:mm", CultureInfo.InvariantCulture);
|
||||
output.Add(new FxcmVolume
|
||||
{
|
||||
DataType = MarketDataType.Base,
|
||||
Time = time,
|
||||
Value = Parse.Long(obs[1]),
|
||||
Transactions = Parse.Int(obs[2])
|
||||
});
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public static List<string[]> ReadZipFolderData(string outputFolder)
|
||||
{
|
||||
var actualdata = new List<string[]>();
|
||||
var files = Directory.GetFiles(outputFolder, "*.zip");
|
||||
foreach (var file in files)
|
||||
{
|
||||
actualdata.AddRange(ReadZipFileData(file));
|
||||
}
|
||||
return actualdata;
|
||||
}
|
||||
|
||||
public static List<string[]> ReadZipFileData(string dataZipFile)
|
||||
{
|
||||
var actualdata = new List<string[]>();
|
||||
using (ZipFile zip = ZipFile.Read(dataZipFile))
|
||||
using (var entryReader = new StreamReader(zip.First().OpenReader()))
|
||||
{
|
||||
while (!entryReader.EndOfStream)
|
||||
{
|
||||
actualdata.Add(entryReader.ReadLine().Split(','));
|
||||
}
|
||||
}
|
||||
return actualdata;
|
||||
}
|
||||
|
||||
public static DateTime? GetLastAvailableDateOfData(Symbol symbol, Resolution resolution, string folderPath)
|
||||
{
|
||||
if (!Directory.Exists(folderPath)) return null;
|
||||
DateTime? lastAvailableDate = null;
|
||||
switch (resolution)
|
||||
{
|
||||
case Resolution.Daily:
|
||||
case Resolution.Hour:
|
||||
var expectedFilePath = Path.Combine(folderPath, $"{symbol.Value.ToLowerInvariant()}_volume.zip"
|
||||
);
|
||||
if (File.Exists(expectedFilePath))
|
||||
{
|
||||
var lastStrDate = ReadZipFileData(expectedFilePath).Last() // last observation
|
||||
.First() // first string (date)
|
||||
.Substring(startIndex: 0, length: 8);
|
||||
lastAvailableDate = DateTime.ParseExact(lastStrDate, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
|
||||
}
|
||||
break;
|
||||
case Resolution.Minute:
|
||||
var lastFileDate = Directory
|
||||
.GetFiles(folderPath, "*_volume.zip")
|
||||
.OrderBy(f => f)
|
||||
.LastOrDefault();
|
||||
if (lastFileDate != null)
|
||||
{
|
||||
lastFileDate = Path.GetFileNameWithoutExtension(lastFileDate)
|
||||
.Substring(startIndex: 0, length: 8);
|
||||
lastAvailableDate = DateTime.ParseExact(lastFileDate, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return lastAvailableDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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 QuantConnect.Configuration;
|
||||
using QuantConnect.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.ToolBox.FxcmVolumeDownload
|
||||
{
|
||||
public static class FxcmVolumeDownloadProgram
|
||||
{
|
||||
public static void FxcmVolumeDownload(IList<string> tickers, string resolution, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var isUpdate = false;
|
||||
if (resolution.IsNullOrEmpty())
|
||||
{
|
||||
if (!tickers.IsNullOrEmpty())
|
||||
{
|
||||
var _tickers = tickers.First().ToLowerInvariant();
|
||||
if (_tickers == "all" || _tickers == "update")
|
||||
{
|
||||
if (_tickers == "update")
|
||||
{
|
||||
isUpdate = true;
|
||||
}
|
||||
|
||||
tickers = new List<string> { "EURUSD","USDJPY","GBPUSD","USDCHF","EURCHF","AUDUSD","USDCAD",
|
||||
"NZDUSD","EURGBP","EURJPY","GBPJPY","EURAUD","EURCAD","AUDJPY" };
|
||||
resolution = "all";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Usage:\n\t" +
|
||||
"FxcmVolumeDownloader all\t will download data for all available pair for the three resolutions.\n\t" +
|
||||
"FxcmVolumeDownloader update\t will download just last day data for all pair and resolutions already downloaded.");
|
||||
Console.WriteLine("Usage: FxcmVolumeDownloader --tickers= --resolution= --from-date= --to-date=");
|
||||
Console.WriteLine("--tickers=eg EURUSD,USDJPY\n" +
|
||||
"\tAvailable pairs:\n" +
|
||||
"\tEURUSD, USDJPY, GBPUSD, USDCHF, EURCHF, AUDUSD, USDCAD,\n" +
|
||||
"\tNZDUSD, EURGBP, EURJPY, GBPJPY, EURAUD, EURCAD, AUDJPY");
|
||||
Console.WriteLine("--resolution=Minute/Hour/Daily/All");
|
||||
Environment.Exit(exitCode: 1);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Log.DebuggingEnabled = true;
|
||||
Log.LogHandler = new CompositeLogHandler(new ConsoleLogHandler(), new FileLogHandler("FxcmFxVolumeDownloader.log", useTimestampPrefix: false));
|
||||
|
||||
var resolutions = new[] { Resolution.Daily };
|
||||
if (resolution.ToLowerInvariant() == "all")
|
||||
{
|
||||
resolutions = new[] { Resolution.Daily, Resolution.Hour, Resolution.Minute };
|
||||
}
|
||||
else
|
||||
{
|
||||
resolutions[0] = (Resolution)Enum.Parse(typeof(Resolution), resolution);
|
||||
}
|
||||
|
||||
// Load settings from config.json
|
||||
var dataDirectory = Config.Get("data-directory", "../../../Data");
|
||||
|
||||
var downloader = new FxcmVolumeDownloader(dataDirectory);
|
||||
foreach (var ticker in tickers)
|
||||
{
|
||||
var symbol = Symbol.Create(ticker, SecurityType.Base, Market.FXCM);
|
||||
foreach (var _resolution in resolutions)
|
||||
{
|
||||
downloader.Run(symbol, _resolution, startDate, endDate, isUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Log.Error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Custom;
|
||||
|
||||
namespace QuantConnect.ToolBox
|
||||
{
|
||||
/// <summary>
|
||||
/// FXCM Real FOREX Volume/Transactions Downloader Toolbox Project For LEAN Algorithmic Trading Engine.
|
||||
/// </summary>
|
||||
/// <seealso cref="QuantConnect.ToolBox.IDataDownloader" />
|
||||
public class FxcmVolumeDownloader : IDataDownloader
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// The request base URL.
|
||||
/// </summary>
|
||||
private readonly string _baseUrl = " http://marketsummary2.fxcorporate.com/ssisa/servlet?RT=SSI";
|
||||
|
||||
private readonly string _dataDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// FXCM session id.
|
||||
/// </summary>
|
||||
private readonly string _sid = "quantconnect";
|
||||
|
||||
/// <summary>
|
||||
/// The columns index which should be added to obtain the transactions.
|
||||
/// </summary>
|
||||
private readonly long[] _transactionsIdx = {27, 29, 31, 33};
|
||||
|
||||
/// <summary>
|
||||
/// Integer representing client version.
|
||||
/// </summary>
|
||||
private readonly int _ver = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The columns index which should be added to obtain the volume.
|
||||
/// </summary>
|
||||
private readonly int[] _volumeIdx = {26, 28, 30, 32};
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FxcmVolumeDownloader" /> class.
|
||||
/// </summary>
|
||||
/// <param name="dataDirectory">The Lean data directory.</param>
|
||||
public FxcmVolumeDownloader(string dataDirectory)
|
||||
{
|
||||
_dataDirectory = dataDirectory;
|
||||
}
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get historical data enumerable for a single symbol, type and resolution given this start and end time (in UTC).
|
||||
/// </summary>
|
||||
/// <param name="symbol">Symbol for the data we're looking for.</param>
|
||||
/// <param name="resolution">Resolution of the data request</param>
|
||||
/// <param name="startUtc">Start time of the data in UTC</param>
|
||||
/// <param name="endUtc">End time of the data in UTC</param>
|
||||
/// <returns>
|
||||
/// Enumerable of base data for this symbol
|
||||
/// </returns>
|
||||
public IEnumerable<BaseData> Get(Symbol symbol, Resolution resolution, DateTime startUtc, DateTime endUtc)
|
||||
{
|
||||
var idx = 0;
|
||||
var obsTime = startUtc;
|
||||
var requestedData = new List<BaseData>();
|
||||
var lines = RequestData(symbol, resolution, startUtc, endUtc);
|
||||
|
||||
do
|
||||
{
|
||||
var line = lines[idx++];
|
||||
var obs = line.Split(';');
|
||||
var stringDate = obs[0].Substring(startIndex: 3);
|
||||
obsTime = DateTime.ParseExact(stringDate, "yyyyMMddHHmm",
|
||||
DateTimeFormatInfo.InvariantInfo);
|
||||
var volume = _volumeIdx.Select(x => Parse.Long(obs[x])).Sum();
|
||||
|
||||
var transactions = _transactionsIdx.Select(x => Parse.Int(obs[x])).Sum();
|
||||
requestedData.Add(new FxcmVolume
|
||||
{
|
||||
Symbol = symbol,
|
||||
Time = obsTime,
|
||||
Value = volume,
|
||||
Transactions = transactions
|
||||
});
|
||||
} while (obsTime.Date <= endUtc.Date && idx < lines.Length - 1);
|
||||
return requestedData.Where(o => o.Time.Date >= startUtc.Date && o.Time.Date <= endUtc.Date);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method in charge of making all the steps to save the data. It makes the request, parses the data and saves it.
|
||||
/// This method takes into account the size limitation of the responses, slicing big request into smaller ones.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol.</param>
|
||||
/// <param name="resolution">The resolution.</param>
|
||||
/// <param name="startUtc">The start UTC.</param>
|
||||
/// <param name="endUtc">The end UTC.</param>
|
||||
/// <param name="update">Flag to </param>
|
||||
public void Run(Symbol symbol, Resolution resolution, DateTime startUtc, DateTime endUtc, bool update = false)
|
||||
{
|
||||
var data = new List<BaseData>();
|
||||
var requestDayInterval = 0;
|
||||
var writer = new FxcmVolumeWriter(resolution, symbol, _dataDirectory);
|
||||
var intermediateStartDate = startUtc;
|
||||
var intermediateEndDate = endUtc;
|
||||
|
||||
if (update)
|
||||
{
|
||||
var updatedStartDate = FxcmVolumeAuxiliaryMethods.GetLastAvailableDateOfData(symbol, resolution, writer.FolderPath);
|
||||
if (updatedStartDate == null) return;
|
||||
|
||||
intermediateStartDate = ((DateTime) updatedStartDate).AddDays(value: -1);
|
||||
intermediateEndDate = DateTime.Today;
|
||||
}
|
||||
|
||||
// As the responses has a Limit of 10000 lines, hourly data the minute data request should be sliced.
|
||||
if (resolution == Resolution.Minute && (endUtc - startUtc).TotalMinutes > 10000)
|
||||
{
|
||||
// Six days are 8640 minute observations, 7 days are 10080.
|
||||
requestDayInterval = 6;
|
||||
}
|
||||
else if (resolution == Resolution.Hour && (endUtc - startUtc).TotalHours > 10000)
|
||||
{
|
||||
// 410 days x 24 hr = 9840 hr.
|
||||
requestDayInterval = 410;
|
||||
}
|
||||
|
||||
var counter = 0;
|
||||
do
|
||||
{
|
||||
if (requestDayInterval != 0)
|
||||
{
|
||||
if (counter++ != 0)
|
||||
{
|
||||
intermediateStartDate = intermediateEndDate.AddDays(value: 1);
|
||||
}
|
||||
intermediateEndDate = intermediateStartDate.AddDays(requestDayInterval);
|
||||
if (intermediateEndDate > endUtc) intermediateEndDate = endUtc;
|
||||
}
|
||||
data.AddRange(Get(symbol, resolution, intermediateStartDate, intermediateEndDate));
|
||||
// For every 300k observations in memory, write it.
|
||||
if (resolution == Resolution.Minute && counter % 30 == 0)
|
||||
{
|
||||
writer.Write(data);
|
||||
data.Clear();
|
||||
}
|
||||
} while (intermediateEndDate != endUtc);
|
||||
|
||||
writer.Write(data, update);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FXCM identifier from a FOREX pair symbol.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The pair symbol.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="System.ArgumentException">Volume data is not available for the selected symbol. - symbol</exception>
|
||||
private int GetFxcmIDFromSymbol(Symbol symbol)
|
||||
{
|
||||
int symbolId;
|
||||
try
|
||||
{
|
||||
symbolId = (int) Enum.Parse(typeof(FxcmSymbolId), symbol.Value);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
throw new ArgumentException("Volume data is not available for the selected symbol.", "symbol");
|
||||
}
|
||||
return symbolId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string interval representation from the resolution.
|
||||
/// </summary>
|
||||
/// <param name="resolution">The requested resolution.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// resolution - tick or second resolution are not supported for Forex
|
||||
/// Volume.
|
||||
/// </exception>
|
||||
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.");
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the API Requests.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol.</param>
|
||||
/// <param name="resolution">The resolution.</param>
|
||||
/// <param name="startUtc">The start date in UTC.</param>
|
||||
/// <param name="endUtc">The end date in UTC.</param>
|
||||
/// <returns></returns>
|
||||
private string[] RequestData(Symbol symbol, Resolution resolution, DateTime startUtc, DateTime endUtc)
|
||||
{
|
||||
var startDate = string.Empty;
|
||||
var endDate = endUtc.AddDays(value: 2).ToStringInvariant("yyyyMMdd") + "2100";
|
||||
var symbolId = GetFxcmIDFromSymbol(symbol);
|
||||
var interval = GetIntervalFromResolution(resolution);
|
||||
switch (resolution)
|
||||
{
|
||||
case Resolution.Minute:
|
||||
case Resolution.Hour:
|
||||
startDate = startUtc.ToStringInvariant("yyyyMMdd") + "0000";
|
||||
break;
|
||||
|
||||
case Resolution.Daily:
|
||||
startDate = startUtc.AddDays(value: 1).ToStringInvariant("yyyyMMdd") + "2100";
|
||||
break;
|
||||
}
|
||||
|
||||
var request = $"{_baseUrl}&ver={_ver}&sid={_sid}&interval={interval}&offerID={symbolId}" +
|
||||
$"&timeFrom={startDate.ToStringInvariant()}&timeTo={endDate.ToStringInvariant()}";
|
||||
|
||||
string[] lines;
|
||||
using (var client = new WebClient())
|
||||
{
|
||||
var data = client.DownloadString(request);
|
||||
lines = data.Split('\n');
|
||||
}
|
||||
// Removes the HTML head and tail.
|
||||
return lines.Skip(count: 2).Take(lines.Length - 4).ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Custom;
|
||||
|
||||
namespace QuantConnect.ToolBox
|
||||
{
|
||||
public class FxcmVolumeWriter
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly Resolution _resolution;
|
||||
private readonly Symbol _symbol;
|
||||
|
||||
#endregion
|
||||
|
||||
public string FolderPath { get; }
|
||||
|
||||
public FxcmVolumeWriter(Resolution resolution, Symbol symbol, string dataDirectory)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_resolution = resolution;
|
||||
var market = _symbol.ID.Market;
|
||||
FolderPath =
|
||||
Path.Combine(new[] {dataDirectory, "forex", market.ToLowerInvariant(), _resolution.ToLower()});
|
||||
if (_resolution == Resolution.Minute)
|
||||
{
|
||||
FolderPath = Path.Combine(FolderPath, _symbol.Value.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(IEnumerable<BaseData> data, bool update = false)
|
||||
{
|
||||
if (!Directory.Exists(FolderPath)) Directory.CreateDirectory(FolderPath);
|
||||
if (update && _resolution != Resolution.Minute)
|
||||
{
|
||||
data = ReadActualDataAndAppendNewData(data);
|
||||
}
|
||||
// Seems the data has some duplicate values! This makes the writer throws an error. So, just in case, we clean the data from duplicates.
|
||||
data = data.GroupBy(x => x.Time)
|
||||
.Select(g => g.First());
|
||||
if (_resolution == Resolution.Minute)
|
||||
{
|
||||
WriteMinuteData(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteHourAndDailyData(data);
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private IEnumerable<BaseData> ReadActualDataAndAppendNewData(IEnumerable<BaseData> data)
|
||||
{
|
||||
// Read the actual data
|
||||
var zipFilePath = Path.Combine(FolderPath, _symbol.Value.ToLowerInvariant() + "_volume.zip");
|
||||
var actualData = FxcmVolumeAuxiliaryMethods.GetFxcmVolumeFromZip(zipFilePath);
|
||||
return actualData.Concat(data);
|
||||
}
|
||||
|
||||
private void WriteHourAndDailyData(IEnumerable<BaseData> data)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var volData = data.Cast<FxcmVolume>();
|
||||
foreach (var obs in volData)
|
||||
{
|
||||
sb.AppendLine($"{obs.Time.ToStringInvariant("yyyyMMdd HH:mm")},{obs.Value.ToStringInvariant()},{obs.Transactions}");
|
||||
}
|
||||
var data_to_save = sb.ToString();
|
||||
|
||||
var filename = _symbol.Value.ToLowerInvariant() + "_volume";
|
||||
var csvFilePath = Path.Combine(FolderPath, filename + ".csv");
|
||||
var zipFilePath = csvFilePath.Replace(".csv", ".zip");
|
||||
|
||||
if (File.Exists(zipFilePath)) File.Delete(zipFilePath);
|
||||
File.WriteAllText(csvFilePath, data_to_save);
|
||||
// Write out this data string to a zip file
|
||||
Compression.Zip(csvFilePath, Path.GetFileName(csvFilePath));
|
||||
}
|
||||
|
||||
private void WriteMinuteData(IEnumerable<BaseData> data)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var volData = data.Cast<FxcmVolume>();
|
||||
var dataByDay = volData.GroupBy(o => o.Time.Date);
|
||||
foreach (var dayOfData in dataByDay)
|
||||
{
|
||||
foreach (var obs in dayOfData)
|
||||
{
|
||||
sb.AppendLine($"{obs.Time.TimeOfDay.TotalMilliseconds.ToStringInvariant()},{obs.Value.ToStringInvariant()},{obs.Transactions}");
|
||||
}
|
||||
var filename = $"{dayOfData.Key.ToStringInvariant("yyyyMMdd")}_volume.csv";
|
||||
var filePath = Path.Combine(FolderPath, filename);
|
||||
File.WriteAllText(filePath, sb.ToString());
|
||||
// Write out this data string to a zip file
|
||||
Compression.Zip(filePath, filename);
|
||||
sb.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Consolidators;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.ToolBox
|
||||
{
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Win32;
|
||||
using System.Threading;
|
||||
using QuantConnect.Configuration;
|
||||
|
||||
@@ -41,29 +40,6 @@ namespace QuantConnect.ToolBox.IQFeed
|
||||
);
|
||||
}
|
||||
|
||||
public string getPath()
|
||||
{
|
||||
var key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\DTN\\IQFeed");
|
||||
if (key == null)
|
||||
{
|
||||
// if it isn't in that location, it is possible the user is running and x64 OS. Check the windows virtualized registry location
|
||||
key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Wow6432Node\\DTN\\IQFeed");
|
||||
}
|
||||
string sLocation = null;
|
||||
if (key != null)
|
||||
{
|
||||
sLocation = key.GetValue("EXEDIR", "").ToString();
|
||||
// close the key since we don't need it anymore
|
||||
key.Close();
|
||||
// verify there is a \ on the end before we append the exe name
|
||||
if (!(sLocation.EndsWith("\\") || sLocation.EndsWith("/")))
|
||||
{
|
||||
sLocation += "\\";
|
||||
}
|
||||
sLocation += "IQConnect.exe";
|
||||
}
|
||||
return sLocation;
|
||||
}
|
||||
public string getArguments(IQCredentials iqc)
|
||||
{
|
||||
var arguments = "";
|
||||
|
||||
@@ -19,7 +19,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ikvm.extensions;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Util;
|
||||
@@ -133,7 +132,7 @@ namespace QuantConnect.ToolBox.IVolatilityEquityConverter
|
||||
private static Symbol GetSymbol(string fileName)
|
||||
{
|
||||
var splits = fileName.Split('_');
|
||||
var ticker = splits[1].toLowerCase();
|
||||
var ticker = splits[1].ToLowerInvariant();
|
||||
return Symbol.Create(ticker, SecurityType.Equity, Market.USA);
|
||||
}
|
||||
|
||||
@@ -144,7 +143,7 @@ namespace QuantConnect.ToolBox.IVolatilityEquityConverter
|
||||
/// <returns></returns>
|
||||
private static Resolution ParseResolution(string entry)
|
||||
{
|
||||
switch (entry.Trim().toLowerCase())
|
||||
switch (entry.Trim().ToLowerInvariant())
|
||||
{
|
||||
case "minute":
|
||||
return Resolution.Minute;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.ToolBox
|
||||
{
|
||||
|
||||
@@ -28,8 +28,6 @@ using QuantConnect.ToolBox.CoinApiDataConverter;
|
||||
using QuantConnect.ToolBox.CryptoiqDownloader;
|
||||
using QuantConnect.ToolBox.DukascopyDownloader;
|
||||
using QuantConnect.ToolBox.EstimizeDataDownloader;
|
||||
using QuantConnect.ToolBox.FxcmDownloader;
|
||||
using QuantConnect.ToolBox.FxcmVolumeDownload;
|
||||
using QuantConnect.ToolBox.GDAXDownloader;
|
||||
using QuantConnect.ToolBox.IBDownloader;
|
||||
using QuantConnect.ToolBox.IEX;
|
||||
@@ -96,14 +94,6 @@ namespace QuantConnect.ToolBox
|
||||
case "dukascopydownloader":
|
||||
DukascopyDownloaderProgram.DukascopyDownloader(tickers, resolution, fromDate, toDate);
|
||||
break;
|
||||
case "fdl":
|
||||
case "fxcmdownloader":
|
||||
FxcmDownloaderProgram.FxcmDownloader(tickers, resolution, fromDate, toDate);
|
||||
break;
|
||||
case "fvdl":
|
||||
case "fxcmvolumedownload":
|
||||
FxcmVolumeDownloadProgram.FxcmVolumeDownload(tickers, resolution, fromDate, toDate);
|
||||
break;
|
||||
case "ibdl":
|
||||
case "ibdownloader":
|
||||
IBDownloaderProgram.IBDownloader(tickers, resolution, fromDate, toDate);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>QuantConnect.ToolBox</RootNamespace>
|
||||
<AssemblyName>QuantConnect.ToolBox</AssemblyName>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>6</LangVersion>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
@@ -57,7 +57,6 @@
|
||||
<PackageReference Include="Common.Logging" Version="3.4.1" />
|
||||
<PackageReference Include="Common.Logging.Core" Version="3.4.1" />
|
||||
<PackageReference Include="DotNetZip" Version="1.13.3" />
|
||||
<PackageReference Include="IKVM" Version="8.1.5717.0" />
|
||||
<PackageReference Include="IQFeed.CSharpApiClient" Version="2.5.1" />
|
||||
<PackageReference Include="LaunchDarkly.EventSource" Version="3.3.2" />
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.6.0" />
|
||||
@@ -99,16 +98,6 @@
|
||||
<PackageReference Include="Utf8Json" Version="1.3.7" />
|
||||
<PackageReference Include="WebSocket4Net" Version="0.15.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="QuantConnect.Fxcm">
|
||||
<HintPath>..\Brokerages\Fxcm\QuantConnect.Fxcm.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE">
|
||||
<Pack>True</Pack>
|
||||
|
||||
Reference in New Issue
Block a user