-
-
Notifications
You must be signed in to change notification settings - Fork 373
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
NDAX: added REST api Support #465
Merged
Merged
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
691ef62
init ndax
Kukks 79da3b7
init rough draft of rest api
Kukks 80528fd
fixes
Kukks 63845b9
fixes
Kukks 59cdc9a
remove commented code
Kukks 072b311
remove commented code
Kukks 2d44b2e
rename namespace
Kukks File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
ExchangeSharp/API/Exchanges/Ndax/Converters/BoolConverter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
using Newtonsoft.Json; | ||
|
||
namespace ExchangeSharp.API.Exchanges.Ndax.Converters | ||
{ | ||
public class BoolConverter : JsonConverter | ||
{ | ||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | ||
|
||
{ | ||
writer.WriteValue(((bool) value) ? 1 : 0); | ||
} | ||
|
||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, | ||
JsonSerializer serializer) | ||
|
||
{ | ||
return reader.Value.ToString() == "1"; | ||
} | ||
|
||
public override bool CanConvert(Type objectType) | ||
|
||
{ | ||
return objectType == typeof(bool); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,360 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using ExchangeSharp.API.Exchanges.Ndax.Models; | ||
using Newtonsoft.Json.Linq; | ||
|
||
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"; | ||
|
||
private AuthenticateResult authenticationDetails = null; | ||
public override string Name => ExchangeName.Ndax; | ||
|
||
private static Dictionary<string, long> _marketSymbolToInstrumentIdMapping; | ||
private static Dictionary<string, long> _symbolToProductId; | ||
|
||
public ExchangeNdaxAPI() | ||
{ | ||
RequestContentType = "application/json"; | ||
MarketSymbolSeparator = "_"; | ||
RequestMethod = "POST"; | ||
} | ||
|
||
protected override async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>> OnGetTickersAsync() | ||
{ | ||
var result = | ||
await MakeJsonRequestAsync<Dictionary<string, NdaxTicker>>("returnticker", "https://ndax.io/api", null, "GET"); | ||
_marketSymbolToInstrumentIdMapping = result.ToDictionary(pair => pair.Key, pair => pair.Value.Id); | ||
return result.Select(pair => | ||
new KeyValuePair<string, ExchangeTicker>(pair.Key, pair.Value.ToExchangeTicker(pair.Key))); | ||
} | ||
|
||
protected override async Task<ExchangeTicker> OnGetTickerAsync(string symbol) | ||
{ | ||
return (await GetTickersAsync()).Single(pair => pair.Key.Equals(symbol, StringComparison.InvariantCultureIgnoreCase)).Value; | ||
} | ||
|
||
protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync() | ||
{ | ||
return (await OnGetMarketSymbolsMetadataAsync()).Select(market => market.MarketSymbol); | ||
} | ||
|
||
protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnGetCurrenciesAsync() | ||
{ | ||
var result = await MakeJsonRequestAsync<IEnumerable<NDaxProduct>>("GetProducts", null, | ||
new Dictionary<string, object>() | ||
{ {"OMSId", 1}}, "POST"); | ||
_symbolToProductId = result.ToDictionary(product => product.Product, product => product.ProductId); | ||
return result.ToDictionary(product => product.Product, product => product.ToExchangeCurrency()); | ||
} | ||
|
||
protected override async Task<IEnumerable<ExchangeMarket>> OnGetMarketSymbolsMetadataAsync() | ||
{ | ||
var result = await MakeJsonRequestAsync<IEnumerable<Instrument>>("GetInstruments", null, | ||
new Dictionary<string, object>() | ||
{ {"OMSId", 1}}, "POST"); | ||
|
||
return result.Select(instrument => instrument.ToExchangeMarket()); | ||
} | ||
|
||
protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetCompletedOrderDetailsAsync(string symbol = null, | ||
DateTime? afterDate = null) | ||
{ | ||
var payload = new Dictionary<string, object>() | ||
{ | ||
{"nonce", await GenerateNonceAsync()} | ||
}; | ||
|
||
if (afterDate.HasValue) | ||
{ | ||
payload.Add("StartTimeStamp", new DateTimeOffset( afterDate.Value).ToUniversalTime().ToUnixTimeMilliseconds()); | ||
} | ||
if (symbol != null) | ||
{ | ||
payload.Add("InstrumentId", await GetInstrumentIdFromMarketSymbol(symbol)); | ||
} | ||
var result = await MakeJsonRequestAsync<IEnumerable<Order>>("GetOrdersHistory", null, | ||
payload, "POST"); | ||
|
||
return result.Select(order => order.ToExchangeOrderResult(_marketSymbolToInstrumentIdMapping)); | ||
} | ||
protected override async Task OnGetHistoricalTradesAsync(Func<IEnumerable<ExchangeTrade>, bool> callback, string marketSymbol, DateTime? startDate = null, | ||
DateTime? endDate = null) | ||
{ | ||
|
||
var payload = new Dictionary<string, object>() | ||
{ | ||
{"nonce", await GenerateNonceAsync()} | ||
}; | ||
|
||
if (marketSymbol != null) | ||
{ | ||
payload.Add("InstrumentId", await GetInstrumentIdFromMarketSymbol(marketSymbol)); | ||
} | ||
|
||
if (startDate.HasValue) | ||
{ | ||
payload.Add("StartTimeStamp", new DateTimeOffset( startDate.Value).ToUniversalTime().ToUnixTimeMilliseconds()); | ||
} | ||
if (endDate.HasValue) | ||
{ | ||
payload.Add("EndTimeStamp", new DateTimeOffset( endDate.Value).ToUniversalTime().ToUnixTimeMilliseconds()); | ||
} | ||
var result = await MakeJsonRequestAsync<IEnumerable<TradeHistory>>("GetTradesHistory", null, | ||
payload, "POST"); | ||
|
||
callback.Invoke( result.Select(history => history.ToExchangeTrade())); | ||
} | ||
|
||
protected override async Task<ExchangeDepositDetails> OnGetDepositAddressAsync(string symbol, | ||
bool forceRegenerate = false) | ||
{ | ||
var result = await MakeJsonRequestAsync<NdaxDepositInfo>("GetDepositInfo", null, | ||
new Dictionary<string, object>() | ||
{ | ||
{"ProductId", await GetProductIdFromCryptoCode(symbol)}, | ||
{"GenerateNewKey", forceRegenerate}, | ||
{"nonce", await GenerateNonceAsync()} | ||
}, "POST"); | ||
|
||
return result.ToExchangeDepositDetails(symbol); | ||
} | ||
|
||
|
||
protected override async Task<Dictionary<string, decimal>> OnGetAmountsAsync() | ||
{ | ||
var result = await MakeJsonRequestAsync<IEnumerable<AccountBalance>>("GetAccountPositions", null, | ||
new Dictionary<string, object>() | ||
{ | ||
{"nonce", await GenerateNonceAsync()} | ||
}, "POST"); | ||
return result.ToDictionary(balance => balance.ProductSymbol, balance => balance.Amount); | ||
} | ||
|
||
protected override async Task<Dictionary<string, decimal>> OnGetAmountsAvailableToTradeAsync() | ||
{ | ||
var result = await MakeJsonRequestAsync<IEnumerable<AccountBalance>>("GetAccountPositions", null, | ||
new Dictionary<string, object>() | ||
{ | ||
{"nonce", await GenerateNonceAsync()} | ||
}, "POST"); | ||
return result.ToDictionary(balance => balance.ProductSymbol, balance => balance.Amount- balance.Hold); | ||
} | ||
|
||
protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest order) | ||
{ | ||
var orderType = 0; | ||
var side = order.IsBuy? 0: 1; | ||
switch (order.OrderType) | ||
{ | ||
case OrderType.Market: | ||
orderType = 1; | ||
break; | ||
case OrderType.Limit: | ||
orderType = 2; | ||
break; | ||
|
||
case OrderType.Stop: | ||
orderType = 3; | ||
break; | ||
default: | ||
throw new ArgumentOutOfRangeException(); | ||
} | ||
|
||
var result = await MakeJsonRequestAsync<SendOrderResponse>("SendOrder", null, | ||
new Dictionary<string, object>() | ||
{ | ||
{"InstrumentId", await GetInstrumentIdFromMarketSymbol(order.MarketSymbol)}, | ||
{"Quantity", order.Amount}, | ||
{"LimitPrice", order.Price}, | ||
{"OrderType", orderType}, | ||
{"Side", side}, | ||
{"StopPrice", order.StopPrice}, | ||
{"TimeInForce", 0}, | ||
{"nonce", await GenerateNonceAsync()} | ||
}.Concat(order.ExtraParameters).ToDictionary(pair => pair.Key, pair =>pair.Value ), "POST"); | ||
|
||
if (result.Status.Equals("accepted", StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
return await GetOrderDetailsAsync(result.OrderId.ToString()); | ||
} | ||
throw new APIException($"{result.ErrorMsg}"); | ||
} | ||
|
||
protected override async Task<ExchangeOrderResult[]> OnPlaceOrdersAsync(params ExchangeOrderRequest[] order) | ||
{ | ||
var resp = new List<ExchangeOrderResult>(); | ||
foreach (var o in order) | ||
resp.Add(await PlaceOrderAsync(o)); | ||
return resp.ToArray(); | ||
} | ||
|
||
protected override async Task<ExchangeOrderResult> OnGetOrderDetailsAsync(string orderId, string symbol = null) | ||
{ | ||
await EnsureInstrumentIdsAvailable(); | ||
var result = await MakeJsonRequestAsync<Order>("GetOrderStatus", null, | ||
new Dictionary<string, object>() | ||
{ | ||
{"OrderId", int.Parse(orderId)}, | ||
{"nonce", await GenerateNonceAsync()} | ||
}, "POST"); | ||
return result.ToExchangeOrderResult(_marketSymbolToInstrumentIdMapping); | ||
} | ||
|
||
protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDetailsAsync(string symbol = null) | ||
{ | ||
await EnsureInstrumentIdsAvailable(); | ||
var result = await MakeJsonRequestAsync<IEnumerable<Order>>("GetOpenOrders", null, | ||
new Dictionary<string, object>() | ||
{ | ||
{"nonce", await GenerateNonceAsync()} | ||
}, "POST"); | ||
return result.Select(order => order.ToExchangeOrderResult(_marketSymbolToInstrumentIdMapping)).Where( | ||
orderResult => | ||
symbol == null || | ||
orderResult.MarketSymbol.Equals(symbol, StringComparison.InvariantCultureIgnoreCase)); | ||
} | ||
|
||
|
||
protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) | ||
{ | ||
var result = await MakeJsonRequestAsync<GenericResponse>("CancelOrder", null, | ||
new Dictionary<string, object>() | ||
{ | ||
{"OrderId", orderId}, | ||
{"nonce", await GenerateNonceAsync()} | ||
}, "POST"); | ||
if (!result.Result) | ||
{ | ||
throw new APIException($"{result.ErrorCode}:{result.ErrorMsg}"); | ||
} | ||
} | ||
|
||
protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, | ||
int? limit = null) | ||
{ | ||
var payload = new Dictionary<string, object>() | ||
{ | ||
{"InstrumentId", await GetInstrumentIdFromMarketSymbol(marketSymbol)} | ||
}; | ||
if (startDate.HasValue) | ||
{ | ||
payload.Add("FromDate", new DateTimeOffset( startDate.Value).ToUniversalTime().ToUnixTimeMilliseconds()); | ||
} | ||
|
||
var result = await MakeJsonRequestAsync<IEnumerable<IEnumerable<JToken>>>("GetTickerHistory", null, payload | ||
, "POST"); | ||
|
||
return result.Select(enumerable => new MarketCandle() | ||
{ | ||
Name = marketSymbol, | ||
ExchangeName = ExchangeName.Ndax, | ||
Timestamp = enumerable.ElementAt(0).Value<long>().UnixTimeStampToDateTimeMilliseconds(), | ||
HighPrice = enumerable.ElementAt(1).Value<decimal>(), | ||
LowPrice = enumerable.ElementAt(2).Value<decimal>(), | ||
OpenPrice = enumerable.ElementAt(3).Value<decimal>(), | ||
ClosePrice = enumerable.ElementAt(4).Value<decimal>(), | ||
BaseCurrencyVolume = enumerable.ElementAt(5).Value<double>(), | ||
|
||
}).Where(candle => !endDate.HasValue || candle.Timestamp <= endDate); | ||
} | ||
|
||
protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary<string, object> payload) | ||
{ | ||
if (PrivateApiKey != null && PublicApiKey != null) | ||
{ | ||
request.AddHeader("Authorization", | ||
CryptoUtility.BasicAuthenticationString(PublicApiKey.ToUnsecureString(), | ||
PrivateApiKey.ToUnsecureString())); | ||
} | ||
|
||
if (CanMakeAuthenticatedRequest(payload)) | ||
{ | ||
request.AddHeader("apToken", authenticationDetails.Token); | ||
payload.Add("OMSId", authenticationDetails.OMSId); | ||
payload.Add("AccountId", authenticationDetails.AccountId); | ||
|
||
} | ||
|
||
if (request.Method == "POST") | ||
{ | ||
await request.WritePayloadJsonToRequestAsync(payload); | ||
} | ||
} | ||
|
||
protected override bool CanMakeAuthenticatedRequest(IReadOnlyDictionary<string, object> payload) | ||
{ | ||
if (base.CanMakeAuthenticatedRequest(payload) && !payload.ContainsKey("skipauthrequest")) | ||
{ | ||
if (!(authenticationDetails?.Authenticated ?? false)) | ||
{ | ||
Authenticate().GetAwaiter().GetResult(); | ||
} | ||
|
||
return authenticationDetails?.Authenticated ?? false; | ||
} | ||
|
||
return false; | ||
} | ||
private async Task Authenticate() | ||
{ | ||
authenticationDetails = await MakeJsonRequestAsync<AuthenticateResult>("Authenticate", null, | ||
new Dictionary<string, object>() | ||
{ | ||
{"skipauthrequest", true} | ||
}, "POST"); | ||
} | ||
|
||
private async Task EnsureProductIdsAvailable() | ||
{ | ||
if (_symbolToProductId == null) | ||
{ | ||
await GetCurrenciesAsync(); | ||
} | ||
} | ||
|
||
private async Task EnsureInstrumentIdsAvailable() | ||
{ | ||
if (_marketSymbolToInstrumentIdMapping == null) | ||
{ | ||
await GetTickersAsync(); | ||
} | ||
} | ||
|
||
private async Task<long?> GetProductIdFromCryptoCode(string cryptoCode) | ||
{ | ||
cryptoCode = cryptoCode.ToUpperInvariant(); | ||
await EnsureProductIdsAvailable(); | ||
if (_symbolToProductId.TryGetValue(cryptoCode, out var value)) | ||
{ | ||
return value; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private async Task<long?> GetInstrumentIdFromMarketSymbol(string marketSymbol) | ||
{ | ||
marketSymbol = marketSymbol.ToUpperInvariant(); | ||
await EnsureInstrumentIdsAvailable(); | ||
if (_marketSymbolToInstrumentIdMapping.TryGetValue(marketSymbol, out var value)) | ||
{ | ||
return value; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
} | ||
|
||
|
||
public partial class ExchangeName | ||
{ | ||
public const string Ndax = "Ndax"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From their site, the capitalization is NDAX. Conflicts slightly w/ C# capitalization guidelines, but I've been changing the exchange capitalization to match their official titles. |
||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
simplify to ExchangeSharp.NDAX