diff --git a/backend/services/trading-service/prisma/migrations/0007_add_trade_fee/migration.sql b/backend/services/trading-service/prisma/migrations/0007_add_trade_fee/migration.sql new file mode 100644 index 00000000..9a5db853 --- /dev/null +++ b/backend/services/trading-service/prisma/migrations/0007_add_trade_fee/migration.sql @@ -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%进入积分股池)'; diff --git a/backend/services/trading-service/prisma/schema.prisma b/backend/services/trading-service/prisma/schema.prisma index f020ca0c..a7f843f2 100644 --- a/backend/services/trading-service/prisma/schema.prisma +++ b/backend/services/trading-service/prisma/schema.prisma @@ -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 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 34eb4395..6fadb5c6 100644 --- a/backend/services/trading-service/src/application/services/order.service.ts +++ b/backend/services/trading-service/src/application/services/order.service.ts @@ -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)}`, ); // 发布成交事件 diff --git a/backend/services/trading-service/src/infrastructure/persistence/repositories/share-pool.repository.ts b/backend/services/trading-service/src/infrastructure/persistence/repositories/share-pool.repository.ts index 1671d823..73d465d8 100644 --- a/backend/services/trading-service/src/infrastructure/persistence/repositories/share-pool.repository.ts +++ b/backend/services/trading-service/src/infrastructure/persistence/repositories/share-pool.repository.ts @@ -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 { + 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}`, + ); + } + /** * 交易流出(卖出时绿积分从股池流出) */