Skip to content
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 7 commits into from
Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from 6 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
27 changes: 27 additions & 0 deletions ExchangeSharp/API/Exchanges/Ndax/Converters/BoolConverter.cs
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplify to ExchangeSharp.NDAX

{
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);
}
}
}
360 changes: 360 additions & 0 deletions ExchangeSharp/API/Exchanges/Ndax/ExchangeNdaxAPI.cs
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";
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

}
}
Loading