vibeCoding开发流程存档(pm-trader)

·28 阅读·vibeCoding

github链接:https://github.com/trickter/pm-trader

coding规范

  1. 先问自己,你开发这个系统的主要想法是啥,要帮你解决什么问题?
  2. 生成AGENTS.md和rules文件。(针对项目建立好契约,先看看pm实际的接口定义)
  3. 整体方案
    • 约束
    • 核心目标
    • 架构设计
    • 功能模块申明
    • 实施步骤

PM相关

核心概念

  1. 获取slug

    • 比如市场链接为https://polymarket.com/event/oscars-2026-best-actor-winner/will-michael-b-jordan-win-best-actor-at-the-98th-academy-awards#VaHIG4t
    • oscars-2026-best-actor-winner为event slug
    • will-michael-b-jordan-win-best-actor-at-the-98th-academy-awards#VaHIG4t为market slug
    • https://gamma-api.polymarket.com/events?{slug}按slug事件查找
    • https://gamma-api.polymarket.com/markets/slug/{slug} 按市场slug查找
    • 一个事件,可以有多个市场,比如总统选举

    event-market

字段

题目文本 → 哈希成 question ID → 在 CTF 里结合 oracle 和 outcome 数量生成 condition ID,outcome表示生成的token个数(如果只有yes和no,他就是2) → 再为每个 outcome 派生出一个 token ID → 最终形成这个 market 的可交易 Yes/No 代币对,并在市场对象里挂着一个 market address / marketMakerAddress 供系统识别与交互

签名

第 1 步:用私钥做 L1

  • 签一个 EIP-712 的认证消息
  • /auth/api-key/auth/derive-api-key 得到 apiKey / secret / passphrase

第 2 步:保存 L2 凭证

  • 后面高频调用交易接口时主要用它

第 3 步:创建订单时,先本地签订单 payload

  • 因为订单本身必须由用户授权签名。

第 4 步:把签好的订单,通过带 L2 headers 的请求发给 CLOB

  • 服务器验证你的请求签名和订单签名
  • 然后处理挂单/撤单/管理订单等。

prompt

总体设计方案

你是一个资深全栈工程师 + 量化交易系统架构师。请基于 Polymarket 官方文档,开发一个“Polymarket 策略交易系统”的 MVP,技术栈使用 Next.js(App Router)+ TypeScript,前后端一体化实现。

核心原则:
1. 必须严格基于事实开发,只能使用 Polymarket 官方文档中明确存在的 API、字段、认证方式和交易流程。
2. 不允许编造接口、字段、SDK 方法、WebSocket 事件名、签名流程或链上流程。
3. 若文档不确定,先在代码中用 TODO + 注释标记,并输出“待人工确认项”,不要擅自实现。
4. 优先使用 Polymarket REST API, 链接:https://docs.polymarket.com/api-reference。
5. 先完成 MVP,再考虑扩展,不要一开始做过度设计。

你要参考的事实边界:
- Gamma API:用于 markets / events / tags / search 等市场发现与浏览。
- Data API:用于 positions / trades / activity / holders / open interest / leaderboard 等数据读取。
- CLOB API:用于 orderbook / prices / spreads / price history,以及下单、撤单、订单管理。
- Gamma 与 Data API 为公开读接口;CLOB 的交易类接口需要认证。
- 实时 orderbook/价格更新优先用 WebSocket market channel,而不是高频轮询。
- Polymarket 订单底层都是 limit order;“market order”按官方语义是提交可立即成交的 limit order。

目标:
做一个能跑通的 MVP,包含:
A. 市场发现
B. 策略配置
C. 实时行情展示
D. 模拟信号生成
E. 真实下单执行
F. 订单/持仓/成交查看
G. 基础风控
H. 前端可操作页面

MVP 功能范围(必须先实现这些):

1. 首页 / Dashboard
- 展示系统概览
- 展示已配置策略数、运行状态、最近信号、最近订单、最近成交
- 展示选定市场的最新价格、买一卖一、点差、24h 量(仅在官方字段确实存在时展示)
- 明确区分“实时行情”和“策略状态”

2. 市场发现页
- 支持通过 Gamma API 拉取市场或事件
- 支持按关键词搜索 market / event / profile(如果官方 search 接口支持)
- 支持按 active / closed / tag / slug / event 等官方实际支持的条件筛选
- 点击某个 event 后,展示其下属 market 列表
- 点击 market 后进入 market 详情页

3. Market 详情页
- 展示市场基础信息(标题、slug、状态、开始/结束时间、流动性、成交量、open interest 等,仅显示文档里确实有的字段)
- 展示 outcome 列表、token 信息、market / condition / question 等关联标识(仅文档明确有时)
- 展示 orderbook(买盘 / 卖盘)
- 展示 best bid / best ask / mid / last trade price(若官方接口或 SDK 可直接获取)
- 接入实时行情更新(优先 WebSocket;如果首版实现复杂,可以先轮询,但必须说明后续要切到 WSS)

4. 策略页
- 先只做 2 种 MVP 策略:
  a) 阈值突破策略:当 YES 或 NO 价格超过设定阈值时触发
  b) 点差/盘口策略:当 spread、挂单深度、买一卖一失衡达到阈值时触发
- 每个策略配置项:
  - strategy name
  - market / token selection
  - side (BUY / SELL)
  - trigger params
  - max order size
  - max daily trade count
  - cooldown seconds
  - dry-run 开关
  - enabled 开关
- 所有配置落库

5. 策略执行引擎
- 在服务端运行,不在浏览器中跑
- 能周期性拉取行情,或消费 WebSocket 行情
- 对启用中的策略计算信号
- 生成标准 signal 对象:
  - strategyId
  - marketId / tokenId
  - signalType
  - side
  - reason
  - observedPrice / spread / book snapshot summary
  - timestamp
- dry-run 模式下只记录信号,不下单
- live 模式下进入下单流程

6. 交易执行模块
- 使用官方支持的 CLOB 认证与下单流程
- 优先使用官方 TypeScript client
- 必须把“读取行情”和“提交订单”分层隔离
- 支持:
  - place order
  - cancel order
  - list open orders
  - view fills / trades(若官方支持并且认证完成)
- 下单前必须经过风控校验:
  - 单笔下单上限
  - 每策略日内次数限制
  - 最小成交单位 / tick size 校验(若官方接口可得)
  - 不允许重复信号连续下单
- 不得伪造 market order;若实现“市价买入/卖出”,必须按官方语义用可成交的 limit order 实现,并在 UI 与代码注释中写明

7. 订单与持仓页
- 展示 open orders、order history、trade history
- 若 Data API 或官方接口支持 positions,则展示当前持仓
- 所有数据要注明来源:Gamma / Data / CLOB / local DB
- 对官方没有的字段,不允许前端假数据显示

8. 风控页
- 可设置全局风控参数:
  - global max exposure
  - per-market max exposure
  - max order notional/size
  - max daily orders
  - emergency stop
- 提供 kill switch:开启后停止所有新下单
- 提供 cancel all open orders 按钮(只有官方确有该能力时再实现)
- 所有风控操作记录审计日志

9. 系统设置页
- 配置 API host、链 ID、钱包/签名方式、是否 dry-run
- 所有密钥只能保存在服务端环境变量,绝不能下发到前端
- 如果使用私钥或 API key,必须集中封装在 server-only 模块中

技术要求:
1. 使用 Next.js App Router + TypeScript
2. UI 可用 Tailwind CSS + shadcn/ui
3. 数据库使用 PostgreSQL + Prisma
4. 使用 server actions 或 route handlers 实现服务端 API
5. 行情拉取、策略计算、下单执行必须运行在服务端
6. 前端与后端目录结构清晰,模块边界清晰
7. 必须提供完善的 types 定义,不允许 any 泛滥
8. 必须提供错误处理、重试、限流保护、日志记录
9. 由于官方存在 rate limits,调用层必须做节流/退避设计
10. 实时更新模块要与 UI 解耦,避免页面组件直接处理复杂交易逻辑

建议架构:
- apps/web 或单体 next app
- src/lib/polymarket/gamma.ts
- src/lib/polymarket/data.ts
- src/lib/polymarket/clob-public.ts
- src/lib/polymarket/clob-trading.ts
- src/lib/polymarket/ws.ts
- src/lib/strategy/engine.ts
- src/lib/strategy/rules/*
- src/lib/risk/*
- src/lib/db/*
- src/app/(dashboard)/*
- prisma/schema.prisma

数据库模型至少包含:
- MarketCache
- EventCache
- Strategy
- StrategyRun
- Signal
- Order
- Fill
- PositionSnapshot
- RiskRule
- AuditLog
- SystemSetting

开发步骤(按顺序输出并执行):
Step 1. 先阅读并归纳官方文档里本 MVP 真正会用到的 API / SDK / WSS 能力
输出一个“事实清单”,格式如下:
- 能力名称
- 官方来源
- 使用方式
- 是否需要认证
- MVP 是否采用
- 不确定点

Step 2. 基于事实清单,给出 MVP 产品方案
输出:
- 页面信息架构
- 用户流程
- 策略执行流程
- 下单流程
- 风控流程
- 非目标范围(本期不做)

Step 3. 给出系统架构设计
输出:
- 模块图
- 数据流
- 服务端任务流
- 实时行情流
- 鉴权与密钥管理方案
- 数据库 schema 草案

Step 4. 初始化项目代码
要求:
- 先生成完整目录结构
- 再生成 package.json、tsconfig、eslint、prisma、env.example
- 再逐模块产出代码
- 每生成一个模块,都说明它依赖的官方事实依据

Step 5. 实现最小可运行版本
至少跑通:
- 市场搜索
- 市场详情
- orderbook 展示
- 策略创建
- dry-run 信号生成
- live order 提交(如果认证参数已齐)
- open orders / trades / positions 查询(仅实现官方已确认支持的部分)

Step 6. 输出验收清单
包括:
- 已实现功能
- 依赖的官方接口
- 尚未确认的文档点
- 风险点
- 下一步路线图

代码质量要求:
- 所有涉及 Polymarket 的接口调用,都要封装成 typed client
- 所有外部 API 响应都要做 zod 校验或等价校验
- 所有交易行为必须可审计
- 所有页面要有 loading / empty / error 状态
- 所有关键动作必须有 toast 或状态反馈
- 不要写伪代码占位,除非该处确实依赖未确认文档;这时必须写明“为什么不能继续硬写”

禁止事项:
- 禁止假设不存在的接口
- 禁止伪造 websocket payload
- 禁止伪造 order status 枚举
- 禁止把私钥暴露到前端
- 禁止把“预测逻辑”写成拍脑袋黑盒,不解释触发条件
- 禁止在没有官方依据时实现充值、提现、桥接、代理钱包复杂流程
- 禁止默认支持所有 Polymarket account 类型;只实现文档已确认的那种,并在 README 里写清楚适用范围

输出格式要求:
1. 先输出“事实清单”
2. 再输出“架构与页面方案”
3. 再输出“项目目录结构”
4. 再输出“关键代码文件”
5. 再输出“本地运行说明”
6. 再输出“已知限制”

请从 Step 1 开始,不要跳步。

codex初版设计输出

• 事实清单

  - Gamma markets / events / public-search
      - 官方来源: https://docs.polymarket.com/developers/gamma-markets-api/overview,
        https://docs.polymarket.com/developers/gamma-markets-api/get-markets,
        https://docs.polymarket.com/developers/gamma-markets-api/search
      - 使用方式: 市场发现、事件钻取、关键词搜索
      - 是否需要认证: 否
      - MVP 是否采用: 是
      - 不确定点: tag 精确筛选参数这版未硬接
  - Data API trades / positions
      - 官方来源: https://docs.polymarket.com/developers/data-api/overview,
        https://docs.polymarket.com/developers/data-api/positions/get-positions
      - 使用方式: 公共成交流、按地址读持仓
      - 是否需要认证: 否
      - MVP 是否采用: 是
      - 不确定点: positions 返回字段随账户而变,这版只渲染已校验字段
  - CLOB public reads
      - 官方来源: https://docs.polymarket.com/developers/CLOB/orders/get-order-book,
        https://docs.polymarket.com/developers/CLOB/authentication, 官方 @polymarket/clob-client
      - 使用方式: orderbook、best bid/ask、spread、mid、last trade
      - 是否需要认证: 否
      - MVP 是否采用: 是
      - 不确定点: 实时更新当前先走服务端轮询
  - CLOB authenticated trading
      - 官方来源: https://docs.polymarket.com/developers/CLOB/authentication,
        https://docs.polymarket.com/developers/CLOB/orders/create-order
      - 使用方式: createOrDeriveApiKey()、createAndPostOrder()、getOpenOrders()、getTrades()、cancelAll()
      - 是否需要认证: 是
      - MVP 是否采用: 是
      - 不确定点: 本期只实现 EOA,不做 POLY_PROXY / POLY_GNOSIS_SAFE
  - CLOB WebSocket market channel
      - 官方来源: https://docs.polymarket.com/developers/CLOB/websocket/wss-overview,
        https://docs.polymarket.com/developers/CLOB/websocket/market-channel
      - 使用方式: 官方推荐实时行情
      - 是否需要认证: 否
      - MVP 是否采用: 否
      - 不确定点: payload 处理与重连策略先留 TODO

  架构与页面方案

  - 页面: / Dashboard、/markets、/markets/[marketId]、/strategies、/orders、/risk、/settings
  - 数据流: Gamma / Data / CLOB -> typed client + zod -> server modules -> Prisma / audit -> App Router 页面
  - 策略流: 服务端轮询启用策略 -> 拉 quote -> 规则计算 -> 生成 Signal -> dry-run 只落库,live 走风控后下单
  - 下单流: 页面或策略 -> 风控 -> 官方 CLOB client -> 本地 Order / AuditLog
  - 风控流: 全局暴露、单市场暴露、单笔上限、日内次数、重复信号、cooldown、kill switch
  - 非目标: 充值/提现/桥接、代理钱包复杂流程、未确认 tag 过滤、未确认 WSS payload 实盘消费

  项目目录结构

  prisma/schema.prisma
  src/app/{page.tsx,markets,strategies,orders,risk,settings,api/*}
  src/components/{market-pages.tsx,ui/*,forms/*}
  src/lib/polymarket/{gamma,data,clob-public,clob-trading,ws,types,server-config}.ts
  src/lib/strategy/{engine,types,rules/*}.ts
  src/lib/risk/engine.ts
  src/lib/db/{settings}.ts
  src/lib/http/fetch-json.ts
  src/lib/mvp-facts.ts
  src/instrumentation.ts

  关键代码文件

  - Typed clients: gamma.ts, data.ts, clob-public.ts, clob-trading.ts
  - 策略与风控: engine.ts, threshold-breakout.ts, spread-imbalance.ts, engine.ts
  - 数据模型: schema.prisma
  - 页面入口: page.tsx, page.tsx, page.tsx, page.tsx, page.tsx
  - 事实与待确认项: mvp-facts.ts, README.md

  本地运行说明

  1. 复制 .env.example 到 .env.local
  2. 配置 PostgreSQL 的 DATABASE_URL
  3. npm install
  4. npm run prisma:generate
  5. npm run prisma:push
  6. npm run dev

  已验证:

  - npm run prisma:generate
  - npm run lint
  - npm run build

  已知限制

  - 实时行情目前走 /api/quote 服务端轮询,未直接消费官方 WSS market channel
  - 只支持 EOA 账户模式;POLY_PROXY / POLY_GNOSIS_SAFE 未实现
  - tag 服务端过滤未接,因为这轮没有把精确 query 参数确认到可安全硬编码
  - positions 展示依赖 POLYMARKET_TRADER_ADDRESS 且 Data API 对该地址返回非空
  - 首次 live 交易前的 allowance / approval 流程保留待人工确认,没有擅自自动化

UI美化-claude

你是一个资深前端 UI 工程师 + Next.js 工程师。
请对当前这个 Polymarket 策略交易系统的前端页面做一轮 展示层优化 / 布局修复 / 可读性提升,目标是解决当前页面中明显存在的 UI 问题,但不要改动业务逻辑、接口调用逻辑、数据来源标注和页面信息架构。

总目标

基于现有页面,做一次 MVP 级前端 polish,重点修复以下问题:

长文本、长 ID、slug、condition id、market id、时间范围等内容发生溢出、挤压、重叠、超出卡片边界

数字过长时没有良好的排版策略,导致卡片内容拥挤或换行难看

卡片高度不一致、信息密度不均、视觉节奏不稳定

列表页中的 event / market 卡片排版不稳,标题、slug、liquidity、volume 的布局容易挤在一起

页面局部留白、栅格、对齐、响应式行为不够稳定

信息优先级不清晰:主值、标签、来源、辅助字段之间视觉层级不够明确

某些英文长串字段不适合直接裸展示,应该提供截断、悬浮提示、复制按钮或详情展开机制

你必须遵守的约束

不要改业务逻辑

不要修改 API 调用

不要变更数据来源

不要改动策略逻辑、交易逻辑、风控逻辑

不要编造字段

页面上展示什么字段,仍然基于当前已有数据

只能优化展示方式、布局方式、交互细节

只做前端视图层与组件层优化

允许重构 React 组件

允许提取通用 UI 组件

允许补充 utility class / CSS / 设计 token

允许增强响应式布局

允许增加 tooltip / copy button / truncate / expand-collapse

保持当前整体设计风格

保留现有偏米色 / 绿色 / 简洁仪表盘风格

不要重做视觉品牌

不要引入重型设计系统改版

需要你重点修复的真实问题
一、详情页指标卡问题

从当前截图看,详情页里的指标卡存在这些问题:

condition id、slug、时间范围等长字段直接撑爆卡片

某些字段文本横向挤压,已经发生内容重叠

指标值与来源文案之间的间距不统一

不同卡片内容长度差异过大,但卡片内部没有自适应策略

数字字段如 volume / liquidity / open interest 很长,读起来费劲

“来源: Gamma / CLOB ...” 这类次级信息权重不够统一

请优化策略:

对长字段统一采用:

单行截断 + tooltip

或两行截断 + tooltip

对 hash / id 类字段可增加 copy 按钮

对数值统一做格式化策略:

千分位

可选紧凑格式(如 105,020.485 或 105.0K / 1.4M,按页面场景决定)

保证同类卡片格式统一

卡片内部采用稳定的纵向结构:

label

primary value

secondary/meta info

让所有 summary/stat cards 具备统一最小高度与统一 padding

避免任何文本溢出卡片边界

二、市场发现页卡片问题

从截图看,市场发现页存在:

market 卡片里的标题换行不美观

slug、liquidity、volume 容易互相挤压

长标题把信息区顶得过于松散

event 卡片里的 question 列表和头部信息层级不够稳定

右侧 market grid 卡片宽度偏窄,导致文本体验差

请优化策略:

重新设计 event card / market card 的内部布局

标题采用限定行数(如 line-clamp-2 / 3)

slug 作为次级信息,弱化显示并截断

liquidity / volume 改成统一的 metadata row 或 definition-style layout

badge(如 Active)位置固定,避免挤占标题区域

调整 grid 列数与断点策略,让卡片宽度更合理

让 event 列表和 market grid 在大屏上更平衡,不要一边太挤一边太空

三、长文本字段展示规范

请为以下字段建立统一展示规则:

slug

market id

question id

condition id

clob token id

contract / address / hash 类字段

开始 / 结束时间

很长的 market title / event title

规范建议:

页面摘要场景:截断显示

鼠标悬浮:显示完整值

可复制的字段:显示 copy icon/button

详情页需要完整内容时:放进 description list / collapsible section / mono font block,而不是硬塞在 summary card 里

四、响应式与栅格系统

请统一检查并优化:

左侧 sidebar 固定宽度与主内容区的比例

dashboard / discovery / detail 页的 grid 断点

2xl、xl、lg、md、sm 下的卡片列数

确保在常见桌面宽度下不会出现:

卡片过窄

卡片间距不均

文本过度换行

横向溢出

建议:

指标卡区域使用更稳定的 auto-fit / explicit breakpoints

market grid 适当提高最小卡片宽度

详情页基础信息区域按“短字段卡片”和“长字段块”拆开,而不是一套卡片模板打天下

五、视觉层级与可读性

请统一提升以下层级:

页面标题

section 标题

卡片 label

primary metric

source/meta text

badge/status

辅助说明

建议:

数值更突出

来源说明更弱化

label 统一大小写和字重

mono font 仅用于 id/hash/token/address

对英文超长串使用更适合的字距和字号

建议你输出和执行的步骤
Step 1:先审视现有页面问题

先根据现有代码和截图,总结一份 UI 问题清单,按页面分组:

Dashboard

市场发现页

市场详情页

通用卡片/通用字段组件

Step 2:制定一套最小设计规范

请先定义一套当前项目内可落地的轻量规范:

spacing

card padding

radius

title / subtitle / meta / metric 字号层级

badge 样式

truncate / tooltip / copy pattern

number formatting pattern

long-id display pattern

Step 3:组件级重构

优先抽出或优化这些通用组件:

StatCard

MetaRow

ValueWithSource

TruncatedField

CopyableId

MarketCard

EventCard

SectionHeader

DescriptionList 或 KeyValueGrid

Step 4:逐页修复

按顺序修:

市场发现页

市场详情页

Dashboard

其他复用页面

Step 5:输出最终结果

最后输出:

改了哪些组件

每类问题如何被解决

哪些字段使用了截断/tooltip/copy

哪些 grid / spacing / typography 被统一

哪些问题仍需后续优化

实现要求

技术栈默认:

Next.js App Router

TypeScript

Tailwind CSS

shadcn/ui(如果项目里已有)

不要额外引入复杂 UI 框架

实现要求:

尽量通过组件复用解决问题,不要每页硬 patch

能用 Tailwind utility 就不要过度写自定义 CSS

对于 tooltip / copy / line clamp / overflow,要形成统一模式

所有修改都必须可运行,不要只写伪代码

不要输出空泛建议,要直接改代码

如果某些布局问题来自父容器宽度或 grid 设置,请连同父层一起修

避免为了“显示完整文本”而破坏整体布局

所有长串标识建议使用等宽字体 + 截断 + tooltip + copy

所有数字展示必须统一格式化函数,不允许各处手写

具体修复建议(可直接落实)

请优先考虑这些具体实现:

对 summary cards:

min-h

flex flex-col justify-between

value 区和 meta 区留固定 gap

对长文本:

truncate

line-clamp-2

break-all 只用于极少数 hash block,不要滥用

对 ID:

详情卡摘要中只显示前后几位,如 0xb486...40c8b

完整值放 tooltip / copy

对数字:

做 formatNumber()、formatCompactNumber()、formatPrice()、formatPercent() 工具函数

对 grid:

统一卡片最小宽度

避免右侧 market card 过窄

对标题:

标题行数限制

badge 不与标题抢主区域

对 section:

统一 section header 与内容区间距

对 metadata:

使用 definition list 或双列 key-value 结构,避免自由文本乱流动

交付要求

请直接开始改代码,并按下面格式输出:

问题清单

优化方案

修改的组件/页面

关键代码

结果说明

不要只给建议,要实际修改项目代码。

position页面优化

你是一个资深全栈工程师,当前任务不是做 UI 美化,而是修正 /orders 页面中 Positions 的业务展示逻辑,使其更接近 Polymarket 原始仓位页的使用习惯。

任务目标

请调整 /orders 页面里 Positions tab 的数据筛选逻辑:

默认不展示已经结束的市场(closed / resolved / expired / ended)对应的仓位。

也就是说,Positions 默认应更接近“当前仍有交易/观察意义的活跃仓位列表”,而不是把所有历史仓位都混在一起展示。

核心要求
1. 默认筛掉已结束市场的仓位

在 Positions tab 中,默认只展示仍处于活跃状态、尚未结束、或仍应被视为 open/active 的市场仓位。

不要展示这些仓位:

市场已结束

事件已结束

已 resolved

已 closed

已 expired

已经过最终结算且不再属于活跃仓位浏览范围

如果当前数据源里状态字段命名不同,请基于真实字段做映射判断,不要凭空造字段。

2. 优先参考真实市场状态

判断一个 position 是否应该显示,优先参考与该仓位关联的市场/事件真实状态,例如:

market active

market closed

market archived

event closed

endDate / endTimestamp / closedTime / resolutionTime 等时间字段

是否已明确 resolved

优先使用真实上游状态判断,不要只根据仓位 size 是否大于 0 来决定显示。

3. 不要误删活跃仓位

注意边界:

仍在 active 的市场,即使 value 为 0,也不能因为 value=0 就直接隐藏

仍未结束的市场仓位,只要是有效 position,就应展示

如果市场接近结束但尚未结束,仍然展示

如果数据不完整、无法确定是否结束,默认宁可保守显示,并在代码里注明 fallback 规则

4. History 里仍应保留历史记录

这个改动只针对 Positions 默认展示逻辑。

要求:

Positions:默认只看未结束市场的仓位

History:仍然保留历史订单/成交/已结束市场的历史记录

Open orders:保持现有逻辑,不要因为这个任务误伤未成交订单列表

推荐实现方式
方案目标

请把这个能力实现成明确的业务过滤层,而不是散落在页面 JSX 中写临时判断。

建议抽象出类似:

isMarketEnded(marketOrEvent)

isPositionVisibleInPositionsTab(position, marketContext)

filterActivePositions(positions, markets, events)

由数据层或 selector 层统一处理,再给页面消费。

你需要先做的事情
Step 1:先检查当前 /orders -> Positions 数据来源

请先梳理:

当前 Positions tab 的数据来自哪里

Data API positions

local DB

market cache

event cache

其他聚合层

Position 数据是否已经带有这些字段:

market id

event id

slug

active / closed 状态

end date / end timestamp

resolved 状态

如果 position 本身没有结束状态,当前代码是如何关联 market / event 数据的

Step 2:建立“市场是否结束”的统一判断规则

请基于当前真实数据结构,建立统一规则,按优先级判断一个市场是否结束。

建议优先级类似:

明确状态字段

closed / resolved / active=false / archived

明确结束时间字段

若 endDate/endTimestamp 已早于当前时间,且该字段语义确认是结束时间,可视为 ended

event 层结束状态

fallback

如果没有足够字段,保守不隐藏,并记录 TODO 注释

禁止编造不存在的状态枚举。

Step 3:在 Positions tab 应用过滤

把过滤逻辑应用到 Positions 的数据流中:

页面初始加载时就只显示未结束市场仓位

搜索、排序应作用于过滤后的结果

summary count 也要基于过滤后的 Positions 数量重新计算,除非当前产品明确要显示原始总数

Step 4:可选增强

如果当前结构合适,可以增加一个可选开关,但不是必须:

默认:Active positions

可切换:Show ended markets

但注意:

这不是本轮硬性要求

本轮核心是默认不展示已结束市场

如果加开关,默认必须关闭 ended markets

业务规则要求
必须遵守

不要改 UI 风格作为主要任务

不要伪造字段

不要编造 Polymarket 状态语义

不要把“没有 value / 没有 pnl”误判为 ended

不要把所有历史 position 直接删掉,只是不要在 Positions 默认列表里展示

不要影响 History 和 Open orders 的数据正确性

推荐的判断思路

你可以按这种业务语义实现,但必须映射到项目里的真实字段:

一个 position 在 Positions tab 默认可见,当且仅当:
- 它能关联到一个市场/事件
- 且该市场/事件未被判断为 ended / closed / resolved / expired
- 若无法判断市场是否结束,则默认保守显示
边界情况

请特别处理这些情况:

情况 1:position 有 token / asset,但拿不到 market 状态

不要直接隐藏

默认显示

并在日志或注释中标记无法确定状态

情况 2:market 已结束,但 local DB 中还有残留 position

在 Positions 默认隐藏

但不删除底层数据

情况 3:event 结束但 market 字段不完整

优先用可确认的 event 状态/结束时间判断

情况 4:同一个市场在不同数据源状态不一致

优先采用你项目中定义的主数据源

在代码中明确优先级

不要静默混用造成随机行为

实现要求
代码层要求

把过滤逻辑放在 selector/service/helper 中

页面组件只消费过滤后的结果

补充必要的 TypeScript 类型保护

对空值、缺字段、时间字段格式异常做好防御

若项目已有数据归一化层,请在那里处理

必要时补最小测试用例

如果项目里有测试,请补这些测试

至少覆盖:

active market position -> 显示

closed market position -> 不显示在 Positions

resolved market position -> 不显示在 Positions

ended-by-time market position -> 不显示(前提是时间字段语义明确)

missing-status position -> 默认显示

History 数据不受影响

输出要求

请按以下格式输出并执行:

当前数据流分析

结束市场判断规则

过滤逻辑落点

修改的文件

关键代码

边界情况处理

验证结果

webSocket相关

Market Channel 用 assets_ids(token ID),User Channel 用 markets(condition ID)

核心prompt

交易执行架构必须采用“WebSocket 主链路 + HTTP fallback + 数据新鲜度闸门”模式。

1. 市场实时数据(orderbook / best bid ask / last trade / market lifecycle)优先来自 market WebSocket。
2. 用户订单与成交状态优先来自 authenticated user WebSocket。
3. HTTP 仅用于冷启动快照、状态对账、断线恢复和短时 fallback,不得作为长期实时交易主链路。
4. 若关键市场数据超过 maxMarketDataStalenessMs 未更新,则视为 stale market data。
5. 若关键用户订单状态超过 maxUserStateStalenessMs 未更新,则视为 stale user data。
6. 当任一关键数据源 stale 时,系统必须停止新交易;根据策略配置,可进一步撤销所有未完成挂单并暂停策略。
7. maxMarketDataStalenessMs 和 maxUserStateStalenessMs 必须可在 UI 中配置,默认值先设为 5000ms。
8. WebSocket 恢复后,不得直接恢复交易,必须先做 HTTP reconcile,确认 order / fill / inventory / open orders 状态一致,再恢复。

codex方案

  # Polymarket 交易链路改造方案:WebSocket 主链路 + HTTP Fallback + 数据新鲜度闸门

  ## Summary

  把当前“定时 HTTP 拉行情 -> 策略计算 -> 下单”的主链路,改成两个服务端 WebSocket 主链路驱动:

  - market WebSocket 负责市场实时数据:book、price_change、tick_size_change、last_trade_price,以及启用 custom_feature_enabled:
    true 后的 best_bid_ask、new_market、market_resolved
  - user WebSocket 负责用户订单/成交状态,使用官方 API 凭证 apiKey / secret / passphrase 鉴权
  - HTTP 只用于冷启动快照、断线恢复、状态对账和短时 fallback,不再作为长期实时交易主链路
  - 新增“数据新鲜度闸门”:市场数据或用户状态任一 stale,系统立即停止新交易;WebSocket 恢复后必须先完成 HTTP reconcile,确认 open
    orders / trades / positions(inventory) 与本地一致,再允许恢复 live 交易

  默认策略行为采用保守方案:

  - pauseOnStaleData = true
  - cancelOpenOrdersOnStaleData = false
  - stale 时先全局阻止新单,再按策略配置决定是否暂停和撤掉该策略相关挂单

  ## Key Changes

  ### 1. 运行时与数据模型

  - 扩展 RuntimeSettings
      - maxMarketDataStalenessMs: number,默认 5000
      - maxUserStateStalenessMs: number,默认 5000
  - 扩展 Strategy
      - pauseOnStaleData: boolean,默认 true
      - cancelOpenOrdersOnStaleData: boolean,默认 false
  - 新增持久化状态表,单独保存链路健康与 reconcile 状态
      - SystemHealthState
          - key
          - marketWsConnected
          - userWsConnected
          - lastMarketMessageAt
          - lastUserMessageAt
          - lastMarketReconciledAt
          - lastUserReconciledAt
          - tradingBlocked
          - blockReason
          - marketStale
          - userStale
          - lastHeartbeatAt
          - details
  - 不新增假字段到 Polymarket 响应类型;只在本地新增归一化缓存类型
      - LiveMarketState
      - LiveUserState
      - TradingReadiness

  ### 2. WebSocket 与 HTTP 服务端分层

  - 重写 src/lib/polymarket/ws.ts 为实际服务端模块,拆成三层
      - market-stream.ts
          - 连接 wss://ws-subscriptions-clob.polymarket.com/ws/market
          - 订阅当前启用策略涉及的 token 集合
          - 维护 token 级实时状态:orderbook、best bid/ask、last trade、tick size、最后消息时间、market lifecycle 事件
      - user-stream.ts
          - 连接 wss://ws-subscriptions-clob.polymarket.com/ws/user
          - 使用官方 API credentials 鉴权
          - 维护订单状态、成交增量、最后消息时间
      - stream-supervisor.ts
          - 统一重连、退避、订阅更新、健康状态计算
          - 计算 stale
          - stale 时设置全局 tradingBlocked
          - 重连成功后触发 reconcile,不直接解锁交易
  - 保留现有 HTTP clients,但角色改成:
      - 冷启动快照:getOrderBook / listOpenOrders / listTrades / getPositions
      - 对账恢复:从远端拉全量快照,覆盖/修正本地状态
      - 短时 fallback:若 market/user WS 尚未 ready,则读 HTTP 但结果仅用于显示或 reconcile,不作为可交易新鲜数据来源

  ### 3. 新鲜度闸门与 reconcile

  - 新增统一闸门模块 src/lib/trading/readiness.ts
      - getTradingReadiness()
      - assertFreshMarketData(tokenId)
      - assertFreshUserState()
      - assertTradingAllowedForExecution(strategy?)
  - 新鲜度规则
      - Date.now() - lastMarketMessageAt > maxMarketDataStalenessMs => marketStale
      - Date.now() - lastUserMessageAt > maxUserStateStalenessMs => userStale
      - 任一 true => tradingBlocked = true
  - reconcile 规则
      - market WS 恢复后:拉当前策略 token 的 HTTP 快照并覆盖实时缓存
      - user WS 恢复后:HTTP 拉 open orders、trades、positions
      - reconcile 必须完成并标记成功时间后,才能从 tradingBlocked 切回可交易
  - 对账内容
      - Order: 用远端 open orders / trade history 修正本地 SUBMITTED / PARTIALLY_FILLED / FILLED / CANCELLED
      - Fill: 根据 trades 去重落库
      - inventory: 用 Data API positions 作为只读库存视图来源,不做推断式修改
  - stale 期间动作
      - 所有新 live 交易请求直接拒绝:策略引擎、手动下单、API 下单都走同一闸门
      - 对启用策略:
          - 若 pauseOnStaleData=true,记录暂停状态并在引擎中跳过
          - 若 cancelOpenOrdersOnStaleData=true,只撤该策略尚未完成的挂单,不做全局 cancelAll
  - 恢复后动作
      - 不自动恢复 paused 策略的 enabled 字段
      - 只解除全局 tradingBlocked
      - 策略如果是“因 stale 被暂停”,新增本地运行态 pausedBySystem=true,reconcile 成功后清除;不篡改用户手工 enabled=false

  ### 4. 引擎、风控、下单链路改造

  - 改造 strategy/engine.ts 和 strategy/range-engine.ts
      - 行情来源从 getMarketQuote(tokenId) 改为先读 LiveMarketState
      - 若该 token 没有 fresh WS 数据,则本轮直接记 StrategyRun(status="blocked")
      - 不再在 live 执行路径上主动轮询 CLOB quote
  - 改造 risk/engine.ts
      - 在现有额度/次数/重复信号检查前,先走 assertTradingAllowedForExecution
      - 风控拒绝原因新增
          - STALE_MARKET_DATA
          - STALE_USER_STATE
          - RECONCILE_REQUIRED
  - 改造 clob-trading.ts
      - 增加 getOrCreateApiCredentials() 暴露给 user WS 鉴权复用
      - 下单前只用 tickSize 等已缓存市场数据,不再额外发 HTTP quote 请求
  - 改造 /api/orders/manual、server actions、/api/engine/run
      - 全部接入同一 readiness gate
      - stale / reconcile 未完成时返回明确错误,不允许绕过

  ### 5. UI 与可观测性

  - Settings 页新增运行时配置项
      - maxMarketDataStalenessMs
      - maxUserStateStalenessMs
  - Strategies 页新增两个配置项
      - pauseOnStaleData
      - cancelOpenOrdersOnStaleData
  - Dashboard / Orders / Risk 增加只读状态展示
      - market WS: connected / stale / reconnecting
      - user WS: connected / stale / reconnecting
      - trading readiness: ready / blocked / reconciling
      - lastMarketMessageAt / lastUserMessageAt / lastReconciledAt
  - AuditLog 新增动作
      - market_ws_connected
      - market_ws_stale
      - user_ws_connected
      - user_ws_stale
      - trading_blocked
      - trading_unblocked_after_reconcile
      - strategy_paused_by_staleness
      - strategy_orders_cancelled_by_staleness
      - reconcile_started
      - reconcile_completed
      - reconcile_failed

  ## Public Interfaces / Types

  - RuntimeSettings
      - 新增 maxMarketDataStalenessMs
      - 新增 maxUserStateStalenessMs
  - Strategy
      - 新增 pauseOnStaleData
      - 新增 cancelOpenOrdersOnStaleData
  - 新增 server-only 接口
      - startPolymarketStreams()
      - updateTrackedMarkets(tokenIds: string[])
      - getLiveMarketSnapshot(tokenId: string)
      - getLiveUserSnapshot()
      - getTradingReadiness()
      - reconcileTradingState(reason: "startup" | "reconnect" | "manual")
  - 现有 getMarketQuote() 保留,但降级为 cold-start/fallback helper;live execution path 禁止直接依赖它

  ## Test Plan

  - 单元测试
      - fresh market + fresh user => 可交易
      - market stale => 拒绝新单
      - user stale => 拒绝新单
      - WS reconnect but reconcile 未完成 => 仍拒绝新单
      - reconcile 完成 => 恢复可交易
      - pauseOnStaleData=true 的策略会被系统暂停
      - cancelOpenOrdersOnStaleData=true 的策略只撤自身未完成挂单
  - 集成测试
      - 冷启动:HTTP 快照 -> WS 建连 -> reconcile -> readiness ready
      - market message 中断超过阈值 -> engine blocked
      - user message 中断超过阈值 -> manual order blocked
      - user WS 恢复 -> HTTP 对账 -> open orders / fills / positions 一致 -> unblock
      - Dashboard / Settings / Strategies 正确显示并保存新配置
  - 回归验证
      - Open Orders、History、Positions 展示逻辑不因 WS 改造而丢数据
      - dry-run 仍可运行,但 stale 时同样不应模拟 live-ready 状态
      - 生产模式 next build / next start 下,WS 模块仅在 Node runtime 初始化,不进入 edge graph

  ## Assumptions

  - 官方 WebSocket 事实边界采用 Polymarket 文档当前公开能力:
      - market endpoint: wss://ws-subscriptions-clob.polymarket.com/ws/market
      - user endpoint: wss://ws-subscriptions-clob.polymarket.com/ws/user
      - market 事件至少使用:book、price_change、tick_size_change、last_trade_price、best_bid_ask、market_resolved
      - user channel 使用官方 API credentials apiKey / secret / passphrase
  - positions 仍以 Data API 为只读库存视图,不把 user WS 直接当作库存真相源
  - 这次不做浏览器端直连 WebSocket;连接、重连、对账、闸门全部在服务端
  - 这次不实现“恢复后自动重新启用用户手工关闭的策略”;只恢复系统级交易闸门
  - 若某个 token 暂时没有 market WS fresh 数据,则宁可阻止 live 交易,也不降级成长期 HTTP 实时主链路

websocket页面呈现

  页面里当前的数据来源划分如下:

  - /settings
      - market data / user state / trading readiness:走服务端 WebSocket 监督器状态,加上 HTTP reconcile 结果
      - wallet mode / signatureType:走服务端运行时配置
  - /
      - “实时行情”卡片:优先走 market WebSocket 内存快照;没有快照时短时回退 HTTP snapshot
      - “策略状态 / 最近订单 / 最近成交”:本地 DB
  - /markets/[marketId]
      - MarketStats 和 Orderbook:优先走 market WebSocket;没有时回退 CLOB HTTP
      - 手工下单表单本身不是 WS,但下单前会走 freshness gate
  - /orders
      - Positions:Data API + market/event 过滤逻辑,不是 WS
      - Open Orders / History:页面读取仍主要是 CLOB HTTP + local DB
      - 但 user WebSocket 会在后台实时更新本地 Order / Fill,所以这些列表现在是“间接受益于 user WS”的
  - /risk
      - data freshness gate:服务端 WS/readiness 状态
      - 其他风控参数:local DB
  - /strategies
      - 策略配置和策略列表:local DB
      - live 执行时行情读取已经改成依赖 WS fresh 数据,不再把 HTTP quote 当长期实时主链路

  一句话总结:

  - 页面里真正“直接吃 WebSocket 状态”的,主要是 /settings、Dashboard 实时行情、Market 详情盘口
  - 订单/成交页目前还是“HTTP/DB 展示为主”,但其底层状态会被 user WebSocket 持续修正

策略相关

策略模型

  1. 单市场策略
  2. 扫描策略
  3. 多市场策略
  4. 组合策略

双边策略字段+策略解耦方法

  价格区间参数:

  - entryLow / entryHigh
      - 入场区间。中间价 midPrice 落在这个区间内时,策略才考虑挂买单。
      - 代码里 evaluateRangeEntry() 明确要求 midPrice 在 [entryLow, entryHigh] 内,见 range-quoting.ts。
  - exitLow / exitHigh
      - 出场区间。你已经有库存时,中间价进入这个区间才考虑挂卖单。
      - 这是“低买高卖”的第二段。

  下单与库存控制:

  - orderSize
      - 每次信号触发时希望下的单量。
      - 真正下单量还会再被全局 maxOrderSize 截断,代码是 Math.min(params.orderSize, Number(strategy.maxOrderSize)),见 range-
        engine.ts。
  - maxInventoryPerSide
      - 单个 outcome token 的最大库存。比如 YES 这边最多囤多少。
      - 当前库存达到这个值后,不再继续做 RANGE_ENTRY。
  - maxInventoryPerMarket
      - 设计上是整个 market 的库存上限。
      - 现在 schema 和表单里有这个参数,但当前执行逻辑里还没真正用它做硬拦截,这是个“已落库、未完全执行”的参数。
  - maxOpenOrdersPerSide
      - 每个 token 每个方向允许同时挂着多少未完成订单。
      - 例如某个 token 已经有 2 个 BUY 挂单,就不再继续补新的 BUY。

  市场筛选参数:

  - maxMarketsTracked
      - 给市场扫描器用的,表示最多追踪/评分多少候选 market。
      - 主要影响双边策略的“选市场”能力,而不是单市场执行本身,见 market-scanner.ts。
  - minLiquidity
      - 市场最低流动性门槛,不够就不纳入候选。
  - minVolume24h
      - 24 小时成交量门槛。
  - minBookDepth
      - 盘口深度门槛,扫描器会看 top levels depth。
  - rangeMaxSpread
      - 你在表单里看到的是这个名字,但真正存进 triggerParams 后映射成 maxSpread。
      - 作用是限制盘口不能太宽,太宽就不进场。
  - minTimeToExpiryMinutes
      - 距离结束太近的市场不做,避免临近结算时流动性和风险结构恶化。

  运行与刷新参数:

  - quoteRefreshSeconds
      - 设计上是报价刷新节奏。
      - 在现在这版 WS 主链路下,这个参数更多是保留给扫描/执行节奏控制,不是直接驱动实时行情来源。
  - staleQuoteSeconds
      - 用来判定“挂单挂太久”时是否应撤掉,当前用于 cancelStaleOrders()。
  - scanIntervalSeconds
      - 市场扫描周期,用于候选市场重新评分。

  过滤器:

  - trendFilterEnabled
      - 是否启用趋势过滤。
  - trendFilterThreshold
      - 趋势过滤阈值。
  - allowBothSidesInventory
      - 是否允许同一 market 同时持有两边库存。
      - 这是双边策略里很关键的模式参数,但当前执行层还没有把它完全展开成完整的持仓互斥规则,属于“已建模,待继续强化”。

  再说数据库设计。核心模型在 schema.prisma。

  Strategy 这张表的设计是“公共骨架 + 策略私有 JSON 参数”:

  - 公共字段:
      - id
      - name
      - type
      - marketId
      - tokenId
      - side
      - maxOrderSize
      - maxDailyTradeCount
      - cooldownSeconds
      - pauseOnStaleData
      - cancelOpenOrdersOnStaleData
      - dryRun
      - enabled
  - 私有字段:
      - triggerParams Json

  这个 triggerParams 是解耦的关键。不同策略类型把各自参数塞进同一个 JSON 字段里:

  - THRESHOLD_BREAKOUT 存 { threshold, comparator }
  - ORDERBOOK_IMBALANCE 存 { maxSpread, minTopDepth, imbalanceRatio }
  - TWO_SIDED_RANGE_QUOTING 存一整套 range 参数

  然后在服务端按 type 分发并校验:

  - 创建时在 actions.ts 根据 type 选择对应 zod schema
  - 执行时在 engine.ts 或 range-engine.ts 按 type 分发
  - 具体规则里再次用 zod parse 一次,确保运行时参数形状正确

  前端表单的解耦是另一层:

  - 配置定义集中在 form-config.ts
  - 渲染器在 strategy-form.tsx

  它的结构是:

  - commonStrategySections
      - 所有策略共享的字段
  - strategyFormDefinitions[type]
      - 每种策略自己的字段分组
  - 页面只渲染一个通用表单组件,根据 type 选配置

  所以以后新增一个策略,通常只要补三处:

  1. Prisma StrategyType 枚举里加新类型
  2. 在 form-config.ts 增加该类型的字段定义
  3. 在 actions.ts 增加该类型的 triggerParams 解析
  4. 在执行层加对应 rule/engine

  这套设计的优点:

  - 表单不跟具体策略 JSX 强耦合
  - DB 不需要每加一种策略就加一堆 nullable 列
  - 服务端校验边界清晰,type -> schema -> engine 是一条线

  代价也很明确:

  - triggerParams 是 JSON,不适合做复杂 SQL 查询
  - 有些参数会出现“已落库但暂未完全执行”的情况,需要靠 schema 和 engine 一起维护一致性

策略页面分类设计方案

 PM Trader 是基于 Polymarket 的策略交易平台。当前系统有 3 种策略类型和 2 种
 scope,但随着策略丰富,需要一个清晰的分类框架来组织页面设计。目标是按策略与市场的关系分为 4
 类,提前规划好数据模型和页面结构,方便后续逐步实现。

 四类策略框架

 1. 单市场策略 (SINGLE_MARKET)

 - 关系: 绑定一个特定市场 + token
 - Scope: STATIC_MARKET (marketId + tokenId)
 - 现有: THRESHOLD_BREAKOUT, ORDERBOOK_IMBALANCE
 - 可扩展:
   - MOMENTUM_SCALP: 价格在 Y 分钟内变动 X%,顺势跟入,反转退出。参数: priceChangePct, lookbackMinutes, exitReversePct
   - MEAN_REVERSION: 价格偏离 N 小时均值超过 Z 个标准差,做反向。参数: stdDevThreshold, lookbackHours

 2. 扫描策略 (SCANNER)

 - 关系: 自动发现符合条件的市场
 - Scope: DISCOVERY_QUERY (过滤条件: 流动性、交易量、深度、价差等)
 - 现有: TWO_SIDED_RANGE_QUOTING
 - 可扩展:
   - EXPIRY_DECAY_SWEEP: 扫描临近到期 (<24h) 且价格极端 (>0.9 或 <0.1) 的市场,利用时间衰减。参数: maxMinutesToExpiry,
 priceExtremeThreshold
   - LIQUIDITY_VACUUM: 扫描流动性骤降(做市商撤离)导致价差扩大的市场。参数: liquidityDropPct, minSpread

 3. 多市场策略 (MULTI_MARKET)

 - 关系: 同时绑定 2+ 个关联市场
 - Scope: MULTI_MARKET (新增,通过 StrategyMarketBinding 表存储市场腿)
 - 可实现:
   - CORRELATION_ARB: 两个相关市场 (如 "3月前发生" vs "6月前发生") 价差超过阈值时套利。参数: spreadThreshold, exitSpread
   - YES_NO_MISPRICING: 同一市场 YES+NO 价格偏离 1.00 时套利。参数: mispricingThreshold, feeRate
   - EVENT_SPREAD: 同一事件下多个市场 (如候选人A vs 候选人B) 概率之和应为1,偏离时交易。参数: expectedSum, deviationThreshold

 4. 组合策略 (PORTFOLIO)

 - 关系: 基于整体持仓状态,不绑定特定市场
 - Scope: PORTFOLIO (新增,读取 portfolio 状态)
 - 可实现:
   - EXPOSURE_REBALANCE: 总敞口或单市场敞口超限时自动减仓。参数: maxTotalExposure, maxPerMarketExposure, reductionPct
   - DELTA_HEDGE: 维持总持仓方向中性,自动对冲偏移。参数: maxNetDelta, hedgeCheckIntervalMinutes

 ---
 数据模型变更

 Prisma Schema

 1. 扩展 StrategyType 枚举 (按需逐步添加):
 THRESHOLD_BREAKOUT / ORDERBOOK_IMBALANCE / MOMENTUM_SCALP / MEAN_REVERSION
 TWO_SIDED_RANGE_QUOTING / EXPIRY_DECAY_SWEEP / LIQUIDITY_VACUUM
 CORRELATION_ARB / YES_NO_MISPRICING / EVENT_SPREAD
 EXPOSURE_REBALANCE / DELTA_HEDGE

 2. 扩展 StrategyScopeType 枚举:
 STATIC_MARKET / DISCOVERY_QUERY / MULTI_MARKET / PORTFOLIO

 3. 新增 StrategyMarketBinding 模型 (多市场策略用):
 model StrategyMarketBinding {
   id         String   @id @default(cuid())
   strategyId String
   strategy   Strategy @relation(fields: [strategyId], references: [id], onDelete: Cascade)
   role       String   // "LEG_A", "LEG_B", "HEDGE"
   marketId   String
   tokenId    String?
   weight     Decimal? @db.Decimal(10, 4)
   meta       Json?
   createdAt  DateTime @default(now())
   @@index([strategyId])
   @@unique([strategyId, role])
 }

 4. Strategy 模型添加 relation: marketBindings StrategyMarketBinding[]

 分类系统 (TypeScript 层,非 Prisma)

 在 src/lib/strategy/categories.ts 中定义 STRATEGY_TYPE_TO_CATEGORY 映射,从 StrategyType 派生 category,不在数据库中冗余存储。

 ---
 页面设计

 路由结构

 /strategies              -> 策略列表 (分类 Tab 视图)
 /strategies/new          -> 新建策略 (独立页面)
 /strategies/[strategyId] -> 策略详情/编辑

 策略列表页 (/strategies)

 PageHeader: "策略管理" + [新建策略] 按钮

 [StatCard 行: 策略总数 | 启用中 | Dry-run | Live运行]

 [分类 Tab 栏] (URL search param: ?category=SINGLE_MARKET)
 ┌──────────┬──────────┬──────────┬──────────┐
 │ 单市场(2) │ 扫描(1)  │ 多市场(0) │ 组合(0)  │
 └──────────┴──────────┴──────────┴──────────┘

 [当前分类的策略卡片列表]

 [底部: 最近信号 | 最近引擎运行 (跨分类共享)]

 新建策略页 (/strategies/new)

 多步骤表单 (单页垂直展开,非向导):
 1. 选分类: 4 张大卡片,每张显示分类名 + 描述 + icon
 2. 选策略类型: 该分类下可选的策略类型
 3. 配置 Scope: 根据分类不同展示不同字段
   - 单市场: 市场 ID + Token ID
   - 扫描: 过滤条件 (流动性、交易量等)
   - 多市场: 可重复的"添加市场腿"编辑器 (角色 + marketId + tokenId)
   - 组合: 组合级参数 (敞口限额、再平衡间隔)
 4. 配置触发参数: 策略类型特定的参数字段
 5. 通用参数: 名称、dry-run、启用、冷却时间等

 策略卡片设计

 ┌────────────────────────────────────────────────┐
 │ [分类Badge] 策略名称              [enabled] [dry-run] │
 │ THRESHOLD_BREAKOUT · BUY · STATIC_MARKET              │
 │ market: 0x1234...5678                                  │
 │ 上次运行: 2分钟前 · 今日信号: 3                          │
 │ [编辑] [启停] [删除]                                    │
 └────────────────────────────────────────────────┘

 多市场策略额外显示: 腿: LEG_A: 0x1234 · LEG_B: 0x9abc
 组合策略额外显示: 管理 5 个持仓 · 敞口 $432.50

 ---
 引擎架构变更

 策略执行器注册表

 将 engine.ts 的 type-specific dispatch 改为注册表模式:

 const STRATEGY_EXECUTORS: Record<StrategyType, Executor> = {
   THRESHOLD_BREAKOUT: executeStaticMarketStrategy,
   ORDERBOOK_IMBALANCE: executeStaticMarketStrategy,
   TWO_SIDED_RANGE_QUOTING: executeRangeQuotingStrategy,
   // 新策略类型注册到对应 executor
 };

 新增引擎模块

 src/lib/strategy/engines/static-market.ts   -- 从 engine.ts 重构
 src/lib/strategy/engines/scanner.ts         -- 从 range-engine.ts 泛化
 src/lib/strategy/engines/multi-market.ts    -- 新建
 src/lib/strategy/engines/portfolio.ts       -- 新建

 ---
 关键文件

 ┌─────────────────────────────────────────────────┬─────────────────────────────────────────┐
 │                      文件                       │                  变更                   │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ prisma/schema.prisma                            │ 新枚举值、StrategyMarketBinding 模型    │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/lib/strategy/categories.ts                  │ 新建 - 分类元数据 + type→category 映射  │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/lib/strategy/config.ts                      │ 新 scope 解析 (MULTI_MARKET, PORTFOLIO) │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/lib/strategy/types.ts                       │ 新策略类型参数 schema                   │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/lib/strategy/form-config.ts                 │ 新策略类型表单定义                      │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/lib/strategy/engine.ts                      │ 注册表式 dispatch                       │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/app/strategies/page.tsx                     │ 分类 Tab 布局                           │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/app/strategies/new/page.tsx                 │ 新建 - 多步骤创建表单                   │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/app/strategies/[strategyId]/page.tsx        │ 新建 - 详情/编辑页                      │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/components/strategies/category-tabs.tsx     │ 新建 - Tab 栏组件                       │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/components/strategies/strategy-card.tsx     │ 新建 - 策略卡片                         │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/components/strategies/market-leg-editor.tsx │ 新建 - 多市场腿编辑器                   │
 ├─────────────────────────────────────────────────┼─────────────────────────────────────────┤
 │ src/app/actions.ts                              │ 新 action + 重构创建逻辑                │
 └─────────────────────────────────────────────────┴─────────────────────────────────────────┘

 ---
 实施顺序

 1. Phase 1 - 基础: Prisma 变更 + categories.ts + config.ts scope 扩展
 2. Phase 2 - UI 重构: Tab 布局 + 策略卡片 + 独立创建/详情页
 3. Phase 3 - 表单: 多步骤表单 + 多市场腿编辑器 + PORTFOLIO scope UI
 4. Phase 4 - 新策略: 逐步添加每个分类的新策略类型 (规则 + 引擎 + 表单)
 5. Phase 5 - 组合集成: Portfolio 状态读取 + 组合策略引擎

 ---
 验证方式

 - 每个 Phase 完成后验证:
   - Phase 1: npx prisma migrate dev 成功 + categories.ts 类型检查通过
   - Phase 2: 页面正常渲染 4 个 Tab,现有策略正确归类到"单市场"和"扫描"
   - Phase 3: 能通过新表单创建各类策略并保存到数据库
   - Phase 4: 新策略类型的规则评估函数有单元测试
   - Phase 5: 组合策略能读取持仓并生成信号