286 lines
9.3 KiB
TypeScript
286 lines
9.3 KiB
TypeScript
import { Controller, Get, Post, Body, Query, HttpCode, HttpStatus } from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
|
|
import { IsBoolean } from 'class-validator';
|
|
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
|
import { TradingConfigRepository } from '../../infrastructure/persistence/repositories/trading-config.repository';
|
|
import { OrderRepository } from '../../infrastructure/persistence/repositories/order.repository';
|
|
import { OrderType, OrderStatus, OrderSource } from '../../domain/aggregates/order.aggregate';
|
|
import { Public } from '../../shared/guards/jwt-auth.guard';
|
|
|
|
class SetBuyEnabledDto {
|
|
@IsBoolean()
|
|
enabled: boolean;
|
|
}
|
|
|
|
class SetDepthEnabledDto {
|
|
@IsBoolean()
|
|
enabled: boolean;
|
|
}
|
|
|
|
@ApiTags('Admin')
|
|
@Controller('admin')
|
|
export class AdminController {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly tradingConfigRepository: TradingConfigRepository,
|
|
private readonly orderRepository: OrderRepository,
|
|
) {}
|
|
|
|
@Get('accounts/sync')
|
|
@Public()
|
|
@ApiOperation({ summary: '获取所有交易账户用于同步' })
|
|
async getAllAccountsForSync() {
|
|
const accounts = await this.prisma.tradingAccount.findMany({
|
|
select: {
|
|
accountSequence: true,
|
|
shareBalance: true,
|
|
cashBalance: true,
|
|
frozenShares: true,
|
|
frozenCash: true,
|
|
totalBought: true,
|
|
totalSold: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
},
|
|
});
|
|
|
|
return {
|
|
accounts: accounts.map((acc) => ({
|
|
accountSequence: acc.accountSequence,
|
|
shareBalance: acc.shareBalance.toString(),
|
|
cashBalance: acc.cashBalance.toString(),
|
|
frozenShares: acc.frozenShares.toString(),
|
|
frozenCash: acc.frozenCash.toString(),
|
|
totalBought: acc.totalBought.toString(),
|
|
totalSold: acc.totalSold.toString(),
|
|
createdAt: acc.createdAt,
|
|
updatedAt: acc.updatedAt,
|
|
})),
|
|
total: accounts.length,
|
|
};
|
|
}
|
|
|
|
@Get('trading/status')
|
|
@Public()
|
|
@ApiOperation({ summary: '获取交易系统状态' })
|
|
@ApiResponse({ status: 200, description: '返回交易系统配置状态' })
|
|
async getTradingStatus() {
|
|
const config = await this.tradingConfigRepository.getConfig();
|
|
if (!config) {
|
|
return {
|
|
initialized: false,
|
|
isActive: false,
|
|
buyEnabled: false,
|
|
activatedAt: null,
|
|
message: '交易系统未初始化',
|
|
};
|
|
}
|
|
|
|
return {
|
|
initialized: true,
|
|
isActive: config.isActive,
|
|
buyEnabled: config.buyEnabled,
|
|
activatedAt: config.activatedAt,
|
|
totalShares: config.totalShares.toFixed(8),
|
|
burnTarget: config.burnTarget.toFixed(8),
|
|
burnPeriodMinutes: config.burnPeriodMinutes,
|
|
minuteBurnRate: config.minuteBurnRate.toFixed(18),
|
|
message: config.isActive ? '交易系统已激活' : '交易系统未激活',
|
|
};
|
|
}
|
|
|
|
@Get('trading/buy-enabled')
|
|
@Public()
|
|
@ApiOperation({ summary: '获取买入功能开关状态' })
|
|
@ApiResponse({ status: 200, description: '返回买入功能是否启用' })
|
|
async getBuyEnabled() {
|
|
const config = await this.tradingConfigRepository.getConfig();
|
|
return {
|
|
enabled: config?.buyEnabled ?? false,
|
|
};
|
|
}
|
|
|
|
@Post('trading/buy-enabled')
|
|
@Public() // TODO: 生产环境应添加管理员权限验证
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: '设置买入功能开关' })
|
|
@ApiResponse({ status: 200, description: '买入功能开关设置成功' })
|
|
async setBuyEnabled(@Body() dto: SetBuyEnabledDto) {
|
|
await this.tradingConfigRepository.setBuyEnabled(dto.enabled);
|
|
return {
|
|
success: true,
|
|
enabled: dto.enabled,
|
|
message: dto.enabled ? '买入功能已开启' : '买入功能已关闭',
|
|
};
|
|
}
|
|
|
|
@Post('trading/activate')
|
|
@Public() // TODO: 生产环境应添加管理员权限验证
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: '激活交易/销毁系统' })
|
|
@ApiResponse({ status: 200, description: '交易系统已激活' })
|
|
@ApiResponse({ status: 400, description: '交易系统未初始化' })
|
|
async activateTrading() {
|
|
const config = await this.tradingConfigRepository.getConfig();
|
|
if (!config) {
|
|
return {
|
|
success: false,
|
|
message: '交易系统未初始化,请先启动服务',
|
|
};
|
|
}
|
|
|
|
if (config.isActive) {
|
|
return {
|
|
success: true,
|
|
message: '交易系统已处于激活状态',
|
|
activatedAt: config.activatedAt,
|
|
};
|
|
}
|
|
|
|
await this.tradingConfigRepository.activate();
|
|
const updatedConfig = await this.tradingConfigRepository.getConfig();
|
|
|
|
return {
|
|
success: true,
|
|
message: '交易系统已激活,每分钟销毁已开始运行',
|
|
activatedAt: updatedConfig?.activatedAt,
|
|
};
|
|
}
|
|
|
|
@Post('trading/deactivate')
|
|
@Public() // TODO: 生产环境应添加管理员权限验证
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: '关闭交易/销毁系统' })
|
|
@ApiResponse({ status: 200, description: '交易系统已关闭' })
|
|
async deactivateTrading() {
|
|
const config = await this.tradingConfigRepository.getConfig();
|
|
if (!config) {
|
|
return {
|
|
success: false,
|
|
message: '交易系统未初始化',
|
|
};
|
|
}
|
|
|
|
if (!config.isActive) {
|
|
return {
|
|
success: true,
|
|
message: '交易系统已处于关闭状态',
|
|
};
|
|
}
|
|
|
|
await this.tradingConfigRepository.deactivate();
|
|
|
|
return {
|
|
success: true,
|
|
message: '交易系统已关闭,每分钟销毁已暂停',
|
|
};
|
|
}
|
|
|
|
@Get('trading/depth-enabled')
|
|
@Public()
|
|
@ApiOperation({ summary: '获取深度显示开关状态' })
|
|
@ApiResponse({ status: 200, description: '返回深度显示是否启用' })
|
|
async getDepthEnabled() {
|
|
const config = await this.tradingConfigRepository.getConfig();
|
|
return {
|
|
enabled: config?.depthEnabled ?? false,
|
|
};
|
|
}
|
|
|
|
@Get('orders')
|
|
@Public()
|
|
@ApiOperation({ summary: '获取全局订单列表' })
|
|
@ApiQuery({ name: 'page', required: false, type: Number })
|
|
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
|
@ApiQuery({ name: 'type', required: false, enum: ['BUY', 'SELL'] })
|
|
@ApiQuery({ name: 'status', required: false, enum: ['PENDING', 'PARTIAL', 'FILLED', 'CANCELLED'] })
|
|
@ApiQuery({ name: 'source', required: false, enum: ['USER', 'MARKET_MAKER', 'DEX_BOT', 'SYSTEM'] })
|
|
@ApiQuery({ name: 'search', required: false, type: String })
|
|
@ApiQuery({ name: 'startDate', required: false, type: String })
|
|
@ApiQuery({ name: 'endDate', required: false, type: String })
|
|
async getOrders(
|
|
@Query('page') page?: number,
|
|
@Query('pageSize') pageSize?: number,
|
|
@Query('type') type?: string,
|
|
@Query('status') status?: string,
|
|
@Query('source') source?: string,
|
|
@Query('search') search?: string,
|
|
@Query('startDate') startDate?: string,
|
|
@Query('endDate') endDate?: string,
|
|
) {
|
|
const p = page ? Number(page) : 1;
|
|
const ps = pageSize ? Number(pageSize) : 20;
|
|
|
|
const result = await this.orderRepository.findAllOrders({
|
|
type: type as OrderType | undefined,
|
|
status: status as OrderStatus | undefined,
|
|
source: source as OrderSource | undefined,
|
|
search: search || undefined,
|
|
startDate: startDate ? new Date(startDate) : undefined,
|
|
endDate: endDate ? new Date(endDate) : undefined,
|
|
page: p,
|
|
pageSize: ps,
|
|
});
|
|
|
|
return {
|
|
data: result.data,
|
|
total: result.total,
|
|
page: p,
|
|
pageSize: ps,
|
|
totalPages: Math.ceil(result.total / ps),
|
|
};
|
|
}
|
|
|
|
@Get('trades')
|
|
@Public()
|
|
@ApiOperation({ summary: '获取全局成交记录' })
|
|
@ApiQuery({ name: 'page', required: false, type: Number })
|
|
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
|
@ApiQuery({ name: 'source', required: false, enum: ['USER', 'MARKET_MAKER', 'DEX_BOT', 'SYSTEM'] })
|
|
@ApiQuery({ name: 'search', required: false, type: String })
|
|
@ApiQuery({ name: 'startDate', required: false, type: String })
|
|
@ApiQuery({ name: 'endDate', required: false, type: String })
|
|
async getTrades(
|
|
@Query('page') page?: number,
|
|
@Query('pageSize') pageSize?: number,
|
|
@Query('source') source?: string,
|
|
@Query('search') search?: string,
|
|
@Query('startDate') startDate?: string,
|
|
@Query('endDate') endDate?: string,
|
|
) {
|
|
const p = page ? Number(page) : 1;
|
|
const ps = pageSize ? Number(pageSize) : 20;
|
|
|
|
const result = await this.orderRepository.findAllTrades({
|
|
source: source || undefined,
|
|
search: search || undefined,
|
|
startDate: startDate ? new Date(startDate) : undefined,
|
|
endDate: endDate ? new Date(endDate) : undefined,
|
|
page: p,
|
|
pageSize: ps,
|
|
});
|
|
|
|
return {
|
|
data: result.data,
|
|
total: result.total,
|
|
page: p,
|
|
pageSize: ps,
|
|
totalPages: Math.ceil(result.total / ps),
|
|
};
|
|
}
|
|
|
|
@Post('trading/depth-enabled')
|
|
@Public() // TODO: 生产环境应添加管理员权限验证
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: '设置深度显示开关' })
|
|
@ApiResponse({ status: 200, description: '深度显示开关设置成功' })
|
|
async setDepthEnabled(@Body() dto: SetDepthEnabledDto) {
|
|
await this.tradingConfigRepository.setDepthEnabled(dto.enabled);
|
|
return {
|
|
success: true,
|
|
enabled: dto.enabled,
|
|
message: dto.enabled ? '深度显示已开启' : '深度显示已关闭',
|
|
};
|
|
}
|
|
}
|