Skip to content

Commit d7e5437

Browse files
authoredSep 9, 2020
GDAX Brokerage updates (QuantConnect#4635)
* GDAX Brokerage updates - Replaced fill detection from trade stream with monitor task - Order fees for fills are now the real fees paid (previously they were calculated by the brokerage model) - All unit and integration tests are green * Address review - Remove unnecessary signals - Add "gdax-fill-monitor-timeout" config setting * Remove user channel
1 parent 8785af7 commit d7e5437

14 files changed

+313
-282
lines changed
 

‎Brokerages/GDAX/GDAXBrokerage.Messaging.cs

+80-129
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
using System.Collections.Generic;
2323
using System.Globalization;
2424
using System.Linq;
25+
using System.Net;
2526
using System.Threading.Tasks;
2627
using System.Threading;
2728
using RestSharp;
2829
using System.Text.RegularExpressions;
30+
using QuantConnect.Configuration;
2931
using QuantConnect.Logging;
3032
using QuantConnect.Orders.Fees;
3133
using QuantConnect.Securities;
@@ -46,8 +48,6 @@ public partial class GDAXBrokerage
4648
private const string SymbolMatching = "ETH|LTC|BTC|BCH|XRP|EOS|XLM|ETC|ZRX";
4749
private readonly IAlgorithm _algorithm;
4850
private readonly CancellationTokenSource _canceller = new CancellationTokenSource();
49-
private readonly ConcurrentQueue<WebSocketMessage> _messageBuffer = new ConcurrentQueue<WebSocketMessage>();
50-
private volatile bool _streamLocked;
5151
private readonly ConcurrentDictionary<Symbol, DefaultOrderBook> _orderBooks = new ConcurrentDictionary<Symbol, DefaultOrderBook>();
5252
private readonly bool _isDataQueueHandler;
5353
protected readonly IDataAggregator _aggregator;
@@ -60,12 +60,19 @@ internal enum GdaxEndpointType { Public, Private }
6060

6161
private readonly IPriceProvider _priceProvider;
6262

63+
private readonly CancellationTokenSource _ctsFillMonitor = new CancellationTokenSource();
64+
private readonly Task _fillMonitorTask;
65+
private readonly AutoResetEvent _fillMonitorResetEvent = new AutoResetEvent(false);
66+
private readonly int _fillMonitorTimeout = Config.GetInt("gdax-fill-monitor-timeout", 500);
67+
private readonly ConcurrentDictionary<string, Order> _pendingOrders = new ConcurrentDictionary<string, Order>();
68+
private long _lastEmittedFillTradeId;
69+
6370
#endregion
6471

6572
/// <summary>
6673
/// The list of websocket channels to subscribe
6774
/// </summary>
68-
protected virtual string[] ChannelNames { get; } = { "heartbeat", "user" };
75+
protected virtual string[] ChannelNames { get; } = { "heartbeat" };
6976

7077
/// <summary>
7178
/// Constructor for brokerage
@@ -89,50 +96,9 @@ public GDAXBrokerage(string wssUrl, IWebSocket websocket, IRestClient restClient
8996
_priceProvider = priceProvider;
9097
_aggregator = aggregator;
9198

92-
WebSocket.Open += (sender, args) =>
93-
{
94-
var tickers = new[]
95-
{
96-
"LTCUSD", "LTCEUR", "LTCBTC",
97-
"BTCUSD", "BTCEUR", "BTCGBP",
98-
"ETHBTC", "ETHUSD", "ETHEUR",
99-
"BCHBTC", "BCHUSD", "BCHEUR",
100-
"XRPUSD", "XRPEUR", "XRPBTC",
101-
"EOSUSD", "EOSEUR", "EOSBTC",
102-
"XLMUSD", "XLMEUR", "XLMBTC",
103-
"ETCUSD", "ETCEUR", "ETCBTC",
104-
"ZRXUSD", "ZRXEUR", "ZRXBTC",
105-
};
106-
Subscribe(tickers.Select(ticker => Symbol.Create(ticker, SecurityType.Crypto, Market.GDAX)));
107-
};
108-
10999
_isDataQueueHandler = this is GDAXDataQueueHandler;
110-
}
111-
112-
/// <summary>
113-
/// Lock the streaming processing while we're sending orders as sometimes they fill before the REST call returns.
114-
/// </summary>
115-
public void LockStream()
116-
{
117-
Log.Trace("GDAXBrokerage.Messaging.LockStream(): Locking Stream");
118-
_streamLocked = true;
119-
}
120100

121-
/// <summary>
122-
/// Unlock stream and process all backed up messages.
123-
/// </summary>
124-
public void UnlockStream()
125-
{
126-
Log.Trace("GDAXBrokerage.Messaging.UnlockStream(): Processing Backlog...");
127-
while (_messageBuffer.Any())
128-
{
129-
WebSocketMessage e;
130-
_messageBuffer.TryDequeue(out e);
131-
OnMessageImpl(this, e);
132-
}
133-
Log.Trace("GDAXBrokerage.Messaging.UnlockStream(): Stream Unlocked.");
134-
// Once dequeued in order; unlock stream.
135-
_streamLocked = false;
101+
_fillMonitorTask = Task.Factory.StartNew(FillMonitorAction, _ctsFillMonitor.Token);
136102
}
137103

138104
/// <summary>
@@ -141,31 +107,6 @@ public void UnlockStream()
141107
/// <param name="sender"></param>
142108
/// <param name="e"></param>
143109
public override void OnMessage(object sender, WebSocketMessage e)
144-
{
145-
// Verify if we're allowed to handle the streaming packet yet; while we're placing an order we delay the
146-
// stream processing a touch.
147-
try
148-
{
149-
if (_streamLocked)
150-
{
151-
_messageBuffer.Enqueue(e);
152-
return;
153-
}
154-
}
155-
catch (Exception err)
156-
{
157-
Log.Error(err);
158-
}
159-
160-
OnMessageImpl(sender, e);
161-
}
162-
163-
/// <summary>
164-
/// Implementation of the OnMessage event
165-
/// </summary>
166-
/// <param name="sender"></param>
167-
/// <param name="e"></param>
168-
private void OnMessageImpl(object sender, WebSocketMessage e)
169110
{
170111
try
171112
{
@@ -327,90 +268,55 @@ private void OnMatch(string data)
327268
// deserialize the current match (trade) message
328269
var message = JsonConvert.DeserializeObject<Messages.Matched>(data, JsonSettings);
329270

330-
if (string.IsNullOrEmpty(message.UserId))
331-
{
332-
// message received from the "matches" channel
333-
if (_isDataQueueHandler)
334-
{
335-
EmitTradeTick(message);
336-
}
337-
return;
338-
}
339-
340-
// message received from the "user" channel, this trade is ours
341-
342-
// check the list of currently active orders, if the current trade is ours we are either a maker or a taker
343-
var currentOrder = CachedOrderIDs
344-
.FirstOrDefault(o => o.Value.BrokerId.Contains(message.MakerOrderId) || o.Value.BrokerId.Contains(message.TakerOrderId));
345-
346-
if (currentOrder.Value == null)
271+
// message received from the "matches" channel
272+
if (_isDataQueueHandler)
347273
{
348-
// should never happen, log just in case
349-
Log.Error($"GDAXBrokerage.OrderMatch(): Unexpected match: {message.ProductId} {data}");
350-
return;
274+
EmitTradeTick(message);
351275
}
276+
}
352277

353-
Log.Trace($"GDAXBrokerage.OrderMatch(): Match: {message.ProductId} {data}");
354-
355-
var order = currentOrder.Value;
278+
private void EmitFillOrderEvent(Messages.Fill fill, Order order)
279+
{
280+
var symbol = ConvertProductId(fill.ProductId);
356281

357282
if (!FillSplit.ContainsKey(order.Id))
358283
{
359284
FillSplit[order.Id] = new GDAXFill(order);
360285
}
361286

362287
var split = FillSplit[order.Id];
363-
split.Add(message);
364-
365-
var symbol = ConvertProductId(message.ProductId);
288+
split.Add(fill);
366289

367290
// is this the total order at once? Is this the last split fill?
368-
var isFinalFill = Math.Abs(message.Size) == Math.Abs(order.Quantity) || Math.Abs(split.OrderQuantity) == Math.Abs(split.TotalQuantity);
369-
370-
EmitFillOrderEvent(message, symbol, split, isFinalFill);
371-
}
372-
373-
private void EmitFillOrderEvent(Messages.Matched message, Symbol symbol, GDAXFill split, bool isFinalFill)
374-
{
375-
var order = split.Order;
291+
var isFinalFill = Math.Abs(fill.Size) == Math.Abs(order.Quantity) || Math.Abs(split.OrderQuantity) == Math.Abs(split.TotalQuantity);
376292

377293
var status = isFinalFill ? OrderStatus.Filled : OrderStatus.PartiallyFilled;
378294

379-
OrderDirection direction;
380-
// Messages are always from the perspective of the market maker. Flip direction if executed as a taker.
381-
if (order.BrokerId[0] == message.TakerOrderId)
382-
{
383-
direction = message.Side == "sell" ? OrderDirection.Buy : OrderDirection.Sell;
384-
}
385-
else
386-
{
387-
direction = message.Side == "sell" ? OrderDirection.Sell : OrderDirection.Buy;
388-
}
295+
var direction = fill.Side == "sell" ? OrderDirection.Sell : OrderDirection.Buy;
389296

390-
var fillPrice = message.Price;
391-
var fillQuantity = direction == OrderDirection.Sell ? -message.Size : message.Size;
392-
var isMaker = order.BrokerId[0] == message.MakerOrderId;
297+
var fillPrice = fill.Price;
298+
var fillQuantity = direction == OrderDirection.Sell ? -fill.Size : fill.Size;
393299

394300
var currency = order.PriceCurrency == string.Empty
395301
? _algorithm.Securities[symbol].SymbolProperties.QuoteCurrency
396302
: order.PriceCurrency;
397303

398-
var orderFee = new OrderFee(new CashAmount(
399-
GetFillFee(_algorithm.UtcTime, fillPrice, fillQuantity, isMaker),
400-
currency));
304+
var orderFee = new OrderFee(new CashAmount(fill.Fee, currency));
401305

402306
var orderEvent = new OrderEvent
403307
(
404-
order.Id, symbol, message.Time, status,
308+
order.Id, symbol, fill.CreatedAt, status,
405309
direction, fillPrice, fillQuantity,
406-
orderFee, $"GDAX Match Event {direction}"
310+
orderFee, $"GDAX Fill Event {direction}"
407311
);
408312

409313
// when the order is completely filled, we no longer need it in the active order list
410314
if (orderEvent.Status == OrderStatus.Filled)
411315
{
412316
Order outOrder;
413317
CachedOrderIDs.TryRemove(order.Id, out outOrder);
318+
319+
_pendingOrders.TryRemove(fill.OrderId, out outOrder);
414320
}
415321

416322
OnOrderEvent(orderEvent);
@@ -551,7 +457,8 @@ public void PollTick(Symbol symbol)
551457
{
552458
Value = rate,
553459
Time = DateTime.UtcNow,
554-
Symbol = symbol
460+
Symbol = symbol,
461+
TickType = TickType.Quote
555462
};
556463
_aggregator.Update(latest);
557464

@@ -598,14 +505,58 @@ public void Unsubscribe(IEnumerable<Symbol> symbols)
598505
}
599506
}
600507

601-
/// <summary>
602-
/// Returns the fee paid for a total or partial order fill
603-
/// </summary>
604-
public static decimal GetFillFee(DateTime utcTime, decimal fillPrice, decimal fillQuantity, bool isMaker)
508+
private void FillMonitorAction()
605509
{
606-
var feePercentage = GDAXFeeModel.GetFeePercentage(utcTime, isMaker);
510+
Log.Trace("GDAXBrokerage.FillMonitorAction(): task started");
511+
512+
try
513+
{
514+
foreach (var order in GetOpenOrders())
515+
{
516+
_pendingOrders.TryAdd(order.BrokerId.First(), order);
517+
}
518+
519+
while (!_ctsFillMonitor.IsCancellationRequested)
520+
{
521+
_fillMonitorResetEvent.WaitOne(TimeSpan.FromMilliseconds(_fillMonitorTimeout), _ctsFillMonitor.Token);
522+
523+
foreach (var kvp in _pendingOrders)
524+
{
525+
var orderId = kvp.Key;
526+
var order = kvp.Value;
527+
528+
var request = new RestRequest($"/fills?order_id={orderId}", Method.GET);
529+
GetAuthenticationToken(request);
530+
531+
var response = ExecuteRestRequest(request, GdaxEndpointType.Private);
532+
533+
if (response.StatusCode != HttpStatusCode.OK)
534+
{
535+
throw new Exception($"GDAXBrokerage.FillMonitorAction(): request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
536+
}
537+
538+
var fills = JsonConvert.DeserializeObject<List<Messages.Fill>>(response.Content);
539+
foreach (var fill in fills.OrderBy(x => x.TradeId))
540+
{
541+
if (fill.TradeId <= _lastEmittedFillTradeId)
542+
{
543+
continue;
544+
}
545+
546+
EmitFillOrderEvent(fill, order);
547+
548+
_lastEmittedFillTradeId = fill.TradeId;
549+
}
550+
551+
}
552+
}
553+
}
554+
catch (Exception exception)
555+
{
556+
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, -1, exception.Message));
557+
}
607558

608-
return fillPrice * Math.Abs(fillQuantity) * feePercentage;
559+
Log.Trace("GDAXBrokerage.FillMonitorAction(): task ended");
609560
}
610561
}
611562
}

‎Brokerages/GDAX/GDAXBrokerage.cs

+12-8
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ public partial class GDAXBrokerage : BaseWebsocketsBrokerage
4848
/// <returns></returns>
4949
public override bool PlaceOrder(Order order)
5050
{
51-
LockStream();
52-
5351
var req = new RestRequest("/orders", Method.POST);
5452

5553
dynamic payload = new ExpandoObject();
@@ -105,7 +103,6 @@ public override bool PlaceOrder(Order order)
105103
OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, orderFee, "GDAX Order Event") { Status = OrderStatus.Invalid, Message = errorMessage });
106104
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, (int)response.StatusCode, errorMessage));
107105

108-
UnlockStream();
109106
return true;
110107
}
111108

@@ -115,7 +112,6 @@ public override bool PlaceOrder(Order order)
115112
OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, orderFee, "GDAX Order Event") { Status = OrderStatus.Invalid, Message = errorMessage });
116113
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, (int)response.StatusCode, errorMessage));
117114

118-
UnlockStream();
119115
return true;
120116
}
121117

@@ -137,15 +133,16 @@ public override bool PlaceOrder(Order order)
137133
OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, orderFee, "GDAX Order Event") { Status = OrderStatus.Submitted });
138134
Log.Trace($"Order submitted successfully - OrderId: {order.Id}");
139135

140-
UnlockStream();
136+
_pendingOrders.TryAdd(brokerId, order);
137+
_fillMonitorResetEvent.Set();
138+
141139
return true;
142140
}
143141

144142
var message = $"Order failed, Order Id: {order.Id} timestamp: {order.Time} quantity: {order.Quantity} content: {response.Content}";
145143
OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, orderFee, "GDAX Order Event") { Status = OrderStatus.Invalid });
146144
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, message));
147145

148-
UnlockStream();
149146
return true;
150147
}
151148

@@ -180,6 +177,9 @@ public override bool CancelOrder(Order order)
180177
DateTime.UtcNow,
181178
OrderFee.Zero,
182179
"GDAX Order Event") { Status = OrderStatus.Canceled });
180+
181+
Order orderRemoved;
182+
_pendingOrders.TryRemove(id, out orderRemoved);
183183
}
184184
}
185185

@@ -440,10 +440,14 @@ private static IEnumerable<TradeBar> ParseCandleData(Symbol symbol, int granular
440440
/// </summary>
441441
public override void Dispose()
442442
{
443+
_ctsFillMonitor.Cancel();
444+
_fillMonitorTask.Wait(TimeSpan.FromSeconds(5));
445+
443446
_canceller.DisposeSafely();
444447
_aggregator.DisposeSafely();
445-
_publicEndpointRateLimiter.DisposeSafely();
446-
_privateEndpointRateLimiter.DisposeSafely();
448+
449+
_publicEndpointRateLimiter.Dispose();
450+
_privateEndpointRateLimiter.Dispose();
447451
}
448452
}
449453
}

‎Brokerages/GDAX/GDAXFill.cs

+2-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace QuantConnect.Brokerages.GDAX
2424
/// </summary>
2525
public class GDAXFill
2626
{
27-
private readonly List<Matched> _messages = new List<Matched>();
27+
private readonly List<Fill> _messages = new List<Fill>();
2828

2929
/// <summary>
3030
/// The Lean order
@@ -36,11 +36,6 @@ public class GDAXFill
3636
/// </summary>
3737
public int OrderId => Order.Id;
3838

39-
/// <summary>
40-
/// The list of match messages
41-
/// </summary>
42-
public List<Matched> Messages => _messages.ToList();
43-
4439
/// <summary>
4540
/// Total amount executed across all fills
4641
/// </summary>
@@ -65,7 +60,7 @@ public GDAXFill(Orders.Order order)
6560
/// Adds a trade message
6661
/// </summary>
6762
/// <param name="msg"></param>
68-
public void Add(Matched msg)
63+
public void Add(Fill msg)
6964
{
7065
_messages.Add(msg);
7166
}

‎Brokerages/GDAX/Messages.cs

+42
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,48 @@ public class Order
133133
public decimal StopPrice { get; set; }
134134
}
135135

136+
public class Fill
137+
{
138+
[JsonProperty("created_at")]
139+
public DateTime CreatedAt { get; set; }
140+
141+
[JsonProperty("trade_id")]
142+
public long TradeId { get; set; }
143+
144+
[JsonProperty("product_id")]
145+
public string ProductId { get; set; }
146+
147+
[JsonProperty("order_id")]
148+
public string OrderId { get; set; }
149+
150+
[JsonProperty("user_id")]
151+
public string UserId { get; set; }
152+
153+
[JsonProperty("profile_id")]
154+
public string ProfileId { get; set; }
155+
156+
[JsonProperty("liquidity")]
157+
public string Liquidity { get; set; }
158+
159+
[JsonProperty("price")]
160+
public decimal Price { get; set; }
161+
162+
[JsonProperty("size")]
163+
public decimal Size { get; set; }
164+
165+
[JsonProperty("fee")]
166+
public decimal Fee { get; set; }
167+
168+
[JsonProperty("side")]
169+
public string Side { get; set; }
170+
171+
[JsonProperty("settled")]
172+
public bool Settled { get; set; }
173+
174+
[JsonProperty("usd_volume")]
175+
public decimal UsdVolume { get; set; }
176+
}
177+
136178
public class Account
137179
{
138180
public string Id { get; set; }

‎Tests/Brokerages/BrokerageTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -422,17 +422,17 @@ public virtual void LongFromShort(OrderTestParameters parameters)
422422
}
423423

424424
[Test]
425-
public void GetCashBalanceContainsUSD()
425+
public void GetCashBalanceContainsSomething()
426426
{
427427
Log.Trace("");
428428
Log.Trace("GET CASH BALANCE");
429429
Log.Trace("");
430430
var balance = Brokerage.GetCashBalance();
431-
Assert.AreEqual(1, balance.Count(x => x.Currency == Currencies.USD));
431+
Assert.IsTrue(balance.Any());
432432
}
433433

434434
[Test]
435-
public void GetAccountHoldings()
435+
public virtual void GetAccountHoldings()
436436
{
437437
Log.Trace("");
438438
Log.Trace("GET ACCOUNT HOLDINGS");

‎Tests/Brokerages/GDAX/GDAXBrokerageAdditionalTests.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@
2222
using QuantConnect.Configuration;
2323
using QuantConnect.Lean.Engine.DataFeeds;
2424
using QuantConnect.Logging;
25-
using QuantConnect.Util;
2625
using RestSharp;
2726

2827
namespace QuantConnect.Tests.Brokerages.GDAX
2928
{
30-
[TestFixture, Ignore("These tests requires a configured and active GDAX account.")]
29+
[TestFixture, Explicit("These tests requires a configured and active GDAX account.")]
3130
public class GDAXBrokerageAdditionalTests
3231
{
3332
[Test]

‎Tests/Brokerages/GDAX/GDAXBrokerageHistoryProviderTests.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@
2626
using QuantConnect.Lean.Engine.HistoricalData;
2727
using QuantConnect.Logging;
2828
using QuantConnect.Securities;
29-
using QuantConnect.Util;
3029
using RestSharp;
3130

3231
namespace QuantConnect.Tests.Brokerages.GDAX
3332
{
34-
[TestFixture, Ignore("This test requires a configured and testable GDAX account")]
33+
[TestFixture, Explicit("This test requires a configured and testable GDAX account")]
3534
public class GDAXBrokerageHistoryProviderTests
3635
{
3736
[Test, TestCaseSource(nameof(TestParameters))]
@@ -47,7 +46,7 @@ public void GetsHistory(Symbol symbol, Resolution resolution, TickType tickType,
4746

4847
var historyProvider = new BrokerageHistoryProvider();
4948
historyProvider.SetBrokerage(brokerage);
50-
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, null, null, null, null, false, null));
49+
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, null, null, null, null, false, new DataPermissionManager()));
5150

5251
var now = DateTime.UtcNow;
5352

‎Tests/Brokerages/GDAX/GDAXBrokerageIntegrationTests.cs

+29-10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
1617
using QuantConnect.Brokerages.GDAX;
1718
using NUnit.Framework;
1819
using QuantConnect.Interfaces;
@@ -21,14 +22,13 @@
2122
using QuantConnect.Orders;
2223
using Moq;
2324
using QuantConnect.Brokerages;
25+
using QuantConnect.Tests.Common.Securities;
2426
using RestSharp;
25-
using QuantConnect.Data;
26-
using QuantConnect.Util;
2727
using QuantConnect.Lean.Engine.DataFeeds;
2828

2929
namespace QuantConnect.Tests.Brokerages.GDAX
3030
{
31-
[TestFixture, Ignore("This test requires a configured and active account")]
31+
[TestFixture, Explicit("This test requires a configured and active account")]
3232
public class GDAXBrokerageIntegrationTests : BrokerageTests
3333
{
3434
#region Properties
@@ -50,15 +50,27 @@ protected override IBrokerage CreateBrokerage(IOrderProvider orderProvider, ISec
5050
var restClient = new RestClient("https://api.pro.coinbase.com");
5151
var webSocketClient = new WebSocketClientWrapper();
5252

53+
var securities = new SecurityManager(new TimeKeeper(DateTime.UtcNow, TimeZones.NewYork))
54+
{
55+
{Symbol, CreateSecurity(Symbol)}
56+
};
57+
58+
var transactions = new SecurityTransactionManager(null, securities);
59+
transactions.SetOrderProcessor(new FakeOrderProcessor());
60+
5361
var algorithm = new Mock<IAlgorithm>();
54-
algorithm.Setup(a => a.BrokerageModel).Returns(new GDAXBrokerageModel(AccountType.Cash));
62+
algorithm.Setup(a => a.Transactions).Returns(transactions);
63+
algorithm.Setup(a => a.BrokerageModel).Returns(new GDAXBrokerageModel());
64+
algorithm.Setup(a => a.Portfolio).Returns(new SecurityPortfolioManager(securities, transactions));
65+
algorithm.Setup(a => a.Securities).Returns(securities);
5566

56-
var priceProvider = new ApiPriceProvider(Config.GetInt("job-user-id"), Config.Get("api-access-token"));
57-
var aggregator = new AggregationManager();
67+
var priceProvider = new Mock<IPriceProvider>();
68+
priceProvider.Setup(a => a.GetLastPrice(It.IsAny<Symbol>())).Returns(1.234m);
5869

70+
var aggregator = new AggregationManager();
5971
return new GDAXBrokerage(Config.Get("gdax-url", "wss://ws-feed.pro.coinbase.com"), webSocketClient, restClient,
6072
Config.Get("gdax-api-key"), Config.Get("gdax-api-secret"), Config.Get("gdax-passphrase"), algorithm.Object,
61-
priceProvider, aggregator);
73+
priceProvider.Object, aggregator);
6274
}
6375

6476
/// <summary>
@@ -71,7 +83,7 @@ protected override bool IsAsync()
7183

7284
protected override decimal GetAskPrice(Symbol symbol)
7385
{
74-
var tick = ((GDAXBrokerage)this.Brokerage).GetTick(symbol);
86+
var tick = ((GDAXBrokerage)Brokerage).GetTick(symbol);
7587
return tick.AskPrice;
7688
}
7789

@@ -80,12 +92,19 @@ protected override void ModifyOrderUntilFilled(Order order, OrderTestParameters
8092
Assert.Pass("Order update not supported");
8193
}
8294

83-
//no stop limit support
95+
[Test]
96+
public override void GetAccountHoldings()
97+
{
98+
// GDAX GetAccountHoldings() always returns an empty list
99+
Assert.That(Brokerage.GetAccountHoldings().Count == 0);
100+
}
101+
102+
// stop market orders no longer supported (since 3/23/2019)
103+
// no stop limit support
84104
private static TestCaseData[] OrderParameters => new[]
85105
{
86106
new TestCaseData(new MarketOrderTestParameters(Symbol.Create("ETHBTC", SecurityType.Crypto, Market.GDAX))).SetName("MarketOrder"),
87107
new TestCaseData(new LimitOrderTestParameters(Symbol.Create("ETHBTC", SecurityType.Crypto, Market.GDAX), 1m, 0.0001m)).SetName("LimitOrder"),
88-
new TestCaseData(new StopMarketOrderTestParameters(Symbol.Create("ETHBTC", SecurityType.Crypto, Market.GDAX), 1m, 0.0001m)).SetName("StopMarketOrder"),
89108
};
90109

91110
[Test, TestCaseSource(nameof(OrderParameters))]

‎Tests/Brokerages/GDAX/GDAXBrokerageTests.cs

+122-104
Large diffs are not rendered by default.

‎Tests/QuantConnect.Tests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,7 @@
860860
<Content Include="TestData\gdax_accounts.txt">
861861
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
862862
</Content>
863-
<Content Include="TestData\gdax_match.txt">
863+
<Content Include="TestData\gdax_fill.txt">
864864
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
865865
</Content>
866866
<Content Include="TestData\gdax_holding.txt">

‎Tests/TestData/gdax_fill.txt

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
{
3+
"created_at":"2020-08-14T14:48:01.11Z",
4+
"trade_id":123456789,
5+
"product_id":"BTC-USD",
6+
"order_id":"132fb6ae-456b-4654-b4e0-d681ac05cea1",
7+
"user_id":"xyz",
8+
"profile_id":"xyz",
9+
"liquidity":"T",
10+
"price":"400.23",
11+
"size":"5.23512",
12+
"fee":"12",
13+
"side":"buy",
14+
"settled":true,
15+
"usd_volume":"12345"
16+
}
17+
]

‎Tests/TestData/gdax_holding.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"settled": false
1818
},
1919
{
20-
"id": "8b99b139-58f2-4ab2-8e7a-c11c846e3022",
20+
"id": "132fb6ae-456b-4654-b4e0-d681ac05cea1",
2121
"price": "500.00000000",
2222
"size": "1.00000000",
2323
"product_id": "BTC-USD",

‎Tests/TestData/gdax_match.txt

-13
This file was deleted.

‎Tests/TestData/gdax_order.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"type": "type_value","time": "2014-11-07T08:19:27.028459Z","product_id": "BTC-USD","sequence": 10,"order_id": "d50ec984-77a8-460a-b958-66f114b0de9b","price": "123.45","remaining_size": "1.00","side": "side_value"}
1+
{"type": "market","created_at": "2014-11-07T08:19:27.028459Z","product_id": "BTC-USD","status": "pending","id": "132fb6ae-456b-4654-b4e0-d681ac05cea1","price": "123.45","size": "1.00","side": "sell"}

0 commit comments

Comments
 (0)
Please sign in to comment.