fix(trading): 修复积分股重复记账,做市商吃单积分股只进流通池
问题:
之前所有卖出成交时,积分股同时记入了买方账户和流通池,
导致同一笔积分股被重复计入两处(双重记账)。
修复:
- 做市商吃单时:积分股只进入流通池,不增加做市商 shareBalance
(目前不允许做市商挂卖单,做市商不需要持有积分股)
- 普通用户买入时:积分股直接进入买方账户,不再重复计入流通池
- 将 mmConfig 查询提到循环外复用,去掉循环内冗余查询
积分股流向(修复后):
做市商吃单: 卖方 -X → 流通池 +X(做市商 shareBalance 不变)
用户间交易: 卖方 -X → 买方 +X(流通池不变)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4817d92507
commit
000e8f7ef1
|
|
@ -157,10 +157,16 @@ export class OrderService {
|
||||||
|
|
||||||
const matches = this.matchingEngine.findMatchingOrders(incomingOrder, orderBook);
|
const matches = this.matchingEngine.findMatchingOrders(incomingOrder, orderBook);
|
||||||
|
|
||||||
|
// 查询做市商配置(用于判断买方是否为做市商 + 手续费存入)
|
||||||
|
const mmConfig = await this.prisma.marketMakerConfig.findUnique({
|
||||||
|
where: { name: 'MAIN_MARKET_MAKER' },
|
||||||
|
});
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const tradeQuantity = match.trade.quantity;
|
const tradeQuantity = match.trade.quantity;
|
||||||
let burnQuantity = Money.zero();
|
let burnQuantity = Money.zero();
|
||||||
let effectiveQuantity = tradeQuantity;
|
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({
|
const buyerAccountRecord = await tx.tradingAccount.findUnique({
|
||||||
where: { accountSequence: match.buyOrder.accountSequence },
|
where: { accountSequence: match.buyOrder.accountSequence },
|
||||||
});
|
});
|
||||||
if (buyerAccountRecord) {
|
if (buyerAccountRecord) {
|
||||||
const newFrozenCash = Number(buyerAccountRecord.frozenCash) - buyerPayAmount.value.toNumber();
|
const newFrozenCash = Number(buyerAccountRecord.frozenCash) - buyerPayAmount.value.toNumber();
|
||||||
const newCashBalance = Number(buyerAccountRecord.cashBalance) - 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();
|
const newTotalBought = Number(buyerAccountRecord.totalBought) + tradeQuantity.value.toNumber();
|
||||||
|
|
||||||
await tx.tradingAccount.update({
|
await tx.tradingAccount.update({
|
||||||
|
|
@ -279,7 +289,9 @@ export class OrderService {
|
||||||
balanceAfter: newShareBalance,
|
balanceAfter: newShareBalance,
|
||||||
referenceId: match.trade.tradeNo,
|
referenceId: match.trade.tradeNo,
|
||||||
referenceType: 'TRADE',
|
referenceType: 'TRADE',
|
||||||
description: '买入成交',
|
description: isBuyerMarketMaker
|
||||||
|
? '做市商吃单(积分股→流通池)'
|
||||||
|
: '买入成交',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -324,23 +336,23 @@ export class OrderService {
|
||||||
});
|
});
|
||||||
// ========== 事务结束 ==========
|
// ========== 事务结束 ==========
|
||||||
|
|
||||||
// 卖出的积分股进入流通池(非关键操作,可以在事务外执行)
|
// 做市商吃单时:卖出的积分股进入流通池(做市商不持有积分股)
|
||||||
try {
|
// 普通用户买入时:买方已在事务内获得积分股,不重复计入流通池
|
||||||
await this.circulationPoolRepository.addSharesFromSell(
|
if (isBuyerMarketMaker) {
|
||||||
tradeQuantity,
|
try {
|
||||||
match.sellOrder.accountSequence,
|
await this.circulationPoolRepository.addSharesFromSell(
|
||||||
match.sellOrder.id!,
|
tradeQuantity,
|
||||||
`卖出成交, 交易号${match.trade.tradeNo}`,
|
match.sellOrder.accountSequence,
|
||||||
);
|
match.sellOrder.id!,
|
||||||
} catch (error) {
|
`做市商吃单→流通池, 交易号${match.trade.tradeNo}`,
|
||||||
this.logger.warn(`Failed to add shares to circulation pool: ${error}`);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Failed to add shares to circulation pool: ${error}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10%手续费存入做市商账户(做市商的 cashBalance 即积分股池的绿积分)
|
// 10%手续费存入做市商账户(做市商的 cashBalance 即积分值池)
|
||||||
try {
|
try {
|
||||||
const mmConfig = await this.prisma.marketMakerConfig.findUnique({
|
|
||||||
where: { name: 'MAIN_MARKET_MAKER' },
|
|
||||||
});
|
|
||||||
if (mmConfig) {
|
if (mmConfig) {
|
||||||
await this.prisma.tradingAccount.update({
|
await this.prisma.tradingAccount.update({
|
||||||
where: { accountSequence: mmConfig.accountSequence },
|
where: { accountSequence: mmConfig.accountSequence },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue