@@ -202,6 +202,219 @@ protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(
202202 7
203203 ) ) ;
204204 }
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+ } ;
205418 }
206419
207420 public partial class ExchangeName
0 commit comments