From 000e8f7ef10529359afd9f8f5873181b39d7faef Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 3 Feb 2026 05:47:16 -0800 Subject: [PATCH] =?UTF-8?q?fix(trading):=20=E4=BF=AE=E5=A4=8D=E7=A7=AF?= =?UTF-8?q?=E5=88=86=E8=82=A1=E9=87=8D=E5=A4=8D=E8=AE=B0=E8=B4=A6=EF=BC=8C?= =?UTF-8?q?=E5=81=9A=E5=B8=82=E5=95=86=E5=90=83=E5=8D=95=E7=A7=AF=E5=88=86?= =?UTF-8?q?=E8=82=A1=E5=8F=AA=E8=BF=9B=E6=B5=81=E9=80=9A=E6=B1=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: 之前所有卖出成交时,积分股同时记入了买方账户和流通池, 导致同一笔积分股被重复计入两处(双重记账)。 修复: - 做市商吃单时:积分股只进入流通池,不增加做市商 shareBalance (目前不允许做市商挂卖单,做市商不需要持有积分股) - 普通用户买入时:积分股直接进入买方账户,不再重复计入流通池 - 将 mmConfig 查询提到循环外复用,去掉循环内冗余查询 积分股流向(修复后): 做市商吃单: 卖方 -X → 流通池 +X(做市商 shareBalance 不变) 用户间交易: 卖方 -X → 买方 +X(流通池不变) Co-Authored-By: Claude Opus 4.5 --- .../src/application/services/order.service.ts | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/backend/services/trading-service/src/application/services/order.service.ts b/backend/services/trading-service/src/application/services/order.service.ts index a3ee1a16..debffd1b 100644 --- a/backend/services/trading-service/src/application/services/order.service.ts +++ b/backend/services/trading-service/src/application/services/order.service.ts @@ -157,10 +157,16 @@ export class OrderService { const matches = this.matchingEngine.findMatchingOrders(incomingOrder, orderBook); + // 查询做市商配置(用于判断买方是否为做市商 + 手续费存入) + const mmConfig = await this.prisma.marketMakerConfig.findUnique({ + where: { name: 'MAIN_MARKET_MAKER' }, + }); + for (const match of matches) { const tradeQuantity = match.trade.quantity; let burnQuantity = Money.zero(); let effectiveQuantity = tradeQuantity; + const isBuyerMarketMaker = mmConfig?.accountSequence === match.buyOrder.accountSequence; // 如果是卖出成交,执行销毁逻辑 // 卖出的销毁量 = 卖出积分股 × 倍数 @@ -248,14 +254,18 @@ export class OrderService { }, }); - // 4. 更新买方账户(扣除冻结现金,增加积分股) + // 4. 更新买方账户(扣除冻结现金) + // 做市商吃单时:积分股不进做市商账户,只进流通池(事务外处理) + // 普通用户买入时:积分股直接进入买方账户 const buyerAccountRecord = await tx.tradingAccount.findUnique({ where: { accountSequence: match.buyOrder.accountSequence }, }); if (buyerAccountRecord) { const newFrozenCash = Number(buyerAccountRecord.frozenCash) - buyerPayAmount.value.toNumber(); const newCashBalance = Number(buyerAccountRecord.cashBalance) - buyerPayAmount.value.toNumber(); - const newShareBalance = Number(buyerAccountRecord.shareBalance) + tradeQuantity.value.toNumber(); + const newShareBalance = isBuyerMarketMaker + ? Number(buyerAccountRecord.shareBalance) + : Number(buyerAccountRecord.shareBalance) + tradeQuantity.value.toNumber(); const newTotalBought = Number(buyerAccountRecord.totalBought) + tradeQuantity.value.toNumber(); await tx.tradingAccount.update({ @@ -279,7 +289,9 @@ export class OrderService { balanceAfter: newShareBalance, referenceId: match.trade.tradeNo, referenceType: 'TRADE', - description: '买入成交', + description: isBuyerMarketMaker + ? '做市商吃单(积分股→流通池)' + : '买入成交', }, }); } @@ -324,23 +336,23 @@ export class OrderService { }); // ========== 事务结束 ========== - // 卖出的积分股进入流通池(非关键操作,可以在事务外执行) - try { - await this.circulationPoolRepository.addSharesFromSell( - tradeQuantity, - match.sellOrder.accountSequence, - match.sellOrder.id!, - `卖出成交, 交易号${match.trade.tradeNo}`, - ); - } catch (error) { - this.logger.warn(`Failed to add shares to circulation pool: ${error}`); + // 做市商吃单时:卖出的积分股进入流通池(做市商不持有积分股) + // 普通用户买入时:买方已在事务内获得积分股,不重复计入流通池 + if (isBuyerMarketMaker) { + try { + await this.circulationPoolRepository.addSharesFromSell( + tradeQuantity, + match.sellOrder.accountSequence, + match.sellOrder.id!, + `做市商吃单→流通池, 交易号${match.trade.tradeNo}`, + ); + } catch (error) { + this.logger.warn(`Failed to add shares to circulation pool: ${error}`); + } } - // 10%手续费存入做市商账户(做市商的 cashBalance 即积分股池的绿积分) + // 10%手续费存入做市商账户(做市商的 cashBalance 即积分值池) try { - const mmConfig = await this.prisma.marketMakerConfig.findUnique({ - where: { name: 'MAIN_MARKET_MAKER' }, - }); if (mmConfig) { await this.prisma.tradingAccount.update({ where: { accountSequence: mmConfig.accountSequence },