Skip to content

Commit 292bef2

Browse files
authored
Bump BTSE to 3.1 and fix issues in private REST (#542)
1 parent 4fc9141 commit 292bef2

File tree

1 file changed

+126
-74
lines changed

1 file changed

+126
-74
lines changed

src/ExchangeSharp/API/Exchanges/BTSE/ExchangeBTSEAPI.cs

Lines changed: 126 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,36 @@ namespace ExchangeSharp
1010

1111
public sealed partial class ExchangeBTSEAPI : ExchangeAPI
1212
{
13-
public override string BaseUrl { get; set; } = "https://api.btse.com";
13+
public override string BaseUrl { get; set; } = "https://api.btse.com/spot";
14+
public const string TestnetUrl = "https://testapi.btse.io/spot";
1415

16+
public ExchangeBTSEAPI()
17+
{
18+
NonceStyle = NonceStyle.UnixMillisecondsString;
19+
}
1520
protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
1621
{
1722
return (await GetTickersAsync()).Select(pair => pair.Key);
1823
}
1924

2025
protected override async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>> OnGetTickersAsync()
2126
{
22-
JToken allPairs = await MakeJsonRequestAsync<JArray>("/spot/api/v3/market_summary");
23-
var tasks = allPairs.Select(async token => await this.ParseTickerAsync(token,
24-
token["symbol"].Value<string>(), "lowestAsk", "highestBid", "last", "volume", null,
25-
null, TimestampType.UnixMilliseconds, "base", "quote", "symbol"));
27+
JToken allPairs = await MakeJsonRequestAsync<JToken>("/api/v3.1/market_summary", BaseUrl);
28+
var tasks = allPairs.Select(async token => await ParseBTSETicker(token,
29+
token["symbol"].Value<string>()));
2630

2731
return (await Task.WhenAll(tasks)).Select(ticker =>
2832
new KeyValuePair<string, ExchangeTicker>(ticker.MarketSymbol, ticker));
2933
}
3034

3135
protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol)
3236
{
33-
JToken ticker = await MakeJsonRequestAsync<JObject>("/spot/api/v3/market_summary", null,
37+
JToken ticker = await MakeJsonRequestAsync<JToken>("/api/v3.1/market_summary", BaseUrl,
3438
new Dictionary<string, object>()
3539
{
3640
{"symbol", marketSymbol}
3741
});
38-
return await this.ParseTickerAsync(ticker, marketSymbol, "lowestAsk", "highestBid", "last", "volume", null,
39-
null, TimestampType.UnixMilliseconds, "base", "quote", "symbol");
42+
return await ParseBTSETicker(ticker, marketSymbol);
4043
}
4144

4245
protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(string marketSymbol,
@@ -59,7 +62,7 @@ protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(strin
5962
payload.Add("end", startDate.Value.UnixTimestampFromDateTimeMilliseconds());
6063
}
6164

62-
JToken ticker = await MakeJsonRequestAsync<JArray>("/spot/api/v3/ohlcv", null, payload, "GET");
65+
JToken ticker = await MakeJsonRequestAsync<JArray>("/api/v3.1/ohlcv", null, payload, "GET");
6366
return ticker.Select(token =>
6467
this.ParseCandle(token, marketSymbol, periodSeconds, 1, 2, 3, 4, 0, TimestampType.UnixMilliseconds, 5));
6568
}
@@ -69,7 +72,7 @@ protected override async Task OnCancelOrderAsync(string orderId, string? marketS
6972
var payload = await GetNoncePayloadAsync();
7073

7174
payload["order_id"] = orderId.ConvertInvariant<long>();
72-
var url = new UriBuilder(BaseUrl) {Path = "/spot/api/v3/order"};
75+
var url = new UriBuilder(BaseUrl) {Path = "/api/v3.1/order"};
7376
url.AppendPayloadToQuery(new Dictionary<string, object>()
7477
{
7578
{"symbol", marketSymbol},
@@ -80,49 +83,43 @@ await MakeJsonRequestAsync<JToken>(url.ToStringInvariant().Replace(BaseUrl, ""),
8083
requestMethod: "DELETE", payload: payload);
8184
}
8285

83-
protected override async Task<Dictionary<string, decimal>> OnGetAmountsAsync()
86+
protected override Task<Dictionary<string, decimal>> OnGetAmountsAsync()
8487
{
85-
var payload = await GetNoncePayloadAsync();
86-
87-
var result = await MakeJsonRequestAsync<JToken>("/spot/api/v3/user/wallet",
88-
requestMethod: "GET", payload: payload);
89-
return Extract(result, token => (token["currency"].Value<string>(), token["total"].Value<decimal>()));
88+
return GetBTSEBalance(false);
9089
}
9190

92-
protected override async Task<Dictionary<string, decimal>> OnGetAmountsAvailableToTradeAsync()
91+
protected override Task<Dictionary<string, decimal>> OnGetAmountsAvailableToTradeAsync()
9392
{
94-
var payload = await GetNoncePayloadAsync();
95-
96-
var result = await MakeJsonRequestAsync<JToken>("/spot/api/v3/user/wallet",
97-
requestMethod: "GET", payload: payload);
98-
return Extract(result, token => (token["currency"].Value<string>(), token["available"].Value<decimal>()));
93+
return GetBTSEBalance(true);
9994
}
10095

10196
protected override async Task<Dictionary<string, decimal>> OnGetFeesAsync()
10297
{
10398
var payload = await GetNoncePayloadAsync();
10499

105-
var result = await MakeJsonRequestAsync<JToken>("/spot/api/v3/user/fees",
100+
var result = await MakeJsonRequestAsync<JToken>("/api/v3.1/user/fees",
106101
requestMethod: "GET", payload: payload);
107102

108-
//taker or maker fees in BTSE.. i chose take for here
109-
return Extract(result, token => (token["symbol"].Value<string>(), token["taker"].Value<decimal>()));
103+
//taker or maker fees in BTSE.. i chose maker for here
104+
return Extract(result, token => (token["symbol"].Value<string>(), token["makerFee"].Value<decimal>()));
110105
}
111106

112107
protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDetailsAsync(
113108
string? marketSymbol = null)
114109
{
115110
if (marketSymbol == null) throw new ArgumentNullException(nameof(marketSymbol));
116111
var payload = await GetNoncePayloadAsync();
117-
var url = new UriBuilder(BaseUrl) {Path = "/spot/api/v3/open_orders"};
112+
113+
var url = new UriBuilder(BaseUrl) {Path = "/api/v3.1/user/open_orders"};
118114
url.AppendPayloadToQuery(new Dictionary<string, object>()
119115
{
120116
{"symbol", marketSymbol}
121117
});
118+
119+
122120
var result = await MakeJsonRequestAsync<JToken>(url.ToStringInvariant().Replace(BaseUrl, ""),
123121
requestMethod: "GET", payload: payload);
124122

125-
//taker or maker fees in BTSE.. i chose take for here
126123
return Extract2(result, token => new ExchangeOrderResult()
127124
{
128125
Amount = token["size"].Value<decimal>(),
@@ -135,22 +132,51 @@ protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDe
135132
});
136133
}
137134

138-
public override async Task<ExchangeOrderResult[]> PlaceOrdersAsync(params ExchangeOrderRequest[] orders)
139-
{
140-
var payload = await GetNoncePayloadAsync();
141-
payload.Add("body", orders.Select(request => new
135+
protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrderRequest request)
136+
{var payload = await GetNoncePayloadAsync();
137+
138+
var dict = new Dictionary<string, object>();
139+
140+
var id = request.OrderId ?? request.ClientOrderId;
141+
if (!string.IsNullOrEmpty(id))
142142
{
143-
size = request.Amount,
144-
side = request.IsBuy ? "BUY" : "SELL",
145-
price = request.Price,
146-
stopPrice = request.StopPrice,
147-
symbol = request.MarketSymbol,
148-
txType = request.OrderType == OrderType.Limit ? "LIMIT" :
149-
request.OrderType == OrderType.Stop ? "STOP" : null,
150-
type = request.OrderType == OrderType.Limit ? "LIMIT" :
151-
request.OrderType == OrderType.Market ? "MARKET" : null
152-
}));
153-
var result = await MakeJsonRequestAsync<JToken>("/spot/api/v3/order",
143+
dict.Add("clOrderID",id);
144+
145+
}
146+
147+
dict.Add("size", request.Amount);
148+
dict.Add("side", request.IsBuy ? "BUY" : "SELL");
149+
dict.Add("price", request.Price);
150+
dict.Add("symbol", request.MarketSymbol);
151+
152+
switch (request.OrderType )
153+
{
154+
case OrderType.Limit:
155+
dict.Add("txType", "LIMIT");
156+
dict.Add("type", "LIMIT");
157+
break;
158+
case OrderType.Market:
159+
160+
dict.Add("type", "MARKET");
161+
break;
162+
case OrderType.Stop:
163+
dict.Add("stopPrice", request.StopPrice);
164+
dict.Add("txType", "STOP");
165+
break;
166+
}
167+
168+
foreach (var extraParameter in request.ExtraParameters)
169+
{
170+
if (!dict.ContainsKey(extraParameter.Key))
171+
{
172+
dict.Add(extraParameter.Key, extraParameter.Value);
173+
}
174+
}
175+
176+
177+
payload.Add("body", dict);
178+
179+
var result = await MakeJsonRequestAsync<JToken>("/api/v3.1/order",
154180
requestMethod: "POST", payload: payload);
155181
return Extract2(result, token =>
156182
{
@@ -185,42 +211,18 @@ public override async Task<ExchangeOrderResult[]> PlaceOrdersAsync(params Exchan
185211
{
186212
Message = token["message"].Value<string>(),
187213
OrderId = token["orderID"].Value<string>(),
188-
IsBuy = token["orderType"].Value<string>().ToLowerInvariant() == "buy",
214+
IsBuy = token["side"].Value<string>().ToLowerInvariant() == "buy",
189215
Price = token["price"].Value<decimal>(),
190216
MarketSymbol = token["symbol"].Value<string>(),
191217
Result = status,
192218
Amount = token["size"].Value<decimal>(),
193219
OrderDate = token["timestamp"].ConvertInvariant<long>().UnixTimeStampToDateTimeMilliseconds(),
194-
};
195-
}).ToArray();
196-
}
197-
198-
private Dictionary<TKey, TValue> Extract<TKey, TValue>(JToken token, Func<JToken, (TKey, TValue)> processor)
199-
{
200-
if (token is JArray resultArr)
201-
{
202-
return resultArr.Select(processor.Invoke)
203-
.ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);
204-
}
205-
206-
var resItem = processor.Invoke(token);
207-
return new Dictionary<TKey, TValue>()
208-
{
209-
{resItem.Item1, resItem.Item2}
210-
};
211-
}
212-
213-
private IEnumerable<TValue> Extract2<TValue>(JToken token, Func<JToken, TValue> processor)
214-
{
215-
if (token is JArray resultArr)
216-
{
217-
return resultArr.Select(processor.Invoke);
218-
}
220+
ClientOrderId = token["clOrderID"].Value<string>(),
221+
AveragePrice = token["averageFillPrice"].Value<decimal>(),
222+
AmountFilled = token["fillSize"].Value<decimal>(),
219223

220-
return new List<TValue>()
221-
{
222-
processor.Invoke(token)
223-
};
224+
};
225+
}).First();
224226
}
225227

226228
protected override Uri ProcessRequestUrl(UriBuilder url, Dictionary<string, object> payload, string method)
@@ -229,7 +231,6 @@ protected override Uri ProcessRequestUrl(UriBuilder url, Dictionary<string, obje
229231
{
230232
url.AppendPayloadToQuery(payload);
231233
}
232-
233234
return base.ProcessRequestUrl(url, payload, method);
234235
}
235236

@@ -252,10 +253,17 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti
252253
json = "";
253254
}
254255

256+
var passphrase = Passphrase?.ToUnsecureString();
257+
if (string.IsNullOrEmpty(passphrase))
258+
{
259+
passphrase = PrivateApiKey?.ToUnsecureString();
260+
}
261+
255262
var hexSha384 = CryptoUtility.SHA384Sign(
256263
$"{request.RequestUri.PathAndQuery.Replace("/spot", string.Empty)}{nonce}{json}",
257-
PrivateApiKey.ToUnsecureString());
264+
passphrase);
258265
request.AddHeader("btse-sign", hexSha384);
266+
request.AddHeader("btse-nonce", nonce);
259267
request.AddHeader("btse-api", PublicApiKey.ToUnsecureString());
260268
await request.WriteToRequestAsync(json);
261269
}
@@ -273,6 +281,50 @@ protected override async Task<Dictionary<string, object>> GetNoncePayloadAsync()
273281

274282
return result;
275283
}
284+
285+
private Dictionary<TKey, TValue> Extract<TKey, TValue>(JToken token, Func<JToken, (TKey, TValue)> processor)
286+
{
287+
if (token is JArray resultArr)
288+
{
289+
return resultArr.Select(processor.Invoke)
290+
.ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);
291+
}
292+
293+
var resItem = processor.Invoke(token);
294+
return new Dictionary<TKey, TValue>()
295+
{
296+
{resItem.Item1, resItem.Item2}
297+
};
298+
}
299+
300+
private IEnumerable<TValue> Extract2<TValue>(JToken token, Func<JToken, TValue> processor)
301+
{
302+
if (token is JArray resultArr)
303+
{
304+
return resultArr.Select(processor.Invoke);
305+
}
306+
307+
return new List<TValue>()
308+
{
309+
processor.Invoke(token)
310+
};
311+
}
312+
313+
private async Task<ExchangeTicker> ParseBTSETicker(JToken ticker, string marketSymbol)
314+
{
315+
return await this.ParseTickerAsync(ticker, marketSymbol, "lowestAsk", "highestBid", "last", "volume", null,
316+
null, TimestampType.UnixMilliseconds, "base", "quote", "symbol");
317+
}
318+
319+
private async Task<Dictionary<string, decimal>> GetBTSEBalance(bool availableOnly)
320+
{
321+
var payload = await GetNoncePayloadAsync();
322+
323+
var result = await MakeJsonRequestAsync<JToken>("/api/v3.1/user/wallet",
324+
requestMethod: "GET", payload: payload);
325+
return Extract(result, token => (token["currency"].Value<string>(), token[availableOnly?"available": "total"].Value<decimal>()));
326+
}
327+
276328
}
277329

278330
public partial class ExchangeName

0 commit comments

Comments
 (0)