From c1790bf86e91a465b607196e67727ba5af253071 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:17:41 +0100 Subject: [PATCH] fix: liquidity limited trading balance check (#3061) * fix: liquidity limited trading balance check * fix: MEXC ZCHF trades --- src/integration/exchange/dto/mexc.dto.ts | 11 +++++++++ .../exchange/services/exchange.service.ts | 2 +- .../exchange/services/mexc.service.ts | 19 +++++++++++++-- .../actions/base/ccxt-exchange.adapter.ts | 24 +++++++++---------- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/integration/exchange/dto/mexc.dto.ts b/src/integration/exchange/dto/mexc.dto.ts index 679c91e8f2..3f61364fc2 100644 --- a/src/integration/exchange/dto/mexc.dto.ts +++ b/src/integration/exchange/dto/mexc.dto.ts @@ -126,3 +126,14 @@ export interface MexcMyTrade { isMaker: boolean; isBestMatch: boolean; } + +export interface MexcOrderResponse { + symbol: string; + orderId: string; + orderListId: number; + price: string; + origQty: string; + type: string; + side: string; + transactTime: number; +} diff --git a/src/integration/exchange/services/exchange.service.ts b/src/integration/exchange/services/exchange.service.ts index 23590ab4ee..01523d71f3 100644 --- a/src/integration/exchange/services/exchange.service.ts +++ b/src/integration/exchange/services/exchange.service.ts @@ -354,7 +354,7 @@ export abstract class ExchangeService extends PricingProvider implements OnModul return this.createOrder(pair, direction, amount, price).then((o) => o.id); } - private async createOrder(pair: string, direction: OrderSide, amount: number, price: number): Promise { + protected async createOrder(pair: string, direction: OrderSide, amount: number, price: number): Promise { return this.callApi((e) => e.createOrder(pair, 'limit', direction, amount, price)); } diff --git a/src/integration/exchange/services/mexc.service.ts b/src/integration/exchange/services/mexc.service.ts index cd8762b83c..d551b7fe38 100644 --- a/src/integration/exchange/services/mexc.service.ts +++ b/src/integration/exchange/services/mexc.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { Method } from 'axios'; -import { Market, mexc, OrderBook, Trade, Transaction } from 'ccxt'; +import { Market, mexc, Order, OrderBook, Trade, Transaction } from 'ccxt'; import { Config, GetConfig } from 'src/config/config'; import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum'; import { DfxLogger } from 'src/shared/services/dfx-logger'; @@ -12,12 +12,13 @@ import { MexcExchangeInfo, MexcMyTrade, MexcOrderBook, + MexcOrderResponse, MexcSymbol, MexcTrade, Withdrawal, WithdrawalStatus, } from '../dto/mexc.dto'; -import { ExchangeService } from './exchange.service'; +import { ExchangeService, OrderSide } from './exchange.service'; @Injectable() export class MexcService extends ExchangeService { @@ -288,4 +289,18 @@ export class MexcService extends ExchangeService { fees: t.commission ? [{ cost: parseFloat(t.commission), currency: t.commissionAsset }] : [], })); } + + protected async createOrder(pair: string, direction: OrderSide, amount: number, price: number): Promise { + const symbol = pair.replace('/', ''); + + const response = await this.request('POST', 'order', { + symbol, + side: direction.toUpperCase(), + type: 'LIMIT', + quantity: amount.toString(), + price: price.toString(), + }); + + return { id: response.orderId } as Order; + } } diff --git a/src/subdomains/core/liquidity-management/adapters/actions/base/ccxt-exchange.adapter.ts b/src/subdomains/core/liquidity-management/adapters/actions/base/ccxt-exchange.adapter.ts index a79767f718..5c07b697a2 100644 --- a/src/subdomains/core/liquidity-management/adapters/actions/base/ccxt-exchange.adapter.ts +++ b/src/subdomains/core/liquidity-management/adapters/actions/base/ccxt-exchange.adapter.ts @@ -149,6 +149,12 @@ export abstract class CcxtExchangeAdapter extends LiquidityActionAdapter { const availableBalance = await this.getAvailableTradeBalance(tradeAsset, targetAssetEntity.name); let effectiveMax = Math.min(maxSellAmount, availableBalance); + if (availableBalance < minSellAmount) { + throw new OrderNotProcessableException( + `${this.exchangeService.name}: not enough balance for ${tradeAsset} (balance: ${availableBalance}, min. requested: ${minSellAmount}, max. requested: ${maxSellAmount})`, + ); + } + if (liquidityLimited) { const { amount: liquidity, price: liquidityPrice } = await this.getBestPriceLiquidity( tradeAsset, @@ -157,12 +163,6 @@ export abstract class CcxtExchangeAdapter extends LiquidityActionAdapter { effectiveMax = Math.min(effectiveMax, Util.floor(liquidity * liquidityPrice, 6)); } - if (effectiveMax < minSellAmount) { - throw new OrderNotProcessableException( - `${this.exchangeService.name}: not enough balance/liquidity for ${tradeAsset} (balance: ${effectiveMax}, min. requested: ${minSellAmount}, max. requested: ${maxSellAmount})`, - ); - } - const amount = effectiveMax; order.inputAmount = amount; @@ -197,17 +197,17 @@ export abstract class CcxtExchangeAdapter extends LiquidityActionAdapter { const availableBalance = await this.getAvailableTradeBalance(asset, tradeAsset); let effectiveMax = Math.min(order.maxAmount, availableBalance); + if (availableBalance < order.minAmount) { + throw new OrderNotProcessableException( + `${this.exchangeService.name}: not enough balance for ${asset} (balance: ${availableBalance}, min. requested: ${order.minAmount}, max. requested: ${order.maxAmount})`, + ); + } + if (liquidityLimited) { const { amount: liquidity } = await this.getBestPriceLiquidity(asset, tradeAsset); effectiveMax = Math.min(effectiveMax, liquidity); } - if (effectiveMax < order.minAmount) { - throw new OrderNotProcessableException( - `${this.exchangeService.name}: not enough balance/liquidity for ${asset} (balance: ${effectiveMax}, min. requested: ${order.minAmount}, max. requested: ${order.maxAmount})`, - ); - } - const amount = effectiveMax; order.inputAmount = amount;