From 8c78f26e6d31f1075e903682bce9789579da2b75 Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 17 Jan 2026 21:41:39 -0800 Subject: [PATCH] =?UTF-8?q?feat(trading):=20=E5=AE=9E=E7=8E=B0=E5=81=9A?= =?UTF-8?q?=E5=B8=82=E5=95=86=E5=90=83=E5=8D=95/=E6=8C=82=E5=8D=95?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=BA=92=E6=96=A5=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 后端 - trading-service ### MarketMakerService - 新增 MarketMakerMode 类型:'idle' | 'taker' | 'maker' - 新增 getCurrentMode() 和 getRunningStatus() 方法获取当前运行状态 - start() (吃单模式): 启动前自动停止挂单模式 - startMaker() (挂单模式): 启动前自动停止吃单模式 - 两种模式互斥,同一时间只能运行一种 ### MarketMakerController - getConfig 接口返回 runningStatus 运行状态 - 新增 GET /status 接口获取做市商运行状态 ## 前端 - mining-admin-web ### 做市商管理页面 - 新增运行模式状态卡片,显示当前模式(空闲/吃单/挂单) - 吃单模式和挂单模式使用 runningStatus 判断状态 - 添加互斥提示:启动一个模式会自动停止另一个 - 挂单模式添加警告提示:卖单被吃会触发销毁导致价格上涨 ### API 更新 - 新增 RunningStatus 接口类型 - getConfig 返回类型增加 runningStatus - 新增 getRunningStatus API ## 设计说明 - 吃单模式(推荐):做市商只作为买方,不触发额外销毁 - 挂单模式(谨慎使用):做市商挂卖单会触发销毁机制 Co-Authored-By: Claude Opus 4.5 --- .../controllers/market-maker.controller.ts | 21 +++++ .../services/market-maker.service.ts | 53 +++++++++-- .../src/app/(dashboard)/market-maker/page.tsx | 87 +++++++++++++++++-- .../market-maker/api/market-maker.api.ts | 23 ++++- 4 files changed, 169 insertions(+), 15 deletions(-) diff --git a/backend/services/trading-service/src/api/controllers/market-maker.controller.ts b/backend/services/trading-service/src/api/controllers/market-maker.controller.ts index 6c3a11b2..54948e1d 100644 --- a/backend/services/trading-service/src/api/controllers/market-maker.controller.ts +++ b/backend/services/trading-service/src/api/controllers/market-maker.controller.ts @@ -97,11 +97,14 @@ export class MarketMakerController { @ApiResponse({ status: 200, description: '返回做市商配置' }) async getConfig(@Param('name') name: string) { const config = await this.marketMakerService.getConfig(name); + const runningStatus = this.marketMakerService.getRunningStatus(); + if (!config) { return { success: false, message: `做市商 ${name} 不存在`, config: null, + runningStatus, }; } @@ -124,6 +127,24 @@ export class MarketMakerController { discountRate: config.discountRate.toString(), isActive: config.isActive, }, + runningStatus, + }; + } + + @Get('status') + @Public() + @ApiOperation({ summary: '获取做市商运行状态' }) + @ApiResponse({ status: 200, description: '返回运行状态' }) + getRunningStatus() { + const status = this.marketMakerService.getRunningStatus(); + return { + success: true, + ...status, + modeDescription: { + idle: '空闲(未运行)', + taker: '吃单模式(自动买入用户卖单)', + maker: '挂单模式(双边深度做市)', + }[status.mode], }; } diff --git a/backend/services/trading-service/src/application/services/market-maker.service.ts b/backend/services/trading-service/src/application/services/market-maker.service.ts index 2b8a2fa6..2a64a905 100644 --- a/backend/services/trading-service/src/application/services/market-maker.service.ts +++ b/backend/services/trading-service/src/application/services/market-maker.service.ts @@ -52,6 +52,8 @@ export enum AssetType { SHARE = 'SHARE', } +export type MarketMakerMode = 'idle' | 'taker' | 'maker'; + @Injectable() export class MarketMakerService { private readonly logger = new Logger(MarketMakerService.name); @@ -103,6 +105,33 @@ export class MarketMakerService { }; } + /** + * 获取当前运行模式 + * idle: 空闲(两个模式都未运行) + * taker: 吃单模式运行中 + * maker: 挂单模式运行中 + */ + getCurrentMode(): MarketMakerMode { + if (this.isMakerRunning) return 'maker'; + if (this.isRunning) return 'taker'; + return 'idle'; + } + + /** + * 获取运行状态详情 + */ + getRunningStatus(): { + mode: MarketMakerMode; + takerRunning: boolean; + makerRunning: boolean; + } { + return { + mode: this.getCurrentMode(), + takerRunning: this.isRunning, + makerRunning: this.isMakerRunning, + }; + } + /** * 初始化做市商配置(如果不存在) */ @@ -333,7 +362,8 @@ export class MarketMakerService { } /** - * 启动做市商 + * 启动做市商(吃单模式) + * 注意:与挂单模式互斥,启动吃单模式会自动停止挂单模式 */ async start(name: string = 'MAIN_MARKET_MAKER'): Promise { const config = await this.getConfig(name); @@ -342,17 +372,23 @@ export class MarketMakerService { } if (this.isRunning) { - this.logger.warn('Market maker is already running'); + this.logger.warn('Market maker taker mode is already running'); return; } + // 互斥:如果挂单模式正在运行,先停止它 + if (this.isMakerRunning) { + this.logger.log('Stopping maker mode before starting taker mode (mutual exclusion)'); + await this.stopMaker(name); + } + await this.prisma.marketMakerConfig.update({ where: { id: config.id }, - data: { isActive: true }, + data: { isActive: true, makerEnabled: false }, }); this.isRunning = true; - this.logger.log(`Market maker ${name} started`); + this.logger.log(`Market maker ${name} taker mode started`); // 开始吃单循环 this.scheduleNextRun(config); @@ -723,6 +759,7 @@ export class MarketMakerService { /** * 启动挂单模式(双边深度) + * 注意:与吃单模式互斥,启动挂单模式会自动停止吃单模式 */ async startMaker(name: string = 'MAIN_MARKET_MAKER'): Promise { const configRecord = await this.prisma.marketMakerConfig.findUnique({ @@ -737,9 +774,15 @@ export class MarketMakerService { return; } + // 互斥:如果吃单模式正在运行,先停止它 + if (this.isRunning) { + this.logger.log('Stopping taker mode before starting maker mode (mutual exclusion)'); + await this.stop(name); + } + await this.prisma.marketMakerConfig.update({ where: { id: configRecord.id }, - data: { makerEnabled: true }, + data: { makerEnabled: true, isActive: false }, }); this.isMakerRunning = true; diff --git a/frontend/mining-admin-web/src/app/(dashboard)/market-maker/page.tsx b/frontend/mining-admin-web/src/app/(dashboard)/market-maker/page.tsx index 13894129..f963486e 100644 --- a/frontend/mining-admin-web/src/app/(dashboard)/market-maker/page.tsx +++ b/frontend/mining-admin-web/src/app/(dashboard)/market-maker/page.tsx @@ -58,6 +58,8 @@ export default function MarketMakerPage() { const { data: depthData, isLoading: depthLoading } = useDepth(10); const { data: depthEnabled, isLoading: depthEnabledLoading } = useDepthEnabled(); + const runningStatus = configData?.runningStatus; + const initializeMutation = useInitializeMarketMaker(); const depositCashMutation = useDepositCash(); const withdrawCashMutation = useWithdrawCash(); @@ -341,6 +343,49 @@ export default function MarketMakerPage() { + {/* 运行模式状态 */} + + +
+ 当前运行模式 + {runningStatus?.mode === 'idle' && ( + + + 空闲 + + )} + {runningStatus?.mode === 'taker' && ( + + + 吃单模式运行中 + + )} + {runningStatus?.mode === 'maker' && ( + + + 挂单模式运行中 + + )} +
+
+ +
+
+ + 吃单模式: {runningStatus?.takerRunning ? '运行中' : '未运行'} +
+
+ + 挂单模式: {runningStatus?.makerRunning ? '运行中' : '未运行'} +
+
+ + 两种模式互斥,启动一个会自动停止另一个 +
+
+
+
+ {/* 运行模式控制 */} @@ -359,9 +404,11 @@ export default function MarketMakerPage() { 吃单模式 - 自动买入用户卖单(Taker 策略) + + 自动买入用户卖单(Taker 策略)— 做市商作为买方,不触发额外销毁 + - {config.isActive ? ( + {runningStatus?.takerRunning ? ( 运行中 @@ -396,7 +443,7 @@ export default function MarketMakerPage() {
- {config.isActive ? ( + {runningStatus?.takerRunning ? (
+ + {runningStatus?.makerRunning && ( +
+ + 启动吃单模式将自动停止当前运行的挂单模式 +
+ )} @@ -438,10 +492,12 @@ export default function MarketMakerPage() { 挂单模式(双边深度) - 自动挂买卖单形成深度(Maker 策略) + + 自动挂买卖单形成深度(Maker 策略)— 做市商卖单被吃会触发销毁 + - {config.makerEnabled ? ( - + {runningStatus?.makerRunning ? ( + 运行中 @@ -455,6 +511,13 @@ export default function MarketMakerPage() {
+ {/* 警告提示 */} +
+ + 注意:挂单模式下做市商会挂卖单,当卖单被用户买入时会触发销毁机制导致价格上涨。 + 如果只想提供流动性而不影响价格,请使用吃单模式。 +
+

买单档位

@@ -483,7 +546,7 @@ export default function MarketMakerPage() {
- {config.makerEnabled ? ( + {runningStatus?.makerRunning ? ( ) : (
+ + {runningStatus?.takerRunning && ( +
+ + 启动挂单模式将自动停止当前运行的吃单模式 +
+ )}
diff --git a/frontend/mining-admin-web/src/features/market-maker/api/market-maker.api.ts b/frontend/mining-admin-web/src/features/market-maker/api/market-maker.api.ts index 46e98c74..0d4a43fd 100644 --- a/frontend/mining-admin-web/src/features/market-maker/api/market-maker.api.ts +++ b/frontend/mining-admin-web/src/features/market-maker/api/market-maker.api.ts @@ -34,6 +34,15 @@ tradingClient.interceptors.response.use( } ); +export type MarketMakerMode = 'idle' | 'taker' | 'maker'; + +export interface RunningStatus { + mode: MarketMakerMode; + takerRunning: boolean; + makerRunning: boolean; + modeDescription?: string; +} + export interface MarketMakerConfig { id: string; name: string; @@ -90,12 +99,22 @@ export interface DepthData { } export const marketMakerApi = { - // 获取做市商配置 - getConfig: async (name: string = 'MAIN_MARKET_MAKER'): Promise<{ success: boolean; config: MarketMakerConfig | null }> => { + // 获取做市商配置(包含运行状态) + getConfig: async (name: string = 'MAIN_MARKET_MAKER'): Promise<{ + success: boolean; + config: MarketMakerConfig | null; + runningStatus: RunningStatus; + }> => { const response = await tradingClient.get(`/admin/market-maker/${name}/config`); return response.data; }, + // 获取运行状态 + getRunningStatus: async (): Promise<{ success: boolean } & RunningStatus> => { + const response = await tradingClient.get('/admin/market-maker/status'); + return response.data; + }, + // 初始化做市商 initialize: async (data: { name?: string;