feat(trading): 实现10%交易手续费进入积分股池

- 在成交时从卖方收益中扣除10%手续费
- 手续费流入积分股池(greenPoints/200万账户)
- 添加详细分类账记录,包含买卖双方账户和来源标注
- Trade表新增fee字段记录每笔交易的手续费

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-18 21:33:40 -08:00
parent 192e2551bf
commit c05bcc9a76
4 changed files with 73 additions and 7 deletions

View File

@ -0,0 +1,10 @@
-- ============================================================================
-- trading-service 添加交易手续费字段
-- 10%交易手续费进入积分股池(greenPoints)
-- ============================================================================
-- 添加手续费字段到 trades 表
ALTER TABLE "trades" ADD COLUMN IF NOT EXISTS "fee" DECIMAL(30, 8) NOT NULL DEFAULT 0;
-- 添加注释
COMMENT ON COLUMN "trades"."fee" IS '交易手续费10%进入积分股池)';

View File

@ -200,7 +200,8 @@ model Trade {
quantity Decimal @db.Decimal(30, 8) // 实际成交量
burnQuantity Decimal @default(0) @map("burn_quantity") @db.Decimal(30, 8) // 卖出销毁量
effectiveQty Decimal @default(0) @map("effective_qty") @db.Decimal(30, 8) // 有效量quantity + burnQuantity
amount Decimal @db.Decimal(30, 8) // effectiveQty * price卖出交易额
amount Decimal @db.Decimal(30, 8) // 卖方实际收到金额(扣除手续费后)
fee Decimal @default(0) @db.Decimal(30, 8) // 交易手续费10%进入积分股池)
// 交易来源标识
buyerSource String @default("USER") @map("buyer_source") // USER, MARKET_MAKER, DEX_BOT, SYSTEM
sellerSource String @default("USER") @map("seller_source") // USER, MARKET_MAKER, DEX_BOT, SYSTEM

View File

@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { OrderRepository } from '../../infrastructure/persistence/repositories/order.repository';
import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository';
import { CirculationPoolRepository } from '../../infrastructure/persistence/repositories/circulation-pool.repository';
import { SharePoolRepository } from '../../infrastructure/persistence/repositories/share-pool.repository';
import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import { RedisService } from '../../infrastructure/redis/redis.service';
@ -19,6 +20,9 @@ import {
TradeExecutedPayload,
} from '../../domain/events/trading.events';
// 交易手续费率10%
const TRADE_FEE_RATE = 0.1;
@Injectable()
export class OrderService {
private readonly logger = new Logger(OrderService.name);
@ -28,6 +32,7 @@ export class OrderService {
private readonly orderRepository: OrderRepository,
private readonly accountRepository: TradingAccountRepository,
private readonly circulationPoolRepository: CirculationPoolRepository,
private readonly sharePoolRepository: SharePoolRepository,
private readonly outboxRepository: OutboxRepository,
private readonly prisma: PrismaService,
private readonly redis: RedisService,
@ -184,10 +189,14 @@ export class OrderService {
// 计算交易额
// 买方支付:原始数量 × 价格(买方冻结的是这个金额)
const buyerPayAmount = new Money(tradeQuantity.value.times(match.trade.price.value));
// 卖方收到:有效数量 × 价格(含销毁倍数的收益)
const sellerReceiveAmount = new Money(effectiveQuantity.value.times(match.trade.price.value));
// 卖方总收益(含销毁倍数):有效数量 × 价格
const sellerGrossAmount = new Money(effectiveQuantity.value.times(match.trade.price.value));
// 交易手续费10%):从卖方收益中扣除
const tradeFee = new Money(sellerGrossAmount.value.times(TRADE_FEE_RATE));
// 卖方实际收到:总收益 - 手续费
const sellerReceiveAmount = new Money(sellerGrossAmount.value.minus(tradeFee.value));
// 保存成交记录(包含销毁信息和来源标识)
// 保存成交记录(包含销毁信息、手续费和来源标识)
await this.prisma.trade.create({
data: {
tradeNo: match.trade.tradeNo,
@ -200,6 +209,7 @@ export class OrderService {
burnQuantity: burnQuantity.value,
effectiveQty: effectiveQuantity.value,
amount: sellerReceiveAmount.value,
fee: tradeFee.value,
buyerSource: match.buyOrder.source,
sellerSource: match.sellOrder.source,
},
@ -217,6 +227,23 @@ export class OrderService {
this.logger.warn(`Failed to add shares to circulation pool: ${error}`);
}
// 10%手续费进入积分股池greenPoints
try {
await this.sharePoolRepository.feeIn(
tradeFee,
match.trade.tradeNo,
match.buyOrder.accountSequence,
match.sellOrder.accountSequence,
match.buyOrder.source,
match.sellOrder.source,
);
this.logger.debug(
`Trade fee added to share pool: ${tradeFee.toFixed(8)}, tradeNo=${match.trade.tradeNo}`,
);
} catch (error) {
this.logger.error(`Failed to add trade fee to share pool: ${error}`);
}
// 更新订单(包含销毁信息)
await this.orderRepository.save(match.buyOrder);
await this.orderRepository.saveWithBurnInfo(match.sellOrder, burnQuantity, effectiveQuantity);
@ -228,7 +255,7 @@ export class OrderService {
await this.accountRepository.save(buyerAccount);
}
// 更新卖方账户(收到含销毁倍数的有效交易额)
// 更新卖方账户(收到扣除手续费后的金额)
const sellerAccount = await this.accountRepository.findByAccountSequence(match.sellOrder.accountSequence);
if (sellerAccount) {
sellerAccount.executeSell(tradeQuantity, sellerReceiveAmount, match.trade.tradeNo);
@ -238,7 +265,7 @@ export class OrderService {
this.logger.log(
`Trade executed: ${match.trade.tradeNo}, price=${match.trade.price.toFixed(8)}, ` +
`qty=${tradeQuantity.toFixed(8)}, burn=${burnQuantity.toFixed(8)}, ` +
`buyerPay=${buyerPayAmount.toFixed(8)}, sellerReceive=${sellerReceiveAmount.toFixed(8)}`,
`buyerPay=${buyerPayAmount.toFixed(8)}, fee=${tradeFee.toFixed(8)}, sellerReceive=${sellerReceiveAmount.toFixed(8)}`,
);
// 发布成交事件

View File

@ -12,7 +12,7 @@ export interface SharePoolEntity {
updatedAt: Date;
}
export type SharePoolTransactionType = 'INJECT' | 'TRADE_IN' | 'TRADE_OUT';
export type SharePoolTransactionType = 'INJECT' | 'TRADE_IN' | 'TRADE_OUT' | 'FEE_IN';
export interface SharePoolTransactionEntity {
id: string;
@ -70,6 +70,34 @@ export class SharePoolRepository {
await this.updateBalance('TRADE_IN', amount, true, tradeId, `交易买入流入 ${amount.toFixed(8)}`);
}
/**
* 10%
* @param amount
* @param tradeNo
* @param buyerSequence
* @param sellerSequence
* @param buyerSource USER/MARKET_MAKER/DEX_BOT/SYSTEM
* @param sellerSource USER/MARKET_MAKER/DEX_BOT/SYSTEM
*/
async feeIn(
amount: Money,
tradeNo: string,
buyerSequence: string,
sellerSequence: string,
buyerSource: string,
sellerSource: string,
): Promise<void> {
const buyerLabel = buyerSource === 'MARKET_MAKER' ? '做市商' : buyerSource === 'USER' ? '用户' : buyerSource;
const sellerLabel = sellerSource === 'MARKET_MAKER' ? '做市商' : sellerSource === 'USER' ? '用户' : sellerSource;
await this.updateBalance(
'FEE_IN',
amount,
true,
tradeNo,
`交易手续费(10%) ${amount.toFixed(8)}, 买方: ${buyerSequence}(${buyerLabel}), 卖方: ${sellerSequence}(${sellerLabel}), 交易号: ${tradeNo}`,
);
}
/**
* 绿
*/