feat(trading): 实现10%交易手续费进入积分股池
- 在成交时从卖方收益中扣除10%手续费 - 手续费流入积分股池(greenPoints/200万账户) - 添加详细分类账记录,包含买卖双方账户和来源标注 - Trade表新增fee字段记录每笔交易的手续费 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
192e2551bf
commit
c05bcc9a76
|
|
@ -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%进入积分股池)';
|
||||||
|
|
@ -200,7 +200,8 @@ model Trade {
|
||||||
quantity Decimal @db.Decimal(30, 8) // 实际成交量
|
quantity Decimal @db.Decimal(30, 8) // 实际成交量
|
||||||
burnQuantity Decimal @default(0) @map("burn_quantity") @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)
|
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
|
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
|
sellerSource String @default("USER") @map("seller_source") // USER, MARKET_MAKER, DEX_BOT, SYSTEM
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { OrderRepository } from '../../infrastructure/persistence/repositories/order.repository';
|
import { OrderRepository } from '../../infrastructure/persistence/repositories/order.repository';
|
||||||
import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository';
|
import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository';
|
||||||
import { CirculationPoolRepository } from '../../infrastructure/persistence/repositories/circulation-pool.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 { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
|
||||||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
||||||
import { RedisService } from '../../infrastructure/redis/redis.service';
|
import { RedisService } from '../../infrastructure/redis/redis.service';
|
||||||
|
|
@ -19,6 +20,9 @@ import {
|
||||||
TradeExecutedPayload,
|
TradeExecutedPayload,
|
||||||
} from '../../domain/events/trading.events';
|
} from '../../domain/events/trading.events';
|
||||||
|
|
||||||
|
// 交易手续费率:10%
|
||||||
|
const TRADE_FEE_RATE = 0.1;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrderService {
|
export class OrderService {
|
||||||
private readonly logger = new Logger(OrderService.name);
|
private readonly logger = new Logger(OrderService.name);
|
||||||
|
|
@ -28,6 +32,7 @@ export class OrderService {
|
||||||
private readonly orderRepository: OrderRepository,
|
private readonly orderRepository: OrderRepository,
|
||||||
private readonly accountRepository: TradingAccountRepository,
|
private readonly accountRepository: TradingAccountRepository,
|
||||||
private readonly circulationPoolRepository: CirculationPoolRepository,
|
private readonly circulationPoolRepository: CirculationPoolRepository,
|
||||||
|
private readonly sharePoolRepository: SharePoolRepository,
|
||||||
private readonly outboxRepository: OutboxRepository,
|
private readonly outboxRepository: OutboxRepository,
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly redis: RedisService,
|
private readonly redis: RedisService,
|
||||||
|
|
@ -184,10 +189,14 @@ export class OrderService {
|
||||||
// 计算交易额
|
// 计算交易额
|
||||||
// 买方支付:原始数量 × 价格(买方冻结的是这个金额)
|
// 买方支付:原始数量 × 价格(买方冻结的是这个金额)
|
||||||
const buyerPayAmount = new Money(tradeQuantity.value.times(match.trade.price.value));
|
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({
|
await this.prisma.trade.create({
|
||||||
data: {
|
data: {
|
||||||
tradeNo: match.trade.tradeNo,
|
tradeNo: match.trade.tradeNo,
|
||||||
|
|
@ -200,6 +209,7 @@ export class OrderService {
|
||||||
burnQuantity: burnQuantity.value,
|
burnQuantity: burnQuantity.value,
|
||||||
effectiveQty: effectiveQuantity.value,
|
effectiveQty: effectiveQuantity.value,
|
||||||
amount: sellerReceiveAmount.value,
|
amount: sellerReceiveAmount.value,
|
||||||
|
fee: tradeFee.value,
|
||||||
buyerSource: match.buyOrder.source,
|
buyerSource: match.buyOrder.source,
|
||||||
sellerSource: match.sellOrder.source,
|
sellerSource: match.sellOrder.source,
|
||||||
},
|
},
|
||||||
|
|
@ -217,6 +227,23 @@ export class OrderService {
|
||||||
this.logger.warn(`Failed to add shares to circulation pool: ${error}`);
|
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.save(match.buyOrder);
|
||||||
await this.orderRepository.saveWithBurnInfo(match.sellOrder, burnQuantity, effectiveQuantity);
|
await this.orderRepository.saveWithBurnInfo(match.sellOrder, burnQuantity, effectiveQuantity);
|
||||||
|
|
@ -228,7 +255,7 @@ export class OrderService {
|
||||||
await this.accountRepository.save(buyerAccount);
|
await this.accountRepository.save(buyerAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新卖方账户(收到含销毁倍数的有效交易额)
|
// 更新卖方账户(收到扣除手续费后的金额)
|
||||||
const sellerAccount = await this.accountRepository.findByAccountSequence(match.sellOrder.accountSequence);
|
const sellerAccount = await this.accountRepository.findByAccountSequence(match.sellOrder.accountSequence);
|
||||||
if (sellerAccount) {
|
if (sellerAccount) {
|
||||||
sellerAccount.executeSell(tradeQuantity, sellerReceiveAmount, match.trade.tradeNo);
|
sellerAccount.executeSell(tradeQuantity, sellerReceiveAmount, match.trade.tradeNo);
|
||||||
|
|
@ -238,7 +265,7 @@ export class OrderService {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Trade executed: ${match.trade.tradeNo}, price=${match.trade.price.toFixed(8)}, ` +
|
`Trade executed: ${match.trade.tradeNo}, price=${match.trade.price.toFixed(8)}, ` +
|
||||||
`qty=${tradeQuantity.toFixed(8)}, burn=${burnQuantity.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)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 发布成交事件
|
// 发布成交事件
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export interface SharePoolEntity {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SharePoolTransactionType = 'INJECT' | 'TRADE_IN' | 'TRADE_OUT';
|
export type SharePoolTransactionType = 'INJECT' | 'TRADE_IN' | 'TRADE_OUT' | 'FEE_IN';
|
||||||
|
|
||||||
export interface SharePoolTransactionEntity {
|
export interface SharePoolTransactionEntity {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -70,6 +70,34 @@ export class SharePoolRepository {
|
||||||
await this.updateBalance('TRADE_IN', amount, true, tradeId, `交易买入流入 ${amount.toFixed(8)}`);
|
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}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 交易流出(卖出时绿积分从股池流出)
|
* 交易流出(卖出时绿积分从股池流出)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue