176 lines
6.2 KiB
TypeScript
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();
|
|
}
|
|
}
|