@@ -202,6 +202,219 @@ protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(
202
202
7
203
203
) ) ;
204
204
}
205
+
206
+ protected override async Task < Dictionary < string , decimal > > OnGetAmountsAsync ( )
207
+ {
208
+ var token = await GetBalance ( ) ;
209
+ return ( token [ "balances" ] ?? throw new InvalidOperationException ( ) )
210
+ . Select (
211
+ x =>
212
+ new
213
+ {
214
+ Currency = x [ "asset" ] . Value < string > ( ) ,
215
+ TotalBalance = x [ "free" ] . Value < decimal > ( )
216
+ + x [ "locked" ] . Value < decimal > ( )
217
+ }
218
+ )
219
+ . ToDictionary ( k => k . Currency , v => v . TotalBalance ) ;
220
+ }
221
+
222
+ protected override async Task < Dictionary < string , decimal > > OnGetAmountsAvailableToTradeAsync ( )
223
+ {
224
+ var token = await GetBalance ( ) ;
225
+ return ( token [ "balances" ] ?? throw new InvalidOperationException ( ) )
226
+ . Select (
227
+ x =>
228
+ new
229
+ {
230
+ Currency = x [ "asset" ] . Value < string > ( ) ,
231
+ AvailableBalance = x [ "free" ] . Value < decimal > ( )
232
+ + x [ "locked" ] . Value < decimal > ( )
233
+ }
234
+ )
235
+ . ToDictionary ( k => k . Currency , v => v . AvailableBalance ) ;
236
+ }
237
+
238
+ protected override async Task < IEnumerable < ExchangeOrderResult > > OnGetOpenOrderDetailsAsync (
239
+ string marketSymbol = null )
240
+ {
241
+ if ( string . IsNullOrEmpty ( marketSymbol ) )
242
+ {
243
+ throw new ArgumentNullException ( nameof ( marketSymbol ) , $ "Market symbol cannot be null") ;
244
+ }
245
+
246
+ var token = await MakeJsonRequestAsync < JToken > ( $ "/openOrders?symbol={ marketSymbol . ToUpperInvariant ( ) } ",
247
+ payload : await GetNoncePayloadAsync ( ) ) ;
248
+
249
+ return token . Select ( ParseOrder ) ;
250
+ }
251
+
252
+ protected override async Task < ExchangeOrderResult > OnPlaceOrderAsync ( ExchangeOrderRequest order )
253
+ {
254
+ if ( string . IsNullOrEmpty ( order . MarketSymbol ) )
255
+ {
256
+ throw new ArgumentNullException ( nameof ( order . MarketSymbol ) ) ;
257
+ }
258
+ if ( order . Price == null && order . OrderType != OrderType . Market )
259
+ {
260
+ throw new ArgumentNullException ( nameof ( order . Price ) ) ;
261
+ }
262
+
263
+ var payload = await GetNoncePayloadAsync ( ) ;
264
+ payload [ "symbol" ] = order . MarketSymbol ;
265
+ payload [ "quantity" ] = order . Amount ;
266
+ payload [ "side" ] = order . IsBuy ? "BUY" : "SELL" ;
267
+
268
+ if ( order . OrderType != OrderType . Market )
269
+ {
270
+ decimal orderPrice = await ClampOrderPrice ( order . MarketSymbol , order . Price . Value ) ;
271
+ payload [ "price" ] = orderPrice ;
272
+ if ( order . IsPostOnly != true )
273
+ payload [ "type" ] = "LIMIT" ;
274
+ }
275
+
276
+ switch ( order . OrderType )
277
+ {
278
+ case OrderType . Limit when ! order . IsPostOnly . GetValueOrDefault ( ) :
279
+ payload [ "type" ] = "LIMIT" ;
280
+ break ;
281
+ case OrderType . Limit when order . IsPostOnly . GetValueOrDefault ( ) :
282
+ payload [ "type" ] = "LIMIT_MAKER" ;
283
+ break ;
284
+ case OrderType . Market :
285
+ payload [ "type" ] = "MARKET" ;
286
+ break ;
287
+ case OrderType . Stop :
288
+ default :
289
+ throw new ArgumentOutOfRangeException ( nameof ( order . OrderType ) ) ;
290
+ }
291
+
292
+ if ( ! string . IsNullOrEmpty ( order . ClientOrderId ) )
293
+ {
294
+ payload [ "clientOrderId" ] = order . ClientOrderId ;
295
+ }
296
+
297
+ order . ExtraParameters . CopyTo ( payload ) ;
298
+
299
+ var token = await MakeJsonRequestAsync < JToken > ( "/order" , BaseUrl , payload , "POST" ) ;
300
+
301
+ // MEXC does not return order status with the place order response, so we need to send one more request to get the order details.
302
+ return await GetOrderDetailsAsync (
303
+ ( token [ "orderId" ] ?? throw new InvalidOperationException ( ) ) . ToStringInvariant ( ) ,
304
+ order . MarketSymbol
305
+ ) ;
306
+ }
307
+
308
+ protected override async Task < ExchangeOrderResult > OnGetOrderDetailsAsync ( string orderId , string marketSymbol = null , bool isClientOrderId = false )
309
+ {
310
+ if ( string . IsNullOrEmpty ( marketSymbol ) )
311
+ {
312
+ throw new ArgumentNullException ( nameof ( marketSymbol ) , $ "Market symbol cannot be null") ;
313
+ }
314
+
315
+ if ( string . IsNullOrEmpty ( orderId ) )
316
+ {
317
+ throw new ArgumentNullException (
318
+ nameof ( orderId ) ,
319
+ "Order details request requires order ID or client-supplied order ID"
320
+ ) ;
321
+ }
322
+
323
+ var param = isClientOrderId ? $ "origClientOrderId={ orderId } " : $ "orderId={ orderId } ";
324
+ var token = await MakeJsonRequestAsync < JToken > ( $ "/order?symbol={ marketSymbol . ToUpperInvariant ( ) } &{ param } ",
325
+ payload : await GetNoncePayloadAsync ( ) ) ;
326
+
327
+ return ParseOrder ( token ) ;
328
+ }
329
+
330
+ protected override async Task OnCancelOrderAsync ( string orderId , string marketSymbol = null , bool isClientOrderId = false )
331
+ {
332
+ if ( string . IsNullOrEmpty ( orderId ) )
333
+ {
334
+ throw new ArgumentNullException (
335
+ nameof ( orderId ) ,
336
+ "Cancel order request requires order ID"
337
+ ) ;
338
+ }
339
+
340
+ if ( string . IsNullOrEmpty ( marketSymbol ) )
341
+ {
342
+ throw new ArgumentNullException (
343
+ nameof ( marketSymbol ) ,
344
+ "Cancel order request requires symbol"
345
+ ) ;
346
+ }
347
+
348
+ var payload = await GetNoncePayloadAsync ( ) ;
349
+ payload [ "symbol" ] = marketSymbol ! ;
350
+ switch ( isClientOrderId )
351
+ {
352
+ case true :
353
+ payload [ "origClientOrderId" ] = orderId ;
354
+ break ;
355
+ default :
356
+ payload [ "orderId" ] = orderId ;
357
+ break ;
358
+ }
359
+
360
+ await MakeJsonRequestAsync < JToken > ( "/order" , BaseUrl , payload , "DELETE" ) ;
361
+ }
362
+
363
+ private async Task < JToken > GetBalance ( )
364
+ {
365
+ var token = await MakeJsonRequestAsync < JToken > ( "/account" , payload : await GetNoncePayloadAsync ( ) ) ;
366
+ return token ;
367
+ }
368
+
369
+ private static ExchangeOrderResult ParseOrder ( JToken token )
370
+ {
371
+ // [
372
+ // {
373
+ // "symbol": "LTCBTC",
374
+ // "orderId": "C02__443776347957968896088",
375
+ // "orderListId": -1,
376
+ // "clientOrderId": "",
377
+ // "price": "0.001395",
378
+ // "origQty": "0.004",
379
+ // "executedQty": "0",
380
+ // "cummulativeQuoteQty": "0",
381
+ // "status": "NEW",
382
+ // "timeInForce": null,
383
+ // "type": "LIMIT",
384
+ // "side": "SELL",
385
+ // "stopPrice": null,
386
+ // "icebergQty": null,
387
+ // "time": 1721586762185,
388
+ // "updateTime": null,
389
+ // "isWorking": true,
390
+ // "origQuoteOrderQty": "0.00000558"
391
+ // }
392
+ // ]
393
+
394
+ return new ExchangeOrderResult
395
+ {
396
+ OrderId = token [ "orderId" ] . ToStringInvariant ( ) ,
397
+ ClientOrderId = token [ "orderListId" ] . ToStringInvariant ( ) ,
398
+ MarketSymbol = token [ "symbol" ] . ToStringInvariant ( ) ,
399
+ Amount = token [ "origQty" ] . ConvertInvariant < decimal > ( ) ,
400
+ AmountFilled = token [ "executedQty" ] . ConvertInvariant < decimal > ( ) ,
401
+ Price = token [ "price" ] . ConvertInvariant < decimal > ( ) ,
402
+ IsBuy = token [ "side" ] . ToStringInvariant ( ) == "BUY" ,
403
+ OrderDate = token [ "time" ] . ConvertInvariant < long > ( ) . UnixTimeStampToDateTimeMilliseconds ( ) ,
404
+ Result = ParseOrderStatus ( token [ "status" ] . ToStringInvariant ( ) )
405
+ } ;
406
+ }
407
+
408
+ private static ExchangeAPIOrderResult ParseOrderStatus ( string status ) =>
409
+ status . ToUpperInvariant ( ) switch
410
+ {
411
+ "NEW" => ExchangeAPIOrderResult . Open ,
412
+ "PARTIALLY_FILLED" => ExchangeAPIOrderResult . FilledPartially ,
413
+ "FILLED" => ExchangeAPIOrderResult . Filled ,
414
+ "PARTIALLY_CANCELED" => ExchangeAPIOrderResult . FilledPartiallyAndCancelled ,
415
+ "CANCELED" => ExchangeAPIOrderResult . Canceled ,
416
+ _ => ExchangeAPIOrderResult . Unknown
417
+ } ;
205
418
}
206
419
207
420
public partial class ExchangeName
0 commit comments