1
- namespace ExchangeSharp {
1
+ using Newtonsoft . Json ;
2
+
3
+ namespace ExchangeSharp
4
+ {
2
5
using Newtonsoft . Json . Linq ;
3
6
using System ;
4
7
using System . Collections . Generic ;
5
8
using System . Linq ;
6
9
using System . Threading . Tasks ;
7
10
8
- public sealed partial class ExchangeBTSEAPI : ExchangeAPI {
11
+ public sealed partial class ExchangeBTSEAPI : ExchangeAPI
12
+ {
9
13
public override string BaseUrl { get ; set ; } = "https://api.btse.com" ;
10
14
11
15
protected override async Task < IEnumerable < string > > OnGetMarketSymbolsAsync ( )
@@ -16,7 +20,8 @@ protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
16
20
protected override async Task < IEnumerable < KeyValuePair < string , ExchangeTicker > > > OnGetTickersAsync ( )
17
21
{
18
22
JToken allPairs = await MakeJsonRequestAsync < JArray > ( "/spot/api/v3/market_summary" ) ;
19
- var tasks = allPairs . Select ( async token => await this . ParseTickerAsync ( token , token [ "symbol" ] . Value < string > ( ) , "lowestAsk" , "highestBid" , "last" , "volume" , null ,
23
+ var tasks = allPairs . Select ( async token => await this . ParseTickerAsync ( token ,
24
+ token [ "symbol" ] . Value < string > ( ) , "lowestAsk" , "highestBid" , "last" , "volume" , null ,
20
25
null , TimestampType . UnixMilliseconds , "base" , "quote" , "symbol" ) ) ;
21
26
22
27
return ( await Task . WhenAll ( tasks ) ) . Select ( ticker =>
@@ -25,15 +30,17 @@ protected override async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>>
25
30
26
31
protected override async Task < ExchangeTicker > OnGetTickerAsync ( string marketSymbol )
27
32
{
28
- JToken ticker = await MakeJsonRequestAsync < JObject > ( "/spot/api/v3/market_summary" , null , new Dictionary < string , object > ( )
29
- {
30
- { "symbol" , marketSymbol }
31
- } ) ;
33
+ JToken ticker = await MakeJsonRequestAsync < JObject > ( "/spot/api/v3/market_summary" , null ,
34
+ new Dictionary < string , object > ( )
35
+ {
36
+ { "symbol" , marketSymbol }
37
+ } ) ;
32
38
return await this . ParseTickerAsync ( ticker , marketSymbol , "lowestAsk" , "highestBid" , "last" , "volume" , null ,
33
39
null , TimestampType . UnixMilliseconds , "base" , "quote" , "symbol" ) ;
34
40
}
35
41
36
- protected override async Task < IEnumerable < MarketCandle > > OnGetCandlesAsync ( string marketSymbol , int periodSeconds , DateTime ? startDate = null , DateTime ? endDate = null ,
42
+ protected override async Task < IEnumerable < MarketCandle > > OnGetCandlesAsync ( string marketSymbol ,
43
+ int periodSeconds , DateTime ? startDate = null , DateTime ? endDate = null ,
37
44
int ? limit = null )
38
45
{
39
46
var payload = new Dictionary < string , object > ( )
@@ -46,6 +53,7 @@ protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(strin
46
53
{
47
54
payload . Add ( "start" , startDate . Value . UnixTimestampFromDateTimeMilliseconds ( ) ) ;
48
55
}
56
+
49
57
if ( endDate != null )
50
58
{
51
59
payload . Add ( "end" , startDate . Value . UnixTimestampFromDateTimeMilliseconds ( ) ) ;
@@ -56,15 +64,219 @@ protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(strin
56
64
this . ParseCandle ( token , marketSymbol , periodSeconds , 1 , 2 , 3 , 4 , 0 , TimestampType . UnixMilliseconds , 5 ) ) ;
57
65
}
58
66
67
+ protected override async Task OnCancelOrderAsync ( string orderId , string ? marketSymbol = null )
68
+ {
69
+ var payload = await GetNoncePayloadAsync ( ) ;
70
+
71
+ payload [ "order_id" ] = orderId . ConvertInvariant < long > ( ) ;
72
+ var url = new UriBuilder ( BaseUrl ) { Path = "/spot/api/v3/order" } ;
73
+ url . AppendPayloadToQuery ( new Dictionary < string , object > ( )
74
+ {
75
+ { "symbol" , marketSymbol } ,
76
+ { "orderID" , orderId }
77
+ } ) ;
78
+
79
+ await MakeJsonRequestAsync < JToken > ( url . ToStringInvariant ( ) . Replace ( BaseUrl , "" ) ,
80
+ requestMethod : "DELETE" , payload : payload ) ;
81
+ }
82
+
83
+ protected override async Task < Dictionary < string , decimal > > OnGetAmountsAsync ( )
84
+ {
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 > ( ) ) ) ;
90
+ }
91
+
92
+ protected override async Task < Dictionary < string , decimal > > OnGetAmountsAvailableToTradeAsync ( )
93
+ {
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 > ( ) ) ) ;
99
+ }
100
+
101
+ protected override async Task < Dictionary < string , decimal > > OnGetFeesAsync ( )
102
+ {
103
+ var payload = await GetNoncePayloadAsync ( ) ;
104
+
105
+ var result = await MakeJsonRequestAsync < JToken > ( "/spot/api/v3/user/fees" ,
106
+ requestMethod : "GET" , payload : payload ) ;
107
+
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 > ( ) ) ) ;
110
+ }
111
+
112
+ protected override async Task < IEnumerable < ExchangeOrderResult > > OnGetOpenOrderDetailsAsync (
113
+ string ? marketSymbol = null )
114
+ {
115
+ if ( marketSymbol == null ) throw new ArgumentNullException ( nameof ( marketSymbol ) ) ;
116
+ var payload = await GetNoncePayloadAsync ( ) ;
117
+ var url = new UriBuilder ( BaseUrl ) { Path = "/spot/api/v3/open_orders" } ;
118
+ url . AppendPayloadToQuery ( new Dictionary < string , object > ( )
119
+ {
120
+ { "symbol" , marketSymbol }
121
+ } ) ;
122
+ var result = await MakeJsonRequestAsync < JToken > ( url . ToStringInvariant ( ) . Replace ( BaseUrl , "" ) ,
123
+ requestMethod : "GET" , payload : payload ) ;
124
+
125
+ //taker or maker fees in BTSE.. i chose take for here
126
+ return Extract2 ( result , token => new ExchangeOrderResult ( )
127
+ {
128
+ Amount = token [ "size" ] . Value < decimal > ( ) ,
129
+ AmountFilled = token [ "filledSize" ] . Value < decimal > ( ) ,
130
+ OrderId = token [ "orderID" ] . Value < string > ( ) ,
131
+ IsBuy = token [ "side" ] . Value < string > ( ) == "BUY" ,
132
+ Price = token [ "price" ] . Value < decimal > ( ) ,
133
+ MarketSymbol = token [ "symbol" ] . Value < string > ( ) ,
134
+ OrderDate = token [ "timestamp" ] . ConvertInvariant < long > ( ) . UnixTimeStampToDateTimeMilliseconds ( )
135
+ } ) ;
136
+ }
137
+
138
+ public override async Task < ExchangeOrderResult [ ] > PlaceOrdersAsync ( params ExchangeOrderRequest [ ] orders )
139
+ {
140
+ var payload = await GetNoncePayloadAsync ( ) ;
141
+ payload . Add ( "body" , orders . Select ( request => new
142
+ {
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" ,
154
+ requestMethod : "POST" , payload : payload ) ;
155
+ return Extract2 ( result , token =>
156
+ {
157
+ var status = ExchangeAPIOrderResult . Unknown ;
158
+ switch ( token [ "status" ] . Value < int > ( ) )
159
+ {
160
+ case 2 :
161
+ status = ExchangeAPIOrderResult . Pending ;
162
+ break ;
163
+ case 4 :
164
+ status = ExchangeAPIOrderResult . Filled ;
165
+ break ;
166
+ case 5 :
167
+ status = ExchangeAPIOrderResult . FilledPartially ;
168
+ break ;
169
+ case 6 :
170
+ status = ExchangeAPIOrderResult . Canceled ;
171
+ break ;
172
+ case 9 : //trigger inserted
173
+ case 10 : //trigger activated
174
+ status = ExchangeAPIOrderResult . Pending ;
175
+ break ;
176
+ case 15 : //rejected
177
+ status = ExchangeAPIOrderResult . Error ;
178
+ break ;
179
+ case 16 : //not found
180
+ status = ExchangeAPIOrderResult . Unknown ;
181
+ break ;
182
+ }
183
+
184
+ return new ExchangeOrderResult ( )
185
+ {
186
+ Message = token [ "message" ] . Value < string > ( ) ,
187
+ OrderId = token [ "orderID" ] . Value < string > ( ) ,
188
+ IsBuy = token [ "orderType" ] . Value < string > ( ) . ToLowerInvariant ( ) == "buy" ,
189
+ Price = token [ "price" ] . Value < decimal > ( ) ,
190
+ MarketSymbol = token [ "symbol" ] . Value < string > ( ) ,
191
+ Result = status ,
192
+ Amount = token [ "size" ] . Value < decimal > ( ) ,
193
+ 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
+ }
219
+
220
+ return new List < TValue > ( )
221
+ {
222
+ processor . Invoke ( token )
223
+ } ;
224
+ }
225
+
59
226
protected override Uri ProcessRequestUrl ( UriBuilder url , Dictionary < string , object > payload , string method )
60
227
{
61
- if ( method == "GET" && ( payload ? . Count ?? 0 ) != 0 )
228
+ if ( method == "GET" && ( payload ? . Count ?? 0 ) != 0 && ! payload . ContainsKey ( "nonce" ) )
62
229
{
63
230
url . AppendPayloadToQuery ( payload ) ;
64
231
}
232
+
65
233
return base . ProcessRequestUrl ( url , payload , method ) ;
66
234
}
235
+
236
+ protected override async Task ProcessRequestAsync ( IHttpWebRequest request , Dictionary < string , object > payload )
237
+ {
238
+ if ( CanMakeAuthenticatedRequest ( payload ) )
239
+ {
240
+ if ( payload . TryGetValue ( "body" , out var body ) )
241
+ {
242
+ payload . Remove ( "body" ) ;
243
+ }
244
+
245
+ var nonce = payload [ "nonce" ] . ToString ( ) ;
246
+ payload . Remove ( "nonce" ) ;
247
+
248
+
249
+ var json = JsonConvert . SerializeObject ( body ?? payload ) ;
250
+ if ( json == "{}" )
251
+ {
252
+ json = "" ;
253
+ }
254
+
255
+ var hexSha384 = CryptoUtility . SHA384Sign (
256
+ $ "{ request . RequestUri . PathAndQuery . Replace ( "/spot" , string . Empty ) } { nonce } { json } ",
257
+ PrivateApiKey . ToUnsecureString ( ) ) ;
258
+ request . AddHeader ( "btse-sign" , hexSha384 ) ;
259
+ request . AddHeader ( "btse-api" , PublicApiKey . ToUnsecureString ( ) ) ;
260
+ await request . WriteToRequestAsync ( json ) ;
261
+ }
262
+
263
+ await base . ProcessRequestAsync ( request , payload ) ;
264
+ }
265
+
266
+ protected override async Task < Dictionary < string , object > > GetNoncePayloadAsync ( )
267
+ {
268
+ var result = await base . GetNoncePayloadAsync ( ) ;
269
+ if ( result . ContainsKey ( "recvWindow" ) )
270
+ {
271
+ result . Remove ( "recvWindow" ) ;
272
+ }
273
+
274
+ return result ;
275
+ }
67
276
}
68
277
69
- public partial class ExchangeName { public const string BTSE = "BTSE" ; }
278
+ public partial class ExchangeName
279
+ {
280
+ public const string BTSE = "BTSE" ;
281
+ }
70
282
}
0 commit comments