rwadurian/backend/services/trading-service/src/application/services/order.service.ts

176 lines
6.2 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { OrderRepository } from '../../infrastructure/persistence/repositories/order.repository';
import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import { RedisService } from '../../infrastructure/redis/redis.service';
import { OrderAggregate, OrderType, OrderStatus } from '../../domain/aggregates/order.aggregate';
import { TradingAccountAggregate } from '../../domain/aggregates/trading-account.aggregate';
import { MatchingEngineService } from '../../domain/services/matching-engine.service';
import { Money } from '../../domain/value-objects/money.vo';
@Injectable()
export class OrderService {
private readonly logger = new Logger(OrderService.name);
private readonly matchingEngine = new MatchingEngineService();
constructor(
private readonly orderRepository: OrderRepository,
private readonly accountRepository: TradingAccountRepository,
private readonly prisma: PrismaService,
private readonly redis: RedisService,
) {}
async createOrder(
accountSequence: string,
type: OrderType,
price: string,
quantity: string,
): Promise<{ orderId: string; orderNo: string; status: OrderStatus; filledQuantity: string }> {
const lockValue = await this.redis.acquireLock(`order:create:${accountSequence}`, 10);
if (!lockValue) {
throw new Error('System busy, please try again');
}
try {
// 验证账户
let account = await this.accountRepository.findByAccountSequence(accountSequence);
if (!account) {
account = TradingAccountAggregate.create(accountSequence);
}
const priceAmount = new Money(price);
const quantityAmount = new Money(quantity);
const totalCost = quantityAmount.multiply(priceAmount.value);
// 检查余额并冻结
if (type === OrderType.BUY) {
if (account.availableCash.isLessThan(totalCost)) {
throw new Error('Insufficient cash balance');
}
} else {
if (account.availableShares.isLessThan(quantityAmount)) {
throw new Error('Insufficient share balance');
}
}
// 生成订单号
const orderNo = this.generateOrderNo();
// 创建订单
const order = OrderAggregate.create(orderNo, accountSequence, type, priceAmount, quantityAmount);
// 冻结资产
if (type === OrderType.BUY) {
account.freezeCash(totalCost, orderNo);
} else {
account.freezeShares(quantityAmount, orderNo);
}
// 保存订单和账户
const orderId = await this.orderRepository.save(order);
await this.accountRepository.save(account);
// 尝试撮合
await this.tryMatch(order);
// 获取最新订单状态
const updatedOrder = await this.orderRepository.findByOrderNo(orderNo);
return {
orderId,
orderNo,
status: updatedOrder?.status || order.status,
filledQuantity: updatedOrder?.filledQuantity.toString() || '0',
};
} finally {
await this.redis.releaseLock(`order:create:${accountSequence}`, lockValue);
}
}
async cancelOrder(accountSequence: string, orderNo: string): Promise<void> {
const order = await this.orderRepository.findByOrderNo(orderNo);
if (!order) {
throw new Error('Order not found');
}
if (order.accountSequence !== accountSequence) {
throw new Error('Unauthorized');
}
const account = await this.accountRepository.findByAccountSequence(accountSequence);
if (!account) {
throw new Error('Account not found');
}
// 取消订单
order.cancel();
// 解冻资产
if (order.type === OrderType.BUY) {
account.unfreezeCash(order.lockedAmount, orderNo);
} else {
account.unfreezeShares(order.remainingQuantity, orderNo);
}
await this.orderRepository.save(order);
await this.accountRepository.save(account);
}
private async tryMatch(incomingOrder: OrderAggregate): Promise<void> {
const lockValue = await this.redis.acquireLock('order:matching', 30);
if (!lockValue) return;
try {
const oppositeType = incomingOrder.type === OrderType.BUY ? OrderType.SELL : OrderType.BUY;
const orderBook = await this.orderRepository.findActiveOrders(oppositeType);
const matches = this.matchingEngine.findMatchingOrders(incomingOrder, orderBook);
for (const match of matches) {
// 保存成交记录
await this.prisma.trade.create({
data: {
tradeNo: match.trade.tradeNo,
buyOrderId: match.buyOrder.id!,
sellOrderId: match.sellOrder.id!,
buyerSequence: match.buyOrder.accountSequence,
sellerSequence: match.sellOrder.accountSequence,
price: match.trade.price.value,
quantity: match.trade.quantity.value,
amount: match.trade.amount.value,
},
});
// 更新订单
await this.orderRepository.save(match.buyOrder);
await this.orderRepository.save(match.sellOrder);
// 更新买方账户
const buyerAccount = await this.accountRepository.findByAccountSequence(match.buyOrder.accountSequence);
if (buyerAccount) {
buyerAccount.executeBuy(match.trade.quantity, match.trade.amount, match.trade.tradeNo);
await this.accountRepository.save(buyerAccount);
}
// 更新卖方账户
const sellerAccount = await this.accountRepository.findByAccountSequence(match.sellOrder.accountSequence);
if (sellerAccount) {
sellerAccount.executeSell(match.trade.quantity, match.trade.amount, match.trade.tradeNo);
await this.accountRepository.save(sellerAccount);
}
this.logger.log(
`Trade executed: ${match.trade.tradeNo}, price=${match.trade.price}, qty=${match.trade.quantity}`,
);
}
} finally {
await this.redis.releaseLock('order:matching', lockValue);
}
}
private generateOrderNo(): string {
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substring(2, 8);
return `O${timestamp}${random}`.toUpperCase();
}
}