Skip to content

NDAX: added Trade stream (websocket) #471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 87 additions & 12 deletions ExchangeSharp/API/Exchanges/NDAX/ExchangeNDAXAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ExchangeSharp.NDAX;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Expand All @@ -11,7 +12,7 @@ namespace ExchangeSharp
public sealed partial class ExchangeNDAXAPI : ExchangeAPI
{
public override string BaseUrl { get; set; } = "https://api.ndax.io:8443/AP";
public override string BaseUrlWebSocket { get; set; } = "wss://apindaxstage.cdnhop.net/WSGateway";
public override string BaseUrlWebSocket { get; set; } = "wss://api.ndax.io/WSGateway";

private AuthenticateResult authenticationDetails = null;
public override string Name => ExchangeName.NDAX;
Expand Down Expand Up @@ -366,9 +367,11 @@ private async Task<string> GetMarketSymbolFromInstrumentId(long instrumentId)

protected override async Task<IWebSocket> OnGetTickersWebSocketAsync(Action<IReadOnlyCollection<KeyValuePair<string, ExchangeTicker>>> tickers, params string[] marketSymbols)
{
var instrumentIds = await GetInstrumentIdFromMarketSymbol(marketSymbols);
var instrumentIds = marketSymbols == null || marketSymbols.Length == 0 ?
(await GetMarketSymbolsMetadataAsync()).Select(s => (long?)long.Parse(s.AltMarketSymbol)).ToArray() :
await GetInstrumentIdFromMarketSymbol(marketSymbols);

return await ConnectWebSocketAsync("", async (socket, bytes) =>
return await ConnectWebSocketAsync("", async (socket, bytes) =>
{
var messageFrame =
JsonConvert.DeserializeObject<MessageFrame>(bytes.ToStringFromUTF8().TrimEnd('\0'));
Expand All @@ -377,14 +380,20 @@ protected override async Task<IWebSocket> OnGetTickersWebSocketAsync(Action<IRea
|| messageFrame.FunctionName.Equals("Level1UpdateEvent",
StringComparison.InvariantCultureIgnoreCase))
{
var rawPayload = messageFrame.PayloadAs<Level1Data>();
var symbol = await GetMarketSymbolFromInstrumentId(rawPayload.InstrumentId);
tickers.Invoke(new[]
{
new KeyValuePair<string, ExchangeTicker>(symbol, rawPayload.ToExchangeTicker(symbol)),
});
}
},
var token = JToken.Parse(messageFrame.Payload);
if (token["errormsg"] == null)
{
var rawPayload = messageFrame.PayloadAs<Level1Data>();
var symbol = await GetMarketSymbolFromInstrumentId(rawPayload.InstrumentId);
tickers.Invoke(new[]
{
new KeyValuePair<string, ExchangeTicker>(symbol, rawPayload.ToExchangeTicker(symbol)),
});
}
else // "{\"result\":false,\"errormsg\":\"Resource Not Found\",\"errorcode\":104,\"detail\":\"Instrument not Found\"}"
Logger.Info(messageFrame.Payload);
}
},
async socket =>
{
foreach (var instrumentId in instrumentIds)
Expand All @@ -405,7 +414,73 @@ await socket.SendMessageAsync(new MessageFrame
});
}

private long GetNextSequenceNumber()
protected override async Task<IWebSocket> OnGetTradesWebSocketAsync(Func<KeyValuePair<string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
{
var instrumentIds = marketSymbols == null || marketSymbols.Length == 0 ?
(await GetMarketSymbolsMetadataAsync()).Select(s => (long?)long.Parse(s.AltMarketSymbol)).ToArray() :
await GetInstrumentIdFromMarketSymbol(marketSymbols);

return await ConnectWebSocketAsync("", async (socket, bytes) =>
{
var messageFrame =
JsonConvert.DeserializeObject<MessageFrame>(bytes.ToStringFromUTF8().TrimEnd('\0'));

if (messageFrame.FunctionName.Equals("SubscribeTrades", StringComparison.InvariantCultureIgnoreCase)
|| messageFrame.FunctionName.Equals("OrderTradeEvent", StringComparison.InvariantCultureIgnoreCase)
|| messageFrame.FunctionName.Equals("TradeDataUpdateEvent", StringComparison.InvariantCultureIgnoreCase))
{
if (messageFrame.Payload != "[]")
{
var token = JToken.Parse(messageFrame.Payload);
if (token.Type == JTokenType.Array)
{ // "[[34838,2,0.4656,10879.5,311801351,311801370,1570134695227,1,0,0,0],[34839,2,0.4674,10881.7,311801352,311801370,1570134695227,1,0,0,0]]"
var jArray = token as JArray;
for (int i = 0; i < jArray.Count; i++)
{
var tradesToken = jArray[i];
var symbol = await GetMarketSymbolFromInstrumentId(tradesToken[1].ConvertInvariant<long>());
var trade = tradesToken.ParseTradeNDAX(amountKey: 2, priceKey: 3,
typeKey: 8, timestampKey: 6,
TimestampType.UnixMilliseconds, idKey: 0,
typeKeyIsBuyValue: "0");
if (messageFrame.FunctionName.Equals("SubscribeTrades", StringComparison.InvariantCultureIgnoreCase))
{
trade.Flags |= ExchangeTradeFlags.IsFromSnapshot;
if (i == jArray.Count - 1)
{
trade.Flags |= ExchangeTradeFlags.IsLastFromSnapshot;
}
}
await callback(
new KeyValuePair<string, ExchangeTrade>(symbol, trade));
}
}
else // "{\"result\":false,\"errormsg\":\"Invalid Request\",\"errorcode\":100,\"detail\":null}"
Logger.Info(messageFrame.Payload);
}
}
},
async socket =>
{
foreach (var instrumentId in instrumentIds)
{
await socket.SendMessageAsync(new MessageFrame
{
FunctionName = "SubscribeTrades",
MessageType = MessageType.Request,
SequenceNumber = GetNextSequenceNumber(),
Payload = JsonConvert.SerializeObject(new
{
OMSId = 1,
InstrumentId = instrumentId,
IncludeLastCount = 100,
})
});
}
});
}

private long GetNextSequenceNumber()
{
// Best practice is to carry an even sequence number.
Interlocked.Add(ref _sequenceNumber, 2);
Expand Down
3 changes: 2 additions & 1 deletion ExchangeSharp/API/Exchanges/NDAX/Models/Instrument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public ExchangeMarket ToExchangeMarket()
IsActive = SessionStatus.Equals("running", StringComparison.InvariantCultureIgnoreCase),
MarginEnabled = false,
MarketId = InstrumentId.ToStringInvariant(),
MarketSymbol = Symbol
MarketSymbol = Symbol,
AltMarketSymbol = InstrumentId.ToStringInvariant(),
};
}
}
Expand Down
3 changes: 3 additions & 0 deletions ExchangeSharp/API/Exchanges/NDAX/Models/Level1Data.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ namespace ExchangeSharp
{
public sealed partial class ExchangeNDAXAPI
{
/// <summary>
/// For use in SubscribeLevel1 OnGetTickersWebSocketAsync()
/// </summary>
class Level1Data
{
[JsonProperty("OMSId")]
Expand Down
105 changes: 105 additions & 0 deletions ExchangeSharp/API/Exchanges/NDAX/Models/TradeData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using ExchangeSharp.NDAX;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExchangeSharp.NDAX
{
public enum Direction : byte
{
NoChange = 0,
UpTick = 1,
DownTick = 2,
}
public enum TakerSide : byte
{
Buy = 0,
Sell = 1,
}

public class NDAXTrade : ExchangeTrade
{
public long Order1Id { get; set; }
public long Order2Id { get; set; }
public Direction Direction { get; set; }
public bool IsBlockTrade { get; set; }
public long ClientOrderId { get; set; }
public override string ToString()
{
return string.Format("{0},{1},{2},{3},{4},{5}", base.ToString(),
Order1Id, Order2Id, Direction, IsBlockTrade, ClientOrderId);
}
}
}

namespace ExchangeSharp
{
public sealed partial class ExchangeNDAXAPI
{
/// <summary>
/// unable to use this in SubscribeTrades OnGetTradesWebSocketAsync() becuase of the array structure
/// </summary>
[JsonArray]
class TradeData
{
[JsonProperty(Order = 0)]
public long TradeId { get; set; }

/// <summary>
/// ProductPairCode is the same number and used for the same purpose as InstrumentID.
/// The two are completely equivalent in value. InstrumentId 47 = ProductPairCode 47.
/// </summary>
[JsonProperty(Order = 1)]
public long ProductPairCode { get; set; }

[JsonProperty(Order = 2)]
public long Quantity { get; set; }

[JsonProperty(Order = 3)]
public long Price { get; set; }

[JsonProperty(Order = 4)]
public long Order1Id { get; set; }

[JsonProperty(Order = 5)]
public long Order2Id { get; set; }

[JsonProperty(Order = 6)]
public long TradeTime { get; set; }

[JsonProperty(Order = 7)]
public Direction Direction { get; set; }

[JsonProperty(Order = 8)]
public TakerSide TakerSide { get; set; }

[JsonProperty(Order = 9)]
public bool IsBlockTrade { get; set; }

[JsonProperty(Order = 10)]
public long ClientOrderId { get; set; }

public NDAXTrade ToExchangeTrade()
{
var isBuy = TakerSide == TakerSide.Buy;
return new NDAXTrade()
{
Amount = Quantity,
Id = TradeId.ToStringInvariant(),
Price = Price,
IsBuy = isBuy,
Timestamp = TradeTime.UnixTimeStampToDateTimeMilliseconds(),
Flags = isBuy ? ExchangeTradeFlags.IsBuy : default,
Order1Id = Order1Id,
Order2Id = Order2Id,
Direction = Direction,
IsBlockTrade = IsBlockTrade,
ClientOrderId = ClientOrderId,
};
}
}
}
}
3 changes: 3 additions & 0 deletions ExchangeSharp/API/Exchanges/NDAX/Models/TradeHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ namespace ExchangeSharp
{
public sealed partial class ExchangeNDAXAPI
{
/// <summary>
/// For use in GetTradesHistory: OnGetHistoricalTradesAsync()
/// </summary>
class TradeHistory
{
[JsonProperty("TradeTimeMS")]
Expand Down
22 changes: 19 additions & 3 deletions ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
MIT LICENSE

Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
Expand All @@ -20,6 +20,7 @@ The above copyright notice and this permission notice shall be included in all c
using ExchangeSharp.Coinbase;
using ExchangeSharp.KuCoin;
using Newtonsoft.Json.Linq;
using ExchangeSharp.NDAX;

namespace ExchangeSharp
{
Expand Down Expand Up @@ -593,15 +594,29 @@ internal static ExchangeTrade ParseTradeKucoin(this JToken token, object amountK
return trade;
}

internal static ExchangeTrade ParseTradeNDAX(this JToken token, object amountKey, object priceKey, object typeKey,
object timestampKey, TimestampType timestampType, object idKey, string typeKeyIsBuyValue = "buy")
{
var trade = ParseTradeComponents<NDAXTrade>(token, amountKey, priceKey, typeKey,
timestampKey, timestampType, idKey, typeKeyIsBuyValue);
trade.Order1Id = token[4].ConvertInvariant<long>();
trade.Order2Id = token[5].ConvertInvariant<long>();
trade.Direction = (Direction)token[7].ConvertInvariant<byte>();
trade.IsBlockTrade = token[9].ConvertInvariant<bool>();
trade.ClientOrderId = token[10].ConvertInvariant<long>();
return trade;
}

internal static T ParseTradeComponents<T>(this JToken token, object amountKey, object priceKey, object typeKey,
object timestampKey, TimestampType timestampType, object idKey, string typeKeyIsBuyValue = "buy")
where T : ExchangeTrade, new()
{
var isBuy = token[typeKey].ToStringInvariant().EqualsWithOption(typeKeyIsBuyValue);
T trade = new T
{
Amount = token[amountKey].ConvertInvariant<decimal>(),
Price = token[priceKey].ConvertInvariant<decimal>(),
IsBuy = (token[typeKey].ToStringInvariant().EqualsWithOption(typeKeyIsBuyValue)),
IsBuy = isBuy,
};
trade.Timestamp = (timestampKey == null ? CryptoUtility.UtcNow : CryptoUtility.ParseTimestamp(token[timestampKey], timestampType));
if (idKey == null)
Expand All @@ -619,6 +634,7 @@ internal static T ParseTradeComponents<T>(this JToken token, object amountKey, o
Logger.Info("error parsing trade ID: " + token.ToStringInvariant());
}
}
trade.Flags = isBuy ? ExchangeTradeFlags.IsBuy : default;
return trade;
}
#endregion
Expand Down Expand Up @@ -703,4 +719,4 @@ internal static MarketCandle ParseCandle(this INamed named, JToken token, string
return candle;
}
}
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The following cryptocurrency exchanges are supported:
| Poloniex | x | x | T R B |
| YoBit | x | x | |
| ZB.com | wip | | R |
| NDAX | x | x | T |
| NDAX | x | x | T R |

The following cryptocurrency services are supported:
- Cryptowatch (partial)
Expand Down