docs: 尾盘策略文档归集至 docs/crypto-tail-strategy/,前端链接同步
- 将尾盘策略相关文档移至 docs/crypto-tail-strategy/{zh,en}/
- 新增 docs/crypto-tail-strategy/README.md 目录说明
- 前端配置指南链接改为 docs/crypto-tail-strategy/${lang}/crypto-tail-strategy-user-guide.md
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,204 +0,0 @@
|
||||
# 加密市场尾盘策略 - 流程图
|
||||
|
||||
## 一、整体架构
|
||||
|
||||
```
|
||||
┌─────────────────┐ POST 创建/更新 ┌──────────────────────────┐
|
||||
│ 前端 / API │ ──────────────────────►│ CryptoTailStrategyController│
|
||||
└─────────────────┘ └──────────────┬─────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ CryptoTailStrategyService │
|
||||
│ create / update │
|
||||
│ save → publishEvent │
|
||||
└──────────────┬─────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────┼─────────────────────────────────────────┐
|
||||
│ CryptoTailStrategyChangedEvent │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐
|
||||
│ CryptoTailStrategyScheduler │ │ CryptoTailOrderbookWsService │ │ (其他监听方,如有) │
|
||||
│ @EventListener │ │ @EventListener │ └──────────────────────────────┘
|
||||
│ → runCycle() 一次(补充) │ │ → refreshAndSubscribe() │
|
||||
└──────────────┬───────────────┘ └──────────────┬───────────────┘
|
||||
│ │
|
||||
▼ │
|
||||
┌──────────────────────────────┐ │
|
||||
│ CryptoTailStrategyExecution │ │ 每 25 秒 + 事件时
|
||||
│ runCycle() │ │ refreshAndSubscribe()
|
||||
│ (HTTP 拉订单簿,满足则下单) │ ▼
|
||||
└──────────────────────────────┘ ┌──────────────────────────────┐
|
||||
│ CLOB Market WebSocket │
|
||||
│ wss://.../ws/market │
|
||||
│ subscribe assets_ids │
|
||||
└──────────────┬───────────────┘
|
||||
│ book / price_change
|
||||
▼
|
||||
┌──────────────────────────────┐
|
||||
│ onBestBid(tokenId, bestBid) │
|
||||
│ → tryTriggerWithPriceFromWs │
|
||||
└──────────────┬───────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────┐
|
||||
│ CryptoTailStrategyExecution │
|
||||
│ placeOrderForTrigger │
|
||||
│ → CLOB 下单 + 写触发记录 │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、策略创建/更新流程(API → 事件)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant API as Controller
|
||||
participant Svc as CryptoTailStrategyService
|
||||
participant DB as DB
|
||||
participant Event as ApplicationEventPublisher
|
||||
|
||||
API->>Svc: create(request) / update(request)
|
||||
Svc->>Svc: 参数校验(账户、窗口、价格、金额模式等)
|
||||
Svc->>DB: save(entity)
|
||||
Svc->>Event: publishEvent(CryptoTailStrategyChangedEvent)
|
||||
Svc->>API: Result.success(dto)
|
||||
```
|
||||
|
||||
- **创建**:校验通过后落库,发布 `CryptoTailStrategyChangedEvent`,返回 DTO。
|
||||
- **更新**:同上,更新实体后发布同一事件。
|
||||
- **删除**:不发布事件(策略已移除,WS 下次刷新订阅时会自然不再包含该策略)。
|
||||
|
||||
---
|
||||
|
||||
## 三、策略变更后:双路响应
|
||||
|
||||
事件发出后,两个监听方并行执行,互不阻塞:
|
||||
|
||||
| 监听方 | 动作 | 说明 |
|
||||
|--------|------|------|
|
||||
| **CryptoTailStrategyScheduler** | `onStrategyChanged` → `runCycle()` 一次 | 用 HTTP 拉订单簿做一轮检查,作为 WS 未就绪时的补充。 |
|
||||
| **CryptoTailOrderbookWsService** | `onStrategyChanged` → `refreshAndSubscribe()` | 按当前启用策略重新算 token 列表,向 WS 发送新的 `assets_ids` 订阅。 |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph 事件
|
||||
E[CryptoTailStrategyChangedEvent]
|
||||
end
|
||||
subgraph 调度器
|
||||
S[Scheduler.onStrategyChanged]
|
||||
R[executionService.runCycle]
|
||||
S --> R
|
||||
end
|
||||
subgraph WS服务
|
||||
W[OrderbookWsService.onStrategyChanged]
|
||||
Ref[refreshAndSubscribe]
|
||||
W --> Ref
|
||||
end
|
||||
E --> S
|
||||
E --> W
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、WebSocket 订单簿监听流程(主路径)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph 启动与连接
|
||||
A[PostConstruct init] --> B[connect]
|
||||
B --> C[OkHttp WebSocket 连接 wss://.../ws/market]
|
||||
C --> D[onOpen: refreshAndSubscribe]
|
||||
end
|
||||
|
||||
subgraph 订阅维护
|
||||
D --> E[buildSubscriptionMap]
|
||||
E --> F[遍历 enabled 策略]
|
||||
F --> G[当前周期 periodStartUnix]
|
||||
G --> H[slug = prefix-periodStartUnix]
|
||||
H --> I[Gamma getEventBySlug]
|
||||
I --> J[得到 tokenIds]
|
||||
J --> K[tokenId → List of WsBookEntry]
|
||||
K --> L[发送 type=MARKET, assets_ids=[...]]
|
||||
T[每 25 秒 @Scheduled] --> E
|
||||
EV[onStrategyChanged] --> E
|
||||
end
|
||||
|
||||
subgraph 收消息与触发
|
||||
M[onMessage: book / price_change]
|
||||
M --> N[解析 asset_id, best_bid]
|
||||
N --> O[onBestBid tokenId, bestBid]
|
||||
O --> P[查 tokenToEntries 得到策略列表]
|
||||
P --> Q[筛时间窗内]
|
||||
Q --> R[scope.launch tryTriggerWithPriceFromWs]
|
||||
R --> S[placeOrderForTrigger]
|
||||
end
|
||||
|
||||
L --> M
|
||||
```
|
||||
|
||||
- **buildSubscriptionMap**:只包含「当前时间仍在窗口内」的策略(`nowSeconds < windowEnd`),并只订阅这些策略对应周期的 token。
|
||||
- **onBestBid**:再按当前时间过滤一次时间窗,对每个命中策略在协程里调用 `tryTriggerWithPriceFromWs`,内部会查「本周期是否已触发」和价格区间,通过则 `placeOrderForTrigger`。
|
||||
|
||||
---
|
||||
|
||||
## 五、执行层:下单条件与顺序(ExecutionService)
|
||||
|
||||
无论来自 **runCycle(HTTP)** 还是 **tryTriggerWithPriceFromWs(WS)**,最终都走同一套下单逻辑。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph runCycle 入口
|
||||
A[runCycle] --> B[findAllByEnabledTrue]
|
||||
B --> C[processStrategy 每个策略]
|
||||
C --> D[在时间窗? 本周期已触发?]
|
||||
D --> E[Gamma getEventBySlug]
|
||||
E --> F[HTTP getOrderbook 两个 token]
|
||||
F --> G[第一个 bestBid 在 minPrice~maxPrice?]
|
||||
G --> H[placeOrderForTrigger]
|
||||
end
|
||||
|
||||
subgraph tryTriggerWithPriceFromWs 入口
|
||||
I[WS onBestBid] --> J[tryTriggerWithPriceFromWs]
|
||||
J --> K[本周期已触发? bestBid 在区间?]
|
||||
K --> H
|
||||
end
|
||||
|
||||
subgraph placeOrderForTrigger 统一
|
||||
H --> L[账户、API 凭证]
|
||||
L --> M[余额、下单金额]
|
||||
M --> N[最优价、数量]
|
||||
N --> O[签名、CLOB 下单]
|
||||
O --> P[保存 CryptoTailStrategyTrigger]
|
||||
end
|
||||
```
|
||||
|
||||
- **每周期最多触发一次**:由 `triggerRepository.findByStrategyIdAndPeriodStartUnix` 保证。
|
||||
- **价格区间**:`minPrice ≤ bestBid ≤ maxPrice` 才触发。
|
||||
- **时间窗**:仅当 `windowStart ≤ now < windowEnd`(以当前周期的 `periodStartUnix` 为基准)才参与检查/下单。
|
||||
|
||||
---
|
||||
|
||||
## 六、关键数据流小结
|
||||
|
||||
| 阶段 | 输入 | 输出/动作 |
|
||||
|------|------|-----------|
|
||||
| 创建/更新策略 | API 请求体 | 落库 + 发布 `CryptoTailStrategyChangedEvent` |
|
||||
| 事件 → 调度器 | 事件 | 执行一次 `runCycle()`(HTTP 拉订单簿,满足则下单) |
|
||||
| 事件 → WS 服务 | 事件 | `refreshAndSubscribe()`,更新订阅的 `assets_ids` |
|
||||
| 定时刷新订阅 | 每 25 秒 | `refreshAndSubscribe()`,保证新周期、新策略被订阅 |
|
||||
| WS 收 book/price_change | asset_id, best_bid | `onBestBid` → 时间窗内策略 → `tryTriggerWithPriceFromWs` → 未触发且价格在区间则 `placeOrderForTrigger` |
|
||||
| placeOrderForTrigger | 策略、周期、token、outcome、价格 | 账户/余额/价格/签名 → CLOB 下单 → 写触发记录 |
|
||||
|
||||
---
|
||||
|
||||
## 七、涉及类与职责
|
||||
|
||||
| 类 | 职责 |
|
||||
|----|------|
|
||||
| **CryptoTailStrategyController** | 接收 list/create/update/delete/triggers/marketOptions 的 POST。 |
|
||||
| **CryptoTailStrategyService** | 策略 CRUD、校验、发布 `CryptoTailStrategyChangedEvent`。 |
|
||||
| **CryptoTailStrategyScheduler** | 监听策略变更事件,执行一次 `runCycle()`。 |
|
||||
| **CryptoTailOrderbookWsService** | 连接 CLOB Market WS、维护订阅(事件 + 每 25 秒)、处理 book/price_change、调用 `tryTriggerWithPriceFromWs`。 |
|
||||
| **CryptoTailStrategyExecutionService** | `runCycle()`(HTTP 路径)、`tryTriggerWithPriceFromWs()`(WS 路径)、`placeOrderForTrigger()`(统一下单与写触发记录)。 |
|
||||
@@ -1,178 +0,0 @@
|
||||
# 加密市场尾盘策略 - 5/15 分钟市场数据获取说明
|
||||
|
||||
> 前端 UI 与交互详见 `crypto-tail-strategy-ui-spec.md`。
|
||||
|
||||
## 1. 数据源
|
||||
|
||||
- **Gamma API**:`https://gamma-api.polymarket.com`
|
||||
- 用于获取市场元数据:conditionId、开始/结束时间、标题、clobTokenIds 等。
|
||||
- 无需鉴权。
|
||||
|
||||
## 2. 市场类型与 Slug 规则
|
||||
|
||||
| 类型 | Event Slug 规则 | 周期长度 | 说明 |
|
||||
|------|-----------------|----------|------|
|
||||
| Bitcoin 5 分钟 | `btc-updown-5m-{periodStartUnix}` | 5 min | periodStartUnix 为 5 分钟边界的 Unix 时间戳(秒) |
|
||||
| Bitcoin 15 分钟 | `btc-updown-15m-{periodStartUnix}` | 15 min | periodStartUnix 为 15 分钟边界:`(now // 900) * 900` |
|
||||
| Ethereum 5 分钟 | `eth-updown-5m-{ts}` | 5 min | 暂未验证是否在平台上线;如有可按相同规则推导 |
|
||||
| Ethereum 15 分钟 | `eth-updown-15m-{ts}` | 15 min | 已验证存在 |
|
||||
|
||||
- 5 分钟周期:按 **300 秒** 对齐;当前周期起点可用 `(nowUnix // 300) * 300`,下一周期为 `+300`。
|
||||
- 15 分钟周期:按 **900 秒** 对齐;当前周期起点可用 `(nowUnix // 900) * 900`。slug 中的时间戳即为周期起始 Unix 秒;周期结束以 API 的 endDate 为准。
|
||||
|
||||
## 3. 获取单个周期市场(开始时间、结束时间)
|
||||
|
||||
### 3.1 请求
|
||||
|
||||
```bash
|
||||
# 5 分钟 - 当前周期(示例时间戳需替换为当前周期起点)
|
||||
curl -s "https://gamma-api.polymarket.com/events/slug/btc-updown-5m-1771007100"
|
||||
|
||||
# 15 分钟 - 需使用实际存在的时间戳(可从前端或历史 slug 得知)
|
||||
curl -s "https://gamma-api.polymarket.com/events/slug/btc-updown-15m-1770882300"
|
||||
```
|
||||
|
||||
### 3.2 响应结构(与开始/结束时间相关)
|
||||
|
||||
- **Event 层**:`startDate`、`endDate`(ISO 8601)。
|
||||
- **markets[]**:每个市场有 `conditionId`、`question`、`startDate`、`endDate`、`clobTokenIds` 等。
|
||||
|
||||
**周期本身**:例如 5 分钟市场 "1:30PM-1:35PM ET",理应是 **startDate = 1:30 PM**、**endDate = 1:35 PM**。
|
||||
|
||||
**API 返回值与周期起止的对应关系(已用脚本验证)**:
|
||||
|
||||
| 字段 | 是否等于周期起止 | 说明 |
|
||||
|------|------------------|------|
|
||||
| **endDate**(Event / Market) | **是**,等于周期结束时间(如 1:35 PM) | API 的 endDate 即周期终点,可直接用。 |
|
||||
| **startDate**(Event / Market) | **否**,不等于周期开始时间(1:30 PM) | API 的 startDate 是市场创建/开放时间,不是周期起点,故**不能**当 1:30 PM 用。 |
|
||||
|
||||
**正确做法**:周期起点(1:30 PM)用 **slug 中的时间戳** 推导;周期终点(1:35 PM)用 API 的 **endDate**。
|
||||
|
||||
- **5 分钟**:周期开始 = `slug_ts`(即 slug 中的 Unix 秒),周期结束 = `endDate`(或 `slug_ts + 300`)。
|
||||
- **15 分钟**:周期开始 = `slug_ts`,周期结束 = `endDate`(或 `slug_ts + 900`)。
|
||||
|
||||
**示例(脚本输出解读)**:若 current 5m slug 为 `btc-updown-5m-1771007400`、title 为 "1:30PM-1:35PM ET"、endDate 为 `2026-02-13T18:35:00Z`,则 1771007400 = 18:30 UTC = 1:30 PM ET,即周期起点;endDate 18:35 UTC = 1:35 PM ET = 周期终点。next 5m slug 为 1771007700 = 1771007400 + 300,即下一周期起点。15m 同理:current slug 1771007400(1:30–1:45 PM ET),next 1771008300 = 1771007400 + 900(1:45–2:00 PM ET)。
|
||||
|
||||
## 4. 如何列出“当前及未来”5/15 分钟市场
|
||||
|
||||
- Gamma 未提供按“5 分钟 / 15 分钟”或“Up or Down”的 tag 筛选;`tag_id=744`(cryptocurrency)未返回这些短期市场。
|
||||
- **可行方式**:
|
||||
1. **按周期时间戳生成 slug 并逐个请求**
|
||||
- 5 分钟:当前周期 `ts = (nowUnix // 300) * 300`,下一周期 `ts + 300`,再下一周期 `ts + 600` …
|
||||
- 15 分钟:`ts = (nowUnix // 900) * 900`,然后 `ts + 900`、`ts + 1800` …
|
||||
- 请求 `GET /events/slug/btc-updown-5m-{ts}` 或 `btc-updown-15m-{ts}`;若返回 404 表示该周期尚未创建或已过期,可跳过。
|
||||
2. **用户选择“市场”时**:若前端/后端已知“系列”(如 Bitcoin 5 minute),则只需约定 slug 前缀(`btc-updown-5m`、`btc-updown-15m`)与周期长度(300/900),按当前时间计算周期起点并请求对应 slug 即可得到当前周期的 conditionId、startDate、endDate;下一周期同理。
|
||||
|
||||
## 5. 周期边界与“每周期监听”
|
||||
|
||||
- **周期开始**:使用 **slug 中的时间戳** `periodStartUnix`(即请求 slug 时的 `btc-updown-5m-{ts}` 里的 `ts`),不要用 API 返回的 startDate。
|
||||
- **周期结束**:使用 API 返回的 **event.endDate 或 market.endDate**(与 slug_ts + 300/900 一致)。
|
||||
- 判断“当前是否在该周期内”:`periodStartUnix <= nowUnix < endDateUnix`,其中 `periodStartUnix` 从 slug 得到,`endDateUnix` 由 endDate 解析。
|
||||
- 策略“每周期开始时开始监听”:当 `now` 跨过当前周期的 endDate(或下一周期的 periodStartUnix)时,视为新周期开始,重置“本周期是否已触发”等状态。
|
||||
|
||||
## 6. 如何保证每个周期的市场都能正确处理
|
||||
|
||||
### 6.1 用“当前时间”唯一确定当前周期
|
||||
|
||||
- 服务端只用**当前 Unix 时间**推导周期,不依赖 API 的 startDate。
|
||||
- **5 分钟**:`periodStartUnix = (nowUnix / 300) * 300`(整除)。
|
||||
- **15 分钟**:`periodStartUnix = (nowUnix / 900) * 900`。
|
||||
- 同一时刻算出的 `periodStartUnix` 唯一,对应唯一 slug(如 `btc-updown-5m-{periodStartUnix}`),从而对应唯一市场(conditionId、tokenIds、endDate)。
|
||||
|
||||
### 6.2 按周期拉取市场并切换
|
||||
|
||||
- **首次进入或策略启用**:用当前的 `periodStartUnix` 拼 slug,请求 Gamma `GET /events/slug/{slug}`,拿到该周期的 conditionId、endDate、clobTokenIds;用 endDate 解析得到 `endDateUnix`。
|
||||
- **每次需要判断“是否还在本周期”或“是否该下单”时**:先算当前 `currentPeriodStart = (nowUnix / interval) * interval`(interval 为 300 或 900)。若 `currentPeriodStart` 大于上一笔使用的 `periodStartUnix`,说明已进入**下一周期**:
|
||||
- 用新的 `currentPeriodStart` 拼 slug,重新请求 Gamma,拿到**新周期**的 conditionId、endDate、clobTokenIds;
|
||||
- 用新周期的 tokenIds 订阅/拉取订单簿,用新 endDate 作为本周期结束时间;
|
||||
- 重置本周期“是否已触发”等状态,避免把上一周期的状态带到新周期。
|
||||
- **周期内**:始终用**本周期**的 conditionId、tokenIds、endDate 做价格监听与下单,不要混用上一周期的数据。
|
||||
|
||||
### 6.3 周期切换时机与 404 处理
|
||||
|
||||
- **切换时机**:以 `nowUnix >= endDateUnix` 或 `(nowUnix / interval) * interval > periodStartUnix` 作为“本周期已结束”,立刻按 6.2 用新 `periodStartUnix` 拉新周期市场。
|
||||
- **新周期市场尚未创建(404)**:Gamma 可能稍晚才创建下一周期 event。若请求 slug 返回 404,可短间隔重试(如 5–15 秒)或等到下一整点/对齐点再试;重试时仍用**同一** `periodStartUnix`,避免用错周期。若长时间 404,可记录日志并跳过该周期,下一周期再正常拉取。
|
||||
|
||||
### 6.4 下单失败重试规则(每周期最多下单一次)
|
||||
|
||||
- 市价单提交失败时,**最多重试 2 次**(即 1 次初始 + 2 次重试,共 3 次尝试)。
|
||||
- 若 3 次均失败:
|
||||
- 本周期**不再**对该 outcome 下单;
|
||||
- 记录失败原因与状态(便于审计与前端展示触发记录)。
|
||||
- 周期切换时(6.2)重置为“未下单”,仅对新周期做新的判断与尝试。
|
||||
|
||||
### 6.5 去重与幂等(每周期最多触发一次)
|
||||
|
||||
- 以「策略 + 周期」唯一标识一次执行,例如 `(strategyId, periodStartUnix)` 或 `(accountId, slugPrefix, periodStartUnix)`。
|
||||
- 在数据库或内存中记录:本周期是否已触发、是否已下单。若已触发,同一周期内不再根据价格区间下单。
|
||||
- 周期切换时(6.2)清空或更新为“新周期未触发”,只对新周期的 conditionId/tokenIds 做监听与下单。
|
||||
|
||||
### 6.6 时间区间(窗口)内才触发
|
||||
|
||||
- 策略可配置**时间区间**:从周期起点起算的「开始秒数」与「结束秒数」,例如 5 分钟市场可选 0~300 秒内的一段,15 分钟市场可选 0~900 秒内的一段(对应前端“分+秒”下拉,如 3 分 0 秒~12 分 0 秒即 180~720 秒)。
|
||||
- **执行规则**:仅当 `periodStartUnix + windowStartSeconds <= nowUnix < periodStartUnix + windowEndSeconds` 时,才根据 7.1 判断价格是否进入 [minPrice, maxPrice] 并执行下单;**区间外不进行价格判断与下单**。
|
||||
- 存储:策略表(或配置)中保存 `windowStartSeconds`、`windowEndSeconds`(整数,单位秒);校验:`windowStartSeconds <= windowEndSeconds`,且不超过周期长度(5min 市场 ≤ 300,15min 市场 ≤ 900)。详见 [UI 规格 - 时间区间](crypto-tail-strategy-ui-spec.md)。
|
||||
|
||||
### 6.7 小结
|
||||
|
||||
| 要点 | 做法 |
|
||||
|------|------|
|
||||
| 周期唯一性 | 用 `(nowUnix / interval) * interval` 得到 periodStartUnix,再拼 slug,不依赖 API startDate。 |
|
||||
| 周期数据 | 每周期用**该周期**的 slug 请求 Gamma,使用返回的 conditionId、endDate、clobTokenIds。 |
|
||||
| 切换 | 当 `nowUnix >= endDateUnix` 或当前算出的 periodStartUnix 变化时,拉取新周期并重置状态。 |
|
||||
| 404 | 同一 periodStartUnix 重试;长时间 404 可跳过该周期并打日志。 |
|
||||
| 下单失败 | 失败后最多重试 2 次;仍失败则本周期不再下单并记录状态。 |
|
||||
| 每周期只触发一次 | 用 (策略, periodStartUnix) 做去重,周期切换时重置“已触发”状态。 |
|
||||
| 时间区间 | 仅当 periodStartUnix + windowStartSeconds ≤ now < periodStartUnix + windowEndSeconds 时做价格判断与下单;区间外不处理。 |
|
||||
|
||||
按上述方式,每个周期都会对应到正确的 slug、正确的市场与 endDate,并在周期结束时切换到下一周期;仅在配置的时间窗口内才根据价格触发下单,避免混周期或漏周期。
|
||||
|
||||
## 7. 与订单簿 / 价格的关系
|
||||
|
||||
- 价格由 **CLOB 订单簿**(或 WebSocket)获取,不依赖 Gamma;Gamma 仅提供市场元数据。
|
||||
- 使用 market.conditionId 与 markets[].clobTokenIds 解析出 tokenId,再订阅或请求该 token 的订单簿即可得到实时价格,用于区间判断与市价下单。
|
||||
|
||||
### 7.1 价格区间与「反方向」判断(如 minPrice = 0.92)
|
||||
|
||||
二元市场(Up or Down)有两个 outcome:通常 outcomeIndex 0 = Up,1 = Down,各对应一个 tokenId 和订单簿。
|
||||
|
||||
- **配置含义**:用户配置 minPrice = 0.92(及可选 maxPrice,默认 1)表示「当**某个 outcome 的价格**落在 [0.92, 1] 时触发市价买入**该** outcome」。
|
||||
- **不预先选方向**:不需要用户选「买 Up 还是买 Down」;谁的价格先进入区间就买谁。
|
||||
- **订单簿取价方式(与现有市价单逻辑一致)**:
|
||||
- 对每个 outcome,取该 tokenId 订单簿的 **bestBid**(最高买入价)作为当前价格用于区间判断;若取价规则与现有市价买入逻辑不同,请以系统现有规则为准并在实现文档中写明。
|
||||
- **判断方式**:
|
||||
- 同时取**两个 outcome** 的当前价格(按上述取价规则)。
|
||||
- 对 **outcome 0**:若 `price0 >= minPrice && price0 <= maxPrice` → 满足触发条件,买入 outcome 0(Up)。
|
||||
- 对 **outcome 1**:若 `price1 >= minPrice && price1 <= maxPrice` → 满足触发条件,买入 outcome 1(Down)。
|
||||
- **反方向**:「反方向」即另一个 outcome。例如若本轮已因 outcome 0 进入 [0.92, 1] 而买入 Up,则本周期内**不再**检查 outcome 1 是否也进入区间、也不再买 Down;反之若先触发的是 outcome 1(Down),则本周期不再买 Up。实现上:一旦本周期已对**任意一个** outcome 触发并下单,即标记本周期已触发,不再对**另一个 outcome(反方向)**做区间判断与下单。
|
||||
- **同一时刻两边都进区间**:若同一时刻 Up 和 Down 的价格都在 [0.92, 1](理论上二元市场 Up+Down≈1 时不会同时 ≥0.92,但若出现),可约定按 outcomeIndex 优先(如先判 0 再判 1)或先到先得,只执行一笔买入,本周期不再买反方向。
|
||||
|
||||
总结:配置 0.92 时,对**两个方向**都做同一区间判断;先满足区间的那一侧触发买入,另一侧即为反方向,本周期不再触发。
|
||||
|
||||
## 8. 验证方式
|
||||
|
||||
**startDate/endDate 验证结论**:已用脚本对比 slug 时间戳与 API 返回的 startDate/endDate。**endDate 等于当前周期结束时间**;**startDate 不等于周期起始点**(为市场创建/开放时间),周期起始点应以 slug 中的时间戳为准。详见上文 3.2、5 节。
|
||||
|
||||
### 8.1 脚本(推荐)
|
||||
|
||||
项目内脚本,会请求当前/下一 5 分钟与 15 分钟 BTC 市场并打印 conditionId、startDate、endDate、clobTokenIds:
|
||||
|
||||
```bash
|
||||
python3 scripts/fetch_crypto_minute_markets.py
|
||||
```
|
||||
|
||||
### 8.2 curl 示例
|
||||
|
||||
```bash
|
||||
# 5 分钟 - 当前或下一周期(时间戳需替换为实际周期起点)
|
||||
curl -s "https://gamma-api.polymarket.com/events/slug/btc-updown-5m-1771007100"
|
||||
|
||||
# 15 分钟 - 当前周期(时间戳需替换为实际周期起点)
|
||||
curl -s "https://gamma-api.polymarket.com/events/slug/btc-updown-15m-1771006500"
|
||||
|
||||
# 15 分钟 - 历史存在的事件
|
||||
curl -s "https://gamma-api.polymarket.com/events/slug/btc-updown-15m-1770882300"
|
||||
curl -s "https://gamma-api.polymarket.com/events/slug/eth-updown-15m-1770801300"
|
||||
```
|
||||
|
||||
若返回 403,可加 User-Agent:`curl -s -H "User-Agent: PolymarketBot/1.0" "https://gamma-api.polymarket.com/events/slug/btc-updown-5m-1771007100"`
|
||||
@@ -1,247 +0,0 @@
|
||||
# 尾盘策略 - 最小价差参数流程分析
|
||||
|
||||
## 一、需求摘要
|
||||
|
||||
在现有尾盘策略上增加**最小价差**参数:当策略条件(时间窗、价格区间)满足时,再判断**当前周期 Binance K 线的开盘价与收盘价价差**是否满足最小价差;满足才下单,不满足则等待,直到价差满足再下单。
|
||||
|
||||
- **后端**:需订阅币安对应币对(如 BTC/USDC)的 K 线,维护当前周期的**开盘价**与**实时收盘价**,并在触发时做价差校验。
|
||||
- **前端**:可配置三种场景——无、固定、自动(见下)。
|
||||
|
||||
---
|
||||
|
||||
## 二、前端配置场景
|
||||
|
||||
| 场景 | 配置方式 | 校验逻辑 |
|
||||
|------|----------|----------|
|
||||
| **无** | 不进行价差校验 | 与现有一致:仅判断时间窗 + 价格区间,满足即下单。 |
|
||||
| **固定** | 用户输入一个固定价差(如 30) | 当 \|收盘价 − 开盘价\| ≥ 该固定值时,校验通过,再下单。 |
|
||||
| **自动** | 由系统根据历史数据计算最小价差 | 见下文「自动模式计算逻辑」;得到数值后,后续与固定模式一致:\|收盘价 − 开盘价\| ≥ 计算值 则通过。 |
|
||||
|
||||
### 自动模式计算逻辑
|
||||
|
||||
- 通过币安 API 获取**历史 20 根** K 线(与策略周期一致:5m 取 5m K 线,15m 取 15m K 线)。
|
||||
- **下单方向 = Down**(outcomeIndex = 1):只取「收盘价 < 开盘价」的 K 线,得到价差序列(开盘价 − 收盘价)。
|
||||
- **下单方向 = Up**(outcomeIndex = 0):只取「收盘价 > 开盘价」的 K 线,得到价差序列(收盘价 − 开盘价)。
|
||||
- **异常值剔除**:对上述价差序列做异常值过滤(见下文「异常值剔除」),再用**剩余样本**求平均价差,乘以系数 **80%** 得到最小价差;后续用该值做 \|收盘价 − 开盘价\| ≥ 该值 的校验。
|
||||
- **历史数据获取时机**:**在该周期开始时就拉取并计算**,不在保存策略时计算。订单簿 WS 在周期开始时刷新订阅(含每 25 秒或周期切换时的 refreshAndSubscribe),此时对当前周期内所有启用且为 AUTO 的策略,按 (intervalSeconds, periodStartUnix) 预拉该周期前 20 根已收盘 K 线并计算 minSpreadUp/minSpreadDown 写入缓存;该周期内触发时直接用缓存,无需在触发时再调 REST。
|
||||
|
||||
### 异常值剔除
|
||||
|
||||
- **目的**:避免少数极端 K 线(如 14 组价差在 50 以内、1 组价差 200)拉高平均价差,导致最小价差偏大、难以触发。
|
||||
- **做法**:在按方向得到价差序列后,先**剔除异常值**,再对剩余价差求平均并 × 0.8。
|
||||
- **推荐方法:IQR(四分位距)**
|
||||
- 对价差序列排序,计算 Q1(25% 分位)、Q3(75% 分位)、IQR = Q3 − Q1。
|
||||
- 保留区间 **[Q1 − 1.5×IQR, Q3 + 1.5×IQR]** 内的价差,剔除该区间外的点。
|
||||
- 示例:15 组价差,14 组在 50 以内、1 组为 200 → 200 会超出上界被剔除,只用 14 组参与平均。
|
||||
- **边界与降级**
|
||||
- 若剔除后剩余样本数过少(如 < 3),则**不剔除**:用全部价差样本求平均 × 0.8。
|
||||
- 若无满足方向的 K 线(如 20 根里没有 close < open),仍按原文档降级处理(全量 \|close−open\| 或返回 0)。
|
||||
|
||||
---
|
||||
|
||||
## 三、整体流程(含价差校验)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. 数据源与订阅 │
|
||||
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ • CLOB 订单簿 WS(现有):Polymarket 订单簿 → bestBid。 │
|
||||
│ • 币安 K 线 WS(新增):订阅 BTCUSDC 对应周期(5m/15m),维护「当前周期」的开盘价 │
|
||||
│ open、实时收盘价 close(每根 K 线未收盘前 close 会持续更新)。 │
|
||||
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 2. 触发入口(与现有一致) │
|
||||
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ • 入口 A:CryptoTailOrderbookWsService.onBestBid(tokenId, bestBid) │
|
||||
│ • 入口 B:CryptoTailStrategyExecutionService.runCycle()(HTTP 拉订单簿) │
|
||||
│ 两者在「时间窗 + 价格区间 + 本周期未触发」通过后,都会调用执行层「尝试下单」。 │
|
||||
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 3. 执行层增加「价差校验」 │
|
||||
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 在现有 tryTriggerWithPriceFromWs / runCycle → placeOrderForTrigger 之前增加: │
|
||||
│ │
|
||||
│ if (策略.minSpreadMode == NONE) → 直接进入 placeOrderForTrigger。 │
|
||||
│ else: │
|
||||
│ • 从「币安 K 线服务」取当前周期(与 strategy.intervalSeconds 对齐)的 open、 │
|
||||
│ close(实时)。 │
|
||||
│ • 若取不到 open/close(例如该周期尚未有数据)→ 本轮不下单,等待下次 WS 更新。 │
|
||||
│ • 计算 effectiveMinSpread: │
|
||||
│ - FIXED:effectiveMinSpread = 策略.minSpreadValue(用户填的固定值) │
|
||||
│ - AUTO:effectiveMinSpread = 按当前下单方向(outcomeIndex)取「自动计算 │
|
||||
│ 的最小价差」(见下节;若尚未计算则先拉 20 根历史 K 线并计算、缓存)。 │
|
||||
│ • 若 |close − open| < effectiveMinSpread → 本轮不下单,等待价差满足。 │
|
||||
│ • 若 |close − open| >= effectiveMinSpread → 通过价差校验,进入 │
|
||||
│ placeOrderForTrigger(与现有逻辑一致:预签/签名、提交 CLOB 订单、写触发记录)。│
|
||||
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **「等待价差满足」**:不主动轮询;下次 CLOB 订单簿或币安 K 线有推送时,会再次进入上述判断,此时 close 可能已更新,价差可能已满足,再决定是否下单。
|
||||
- **每周期最多触发一次**:仍由现有「本周期是否已触发」保证;价差不满足时**不写触发记录**,也不占「已触发」名额,直到某次检查同时满足价格区间与价差后才下单并标记已触发。
|
||||
|
||||
---
|
||||
|
||||
## 四、自动模式:何时拉历史、如何算、如何用
|
||||
|
||||
- **何时拉 20 根历史 K 线并计算**
|
||||
- **在该周期开始时就预计算**,不在保存策略时计算。
|
||||
- 订单簿 WS 在**周期开始时**会刷新订阅(`refreshAndSubscribe`:每 25 秒或检测到周期切换时),此时对当前周期内所有启用且 minSpreadMode=AUTO 的策略,按 `(intervalSeconds, periodStartUnix)` 异步拉取该周期前 20 根已收盘 K 线(REST `endTime = periodStartUnix * 1000`),按 Up/Down 分别算 avgSpread × 0.8(含 IQR 剔除)并写入缓存。该周期内后续触发时直接用缓存,**不在触发时再调 REST**。
|
||||
- 若某周期未做预计算(如服务刚启动且尚未到刷新时机),触发时仍会按需调用 `computeAndCache` 并缓存,保证逻辑正确。
|
||||
- 前端「自动最小价差」接口仅作**预览**,实际下单校验不依赖该接口。
|
||||
|
||||
- **计算细节**
|
||||
- 历史 20 根:币安 REST `GET /api/v3/klines?symbol=BTCUSDC&interval=5m|15m&limit=20`(或 21 取前 20 根已收盘),每根格式为 [openTime, open, high, low, close, ...]。
|
||||
- **Down(outcomeIndex=1)**:筛选 close < open,价差 = open − close,得到价差序列 → **异常值剔除(IQR)** → 对剩余价差求平均,再 × 0.8 → minSpreadDown。
|
||||
- **Up(outcomeIndex=0)**:筛选 close > open,价差 = close − open,得到价差序列 → **异常值剔除(IQR)** → 对剩余价差求平均,再 × 0.8 → minSpreadUp。
|
||||
- **异常值剔除**:见上文「异常值剔除」;剔除后再平均。若剔除后剩余样本 < 3,则不剔除,用全部价差样本求平均。
|
||||
- 若无满足方向的 K 线(例如 20 根里没有一根 close < open),可降级:用全部 20 根的 |close−open| 平均 × 0.8,或返回 0/不校验,具体产品可定。
|
||||
|
||||
- **触发时使用**
|
||||
- 当前要下单的是 outcomeIndex(0=Up, 1=Down),取对应的 minSpreadUp 或 minSpreadDown 作为 effectiveMinSpread,再与 |close − open| 比较。
|
||||
|
||||
---
|
||||
|
||||
## 五、后端模块与数据流
|
||||
|
||||
| 模块 | 职责 |
|
||||
|------|------|
|
||||
| **BinanceKlineService(新)** | 1)订阅币安 WS:BTCUSDC 的 5m、15m K 线流(可按需只订阅有策略使用的周期)。<br>2)维护「当前周期」数据:以 periodStartUnix(或 K 线 t 对齐)为 key,存 (open, close);K 线 WS 推送时更新 close,新周期首条推送时更新 open。<br>3)提供 getCurrentOpenClose(symbol, intervalSeconds, periodStartUnix) → (open, close)?,供执行层价差校验使用。 |
|
||||
| **BinanceKlineAutoSpreadService 或合入上者(新)** | 1)按**周期**拉取:以 periodStartUnix 为界,REST 拉取该周期前的 20 根已收盘 K 线。<br>2)按 Up/Down 得到价差序列 → **IQR 异常值剔除** → 对剩余价差求平均 × 0.8,缓存 (intervalSeconds, periodStartUnix) → (minSpreadUp, minSpreadDown)。<br>3)提供 getAutoMinSpread(intervalSeconds, periodStartUnix, outcomeIndex) 与 computeAndCache(intervalSeconds, periodStartUnix)。**周期开始时**由 CryptoTailOrderbookWsService 在 refreshAndSubscribe 后对当前周期内 AUTO 策略预调 computeAndCache;触发时直接用缓存,未命中时再按需计算。 |
|
||||
| **CryptoTailStrategy(实体)** | 新增字段建议:minSpreadMode(NONE/FIXED/AUTO)、minSpreadValue(固定时使用;AUTO 时可为空或存上次计算值用于展示)。 |
|
||||
| **CryptoTailStrategyExecutionService(现有)** | 在 tryTriggerWithPriceFromWs 与 runCycle 分支中,在调用 placeOrderForTrigger 前:若 minSpreadMode != NONE,则取 open/close 与 effectiveMinSpread,校验 \|close−open\| >= effectiveMinSpread;不通过则 return,不调用 placeOrderForTrigger。 |
|
||||
| **CryptoTailOrderbookWsService(现有)** | 仍只根据 CLOB bestBid 触发;价差校验在执行层统一做。**新增**:refreshAndSubscribe 完成后,对当前周期内所有启用且 minSpreadMode=AUTO 的策略,异步调用 BinanceKlineAutoSpreadService.computeAndCache,在周期开始即预计算最小价差。 |
|
||||
|
||||
- **币安 K 线与周期对齐**
|
||||
- 策略周期:periodStartUnix 为秒(如 5m 周期 = 300 的倍数,15m = 900 的倍数)。
|
||||
- 币安 K 线:t 为毫秒,同一周期:t_ms = periodStartUnix * 1000。
|
||||
- 用 (intervalSeconds, periodStartUnix) 或 (interval, t_ms) 对齐即可从 BinanceKlineService 取到「当前周期」的 open 和实时 close。
|
||||
|
||||
---
|
||||
|
||||
## 六、固定(FIXED)与自动(AUTO)时序图
|
||||
|
||||
### 6.1 固定(FIXED)时序图
|
||||
|
||||
固定模式:用户保存策略时写入 `minSpreadValue`(如 30);触发时直接用该值与当前周期 \|close−open\| 比较,不拉历史 K 线。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant API as Controller
|
||||
participant Svc as CryptoTailStrategyService
|
||||
participant DB as 数据库
|
||||
participant CLOB_WS as CLOB 订单簿 WS
|
||||
participant Orderbook as CryptoTailOrderbookWsService
|
||||
participant Exec as CryptoTailStrategyExecutionService
|
||||
participant BinanceWS as BinanceKlineService
|
||||
participant CLOB as Polymarket CLOB
|
||||
|
||||
User->>API: 保存策略 minSpreadMode=FIXED, minSpreadValue=30
|
||||
API->>Svc: create/update
|
||||
Svc->>DB: 写入 min_spread_mode, min_spread_value
|
||||
Svc-->>API: 成功
|
||||
API-->>User: 成功
|
||||
|
||||
Note over BinanceWS: 后台持续:币安 K 线 WS 更新当前周期 (open, close)
|
||||
|
||||
CLOB_WS->>Orderbook: onMessage(book/price_change) → bestBid
|
||||
Orderbook->>Orderbook: 时间窗内?价格在 [min,max]?本周期未触发?
|
||||
Orderbook->>Exec: tryTriggerWithPriceFromWs(strategy, periodStartUnix, ..., bestBid)
|
||||
Exec->>Exec: mutex 锁
|
||||
Exec->>Exec: 本周期已触发?→ 是则 return
|
||||
Exec->>Exec: passMinSpreadCheck(strategy, periodStartUnix, outcomeIndex)
|
||||
Exec->>Exec: mode==FIXED → effectiveMinSpread = strategy.minSpreadValue (30)
|
||||
Exec->>BinanceWS: getCurrentOpenClose(intervalSeconds, periodStartUnix)
|
||||
BinanceWS-->>Exec: (open, close) 来自内存
|
||||
Exec->>Exec: |close−open| >= 30 ? 否 → return,不下单
|
||||
Exec->>Exec: 是 → 通过价差校验
|
||||
Exec->>Exec: ensurePeriodContext → placeOrderForTrigger
|
||||
Exec->>CLOB: 提交订单
|
||||
CLOB-->>Exec: orderId
|
||||
Exec->>DB: 写入触发记录 (本周期已触发)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.2 自动(AUTO)时序图
|
||||
|
||||
自动模式:不在保存策略时计算。**在该周期开始时就预计算**(订单簿 WS 刷新订阅时对该周期内 AUTO 策略异步拉 20 根历史 K 线并计算、缓存);触发时直接用缓存,同一周期内复用。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant API as Controller
|
||||
participant Svc as CryptoTailStrategyService
|
||||
participant DB as 数据库
|
||||
participant CLOB_WS as CLOB 订单簿 WS
|
||||
participant Orderbook as CryptoTailOrderbookWsService
|
||||
participant Exec as CryptoTailStrategyExecutionService
|
||||
participant BinanceWS as BinanceKlineService
|
||||
participant AutoSpread as BinanceKlineAutoSpreadService
|
||||
participant BinanceREST as 币安 REST API
|
||||
participant CLOB as Polymarket CLOB
|
||||
|
||||
User->>API: 保存策略 minSpreadMode=AUTO(不填 minSpreadValue)
|
||||
API->>Svc: create/update
|
||||
Svc->>DB: 写入 min_spread_mode=AUTO
|
||||
Svc-->>API: 成功
|
||||
API-->>User: 成功
|
||||
|
||||
Note over BinanceWS: 后台持续:币安 K 线 WS 更新当前周期 (open, close)
|
||||
|
||||
CLOB_WS->>Orderbook: onMessage → bestBid
|
||||
Orderbook->>Orderbook: 时间窗 + 价格区间 + 本周期未触发 ✓
|
||||
Orderbook->>Exec: tryTriggerWithPriceFromWs(strategy, periodStartUnix, ..., bestBid)
|
||||
Exec->>Exec: mutex 锁
|
||||
Exec->>Exec: passMinSpreadCheck(strategy, periodStartUnix, outcomeIndex)
|
||||
Exec->>BinanceWS: getCurrentOpenClose(intervalSeconds, periodStartUnix)
|
||||
BinanceWS-->>Exec: (open, close)
|
||||
Note over Orderbook,AutoSpread: 周期开始时 refreshAndSubscribe 已对该周期预计算(见下)
|
||||
Exec->>AutoSpread: getAutoMinSpread(intervalSeconds, periodStartUnix, outcomeIndex)
|
||||
AutoSpread->>AutoSpread: 查缓存 (intervalSeconds, periodStartUnix) → 命中(周期开始已预计算)
|
||||
AutoSpread-->>Exec: effectiveMinSpread
|
||||
Exec->>Exec: |close−open| >= effectiveMinSpread ? 否 → return
|
||||
Exec->>Exec: 是 → 通过价差校验
|
||||
Exec->>Exec: placeOrderForTrigger → CLOB 下单
|
||||
Exec->>DB: 写入触发记录
|
||||
|
||||
Note over Orderbook,AutoSpread: 周期开始时(refreshAndSubscribe 或周期切换)
|
||||
Orderbook->>Orderbook: refreshAndSubscribe() → buildSubscriptionMap() → newMap
|
||||
Orderbook->>Orderbook: precomputeAutoMinSpreadForCurrentPeriods(newMap)
|
||||
Orderbook->>AutoSpread: computeAndCache(intervalSeconds, periodStartUnix) [异步]
|
||||
AutoSpread->>BinanceREST: GET /api/v3/klines?symbol=BTCUSDC&interval=15m&limit=20&endTime=periodStart*1000
|
||||
BinanceREST-->>AutoSpread: 20 根已收盘 K 线
|
||||
AutoSpread->>AutoSpread: 按 Up/Down 拆价差 → IQR 剔除 → 平均×0.8 → 缓存
|
||||
|
||||
Note over CLOB_WS,Exec: 同一周期内再次触发(如另一 outcome 或再次 bestBid)
|
||||
CLOB_WS->>Orderbook: onMessage → bestBid
|
||||
Orderbook->>Exec: tryTriggerWithPriceFromWs(...)
|
||||
Exec->>AutoSpread: getAutoMinSpread(intervalSeconds, periodStartUnix, outcomeIndex)
|
||||
AutoSpread->>AutoSpread: 查缓存 → 命中
|
||||
AutoSpread-->>Exec: effectiveMinSpread(不再调 REST)
|
||||
Exec->>Exec: 价差校验 → 通过则下单(或本周期已触发则跳过)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、流程小结(按执行顺序)
|
||||
|
||||
1. **策略配置**
|
||||
- 用户选择:无 / 固定(输入数值)/ 自动。
|
||||
- 固定:必填 minSpreadValue,保存到 DB。
|
||||
- 自动:不填 minSpreadValue,**不在保存时计算**;按周期在首次需要时计算并缓存。
|
||||
|
||||
2. **运行时**
|
||||
- 币安 WS 持续更新当前周期的 (open, close)。
|
||||
- CLOB 订单簿(或 HTTP)带来 bestBid;若时间窗 + 价格区间 + 本周期未触发 均满足:
|
||||
- 若 minSpreadMode == NONE → 直接 placeOrderForTrigger。
|
||||
- 否则取当前周期 open/close 与 effectiveMinSpread(固定值或自动缓存值),若 \|close−open\| >= effectiveMinSpread → placeOrderForTrigger;否则本轮不下单,等后续推送再判。
|
||||
|
||||
3. **下单与去重**
|
||||
- 仍保持「每周期最多触发一次」;价差不满足时不写触发记录,直到某次同时满足价格与价差后才下单并写记录。
|
||||
|
||||
按上述流程即可在现有尾盘策略上接入「最小价差」参数,并由后端订阅币安 K 线、在触发前做价差校验;固定与自动的时序差异见**第六节时序图**。
|
||||
@@ -1,150 +0,0 @@
|
||||
# 加密市场尾盘策略 - 任务梳理
|
||||
|
||||
> 需求与 UI 见 `crypto-tail-strategy-ui-spec.md`,市场数据与执行规则见 `crypto-tail-strategy-market-data.md`。
|
||||
|
||||
以下按**文档 / 数据库 / 后端 / 前端**拆分为可执行任务,便于排期与验收。
|
||||
|
||||
---
|
||||
|
||||
## 一、文档(已完成)
|
||||
|
||||
| 任务 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| PRD 与需求 | ✅ | 周期、价格区间、每周期最多触发一次、重试 2 次等 |
|
||||
| 市场数据文档 | ✅ | `crypto-tail-strategy-market-data.md`:Gamma slug、周期、时间区间、价格判断 |
|
||||
| UI 规格 | ✅ | `crypto-tail-strategy-ui-spec.md`:列表、表单、时间区间、触发记录、赎回前置检查 |
|
||||
|
||||
---
|
||||
|
||||
## 二、数据库
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| D1 | 策略表 migration | 新建表,字段建议:id, account_id, name, market_slug_prefix(如 btc-updown-5m), interval_seconds(300/900), window_start_seconds, window_end_seconds, min_price, max_price, amount_mode(ratio/fixed), amount_value(比例或 USDC 字符串), enabled, created_at, updated_at。唯一/外键按现有规范。 |
|
||||
| D2 | 触发记录表 migration | 新建表,字段建议:id, strategy_id, period_start_unix, market_title, outcome_index(0=Up/1=Down), trigger_price, amount_usdc, order_id(可空), status(success/fail), fail_reason(可空), created_at。便于列表与筛选。 |
|
||||
|
||||
---
|
||||
|
||||
## 三、后端(Kotlin)
|
||||
|
||||
### 3.1 实体与 Repository
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| B1 | 策略实体 Entity | 对应策略表;ID 用 Long?;时间 Long 时间戳;金额 BigDecimal;遵守 backend.mdc 实体规范。 |
|
||||
| B2 | 触发记录实体 Entity | 对应触发记录表。 |
|
||||
| B3 | JpaRepository | 策略、触发记录的 Repository;按 strategyId、时间等查记录。 |
|
||||
|
||||
### 3.2 外部依赖与领域
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| B4 | Gamma API 按 slug 拉市场 | 已有或扩展 PolymarketGammaApi:GET /events/slug/{slug},返回 conditionId、endDate、clobTokenIds 等;与 market-data 文档 3、4 节一致。 |
|
||||
| B5 | 周期与 slug 推导 | 工具或 Service:根据 interval(300/900)、当前时间算 periodStartUnix;拼 slug(如 btc-updown-5m-{ts});解析 endDate 得 endDateUnix。 |
|
||||
| B6 | 订单簿价格 | 使用现有 CLOB/订单簿能力,按 conditionId、clobTokenIds 取各 outcome 的 bestBid;与 market-data 7.1 一致。 |
|
||||
| B7 | 市价单与重试 | 按策略的 amount 计算下单金额;市价买入指定 outcome;失败时最多重试 2 次(共 3 次),仍失败则写触发记录状态为失败并记原因。 |
|
||||
|
||||
### 3.3 策略执行核心逻辑(按 market-data 第 6、7 节)
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| B8 | 周期内时间窗口判断 | 仅当 `periodStartUnix + windowStartSeconds <= nowUnix < periodStartUnix + windowEndSeconds` 时,才做价格区间判断与下单;区间外不处理。 |
|
||||
| B9 | 价格区间与「先满足先买」 | 对两个 outcome 取价,若某 outcome 价格 ∈ [minPrice, maxPrice],则触发买该 outcome;另一 outcome 本周期不再触发(7.1)。 |
|
||||
| B10 | 每周期只触发一次 | 以 (strategyId, periodStartUnix) 去重;周期切换时重置「本周期已触发」状态;结合 B8、B9 实现。 |
|
||||
| B11 | 周期切换与 404 | 当 now >= endDateUnix 或新 periodStartUnix 时,用新 periodStartUnix 拉新 slug;404 时同 periodStartUnix 短间隔重试,长时间 404 可跳过本周期并打日志。 |
|
||||
|
||||
### 3.4 API 与 DTO
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| B12 | 策略 CRUD API | 列表(分页/筛选)、创建、更新、删除、启用/停用;请求/响应为 DTO,不用 Map;统一 ApiResponse;错误码与 MessageSource。 |
|
||||
| B13 | 策略 DTO | 创建/更新包含:accountId, name, marketSlugPrefix, intervalSeconds, windowStartSeconds, windowEndSeconds, minPrice, maxPrice(可选默认 1), amountMode, amountValue;校验 windowStart <= windowEnd,且不超过周期长度。 |
|
||||
| B14 | 触发记录 API | 按 strategyId 分页查询触发记录;返回列表 DTO(时间、市场、方向、价格、金额、订单 ID、状态)。 |
|
||||
| B15 | 5/15 分钟市场列表 API(可选) | 若前端需要「可选市场」列表:可按当前/下一周期拼 slug 调 Gamma 返回市场信息,供前端选择;或前端直接按 slug 规则+周期展示。 |
|
||||
|
||||
### 3.5 自动赎回与调度
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| B16 | 自动赎回包含尾盘策略仓位 | 尾盘策略产生的仓位与跟单/手动一视同仁,纳入现有自动赎回逻辑,不排除(见 UI 规格附录 A)。 |
|
||||
| B17 | 调度/定时或常驻 | 对已启用策略按周期(如每 10–30 秒)检查:当前周期、是否在时间窗口内、是否已触发、价格是否进区间;满足则执行下单并写触发记录。 |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端(React + TypeScript)
|
||||
|
||||
### 4.1 路由与导航
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| F1 | 路由 | App.tsx 增加 `/crypto-tail-strategy`、可选 `/crypto-tail-strategy/records/:id`。 |
|
||||
| F2 | 菜单 | Layout 中增加「尾盘策略」菜单项,与跟单同级或在其下;key 与路由一致。 |
|
||||
|
||||
### 4.2 列表页
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| F3 | 列表页组件 | 如 CryptoTailStrategyList.tsx;页面标题、钱包提示 Alert、新增按钮、筛选(账户、状态)。 |
|
||||
| F4 | 列表展示 | 桌面 Table / 移动 Card:策略名、关联市场、时间区间、价格区间、投入方式、状态、最近触发、操作(编辑、启用/停用、删除、查看触发记录);删除 Popconfirm。 |
|
||||
| F5 | 创建前检查 | 点击「新增策略」先调接口判断是否已配置自动赎回(如 builderApiKeyConfigured);未配置则弹出「请先配置自动赎回」Modal(去配置 → /system-settings,取消),不打开表单。 |
|
||||
|
||||
### 4.3 新增/编辑表单
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| F6 | 表单弹窗 | 策略名、选择账户、选择市场、时间区间、minPrice、maxPrice、投入方式(比例/固定)、启用状态。 |
|
||||
| F7 | 时间区间控件 | 区间开始/结束:下拉选「分钟」+「秒」;5min 市场 0–5 分+0–59 秒(总≤5min),15min 市场 0–15 分+0–59 秒(总≤15min);校验**开始 ≤ 结束**;提交时转为 windowStartSeconds、windowEndSeconds。 |
|
||||
| F8 | 市场选择器 | 仅展示 5/15 分钟加密市场;支持搜索;展示市场标题+周期;选后用于校验时间区间上界(5min 结束≤300s,15min≤900s)。 |
|
||||
| F9 | 表单校验与提交 | 市场类型、时间区间 start≤end 且不超周期、minPrice/maxPrice、比例或固定金额合法;提交后刷新列表、成功提示。 |
|
||||
|
||||
### 4.4 触发记录
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| F10 | 触发记录展示 | 弹窗或独立页:触发时间、市场、方向(Up/Down)、触发价格、投入金额、订单 ID、状态;支持按时间、状态筛选;formatUSDC;移动端 Card/折叠。 |
|
||||
|
||||
### 4.5 通用
|
||||
|
||||
| 序号 | 任务 | 说明 |
|
||||
|------|------|------|
|
||||
| F11 | 类型定义 | 策略、触发记录等 TypeScript 类型;无 any。 |
|
||||
| F12 | API 封装 | apiService 中 cryptoTailStrategy.list/create/update/delete/toggle、records(strategyId) 等。 |
|
||||
| F13 | 多语言 | locales 中 zh-CN、zh-TW、en 的 cryptoTailStrategy.*:list.title、list.walletTip、form.walletTip、redeemRequiredModal.*、时间区间/价格区间等文案。 |
|
||||
|
||||
---
|
||||
|
||||
## 五、依赖关系简图
|
||||
|
||||
```
|
||||
文档 ✅
|
||||
↓
|
||||
D1,D2 数据库
|
||||
↓
|
||||
B1–B3 实体与 Repository
|
||||
↓
|
||||
B4–B7 外部 API、周期、价格、下单
|
||||
↓
|
||||
B8–B11 执行逻辑(时间窗口+价格+去重+周期切换)
|
||||
↓
|
||||
B12–B15 API 与 DTO
|
||||
B16 自动赎回
|
||||
B17 调度
|
||||
↓
|
||||
F1–F2 路由与菜单
|
||||
F11–F12 类型与 API 封装
|
||||
F13 多语言
|
||||
↓
|
||||
F3–F5 列表与创建前检查
|
||||
F6–F9 表单(含时间区间)
|
||||
F10 触发记录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、验收要点
|
||||
|
||||
- **时间区间**:仅当周期内当前时间落在 [windowStartSeconds, windowEndSeconds] 时才判断价格并下单;前端区间开始 ≤ 结束,且不超出 5min/15min。
|
||||
- **每周期一次**:同一策略同一周期只触发一次(先满足价格的 outcome 买入,反方向不买)。
|
||||
- **重试**:下单失败最多重试 2 次,共 3 次;仍失败记入触发记录为失败。
|
||||
- **自动赎回**:尾盘策略产生的仓位可被自动赎回,无排除逻辑。
|
||||
- **创建前检查**:未配置自动赎回时点击新增策略弹出「去配置」弹窗,不打开表单。
|
||||
@@ -1,177 +0,0 @@
|
||||
# 加密市场尾盘策略 - 前端 UI 规格
|
||||
|
||||
> 周期推导与市场数据获取详见 `crypto-tail-strategy-market-data.md`。
|
||||
|
||||
与现有跟单/回测保持同一风格(Ant Design、响应式、多语言),以下为页面结构及所含元素。
|
||||
|
||||
---
|
||||
|
||||
## 1. 导航与路由
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| **菜单** | 在「跟单管理」同级或其下增加一项,如「尾盘策略」,key 建议 `/crypto-tail-strategy`。 |
|
||||
| **路由** | 列表页 `/crypto-tail-strategy`;可选详情/触发记录 `/crypto-tail-strategy/records/:id`。 |
|
||||
|
||||
参考:`Layout.tsx` 中 `/copy-trading`、`/backtest` 的配置;`App.tsx` 中对应 `Route`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 列表页(主页面)
|
||||
|
||||
**路径**:`/crypto-tail-strategy`
|
||||
**组件**:如 `CryptoTailStrategyList.tsx`(或 `TailStrategyList.tsx`)。
|
||||
|
||||
### 2.1 顶部操作区
|
||||
|
||||
| 元素 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| 页面标题 | 标题文案 | 如「加密尾盘策略」,用 `t('cryptoTailStrategy.list.title')`。 |
|
||||
| **钱包使用提示** | **Alert(Warning)** | **必须**在页面顶部或标题下方展示:提示用户**使用单独/专用钱包**运行本策略,避免该钱包用于手动交易、跟单等其他操作,否则可能导致余额或仓位变化,进而造成策略执行异常(如余额不足、下单失败等)。文案走多语言 `t('cryptoTailStrategy.list.walletTip')`,可带 `showIcon`。 |
|
||||
| 新增策略 | Button(Primary) | 点击时**先检查自动赎回相关配置**(见 2.4);若未配置则弹出「去配置」简易弹窗,若已配置则打开「新增策略」表单弹窗。图标可用 `PlusOutlined`。 |
|
||||
| 筛选(可选) | Select / 筛选项 | 按账户、启用状态筛选;移动端可收起到抽屉或折叠。 |
|
||||
|
||||
### 2.2 列表内容(桌面端:Table,移动端:Card 列表)
|
||||
|
||||
| 列/卡片项 | 说明 |
|
||||
|-----------|------|
|
||||
| 策略名称 | 用户填的配置名或自动生成名。 |
|
||||
| 关联市场 | 展示市场标题 + 周期,如「Bitcoin Up or Down - 5 minute」。 |
|
||||
| 时间区间 | 如「3 分 0 秒 ~ 12 分 0 秒」(与周期类型一致:5min 为 0–5 分,15min 为 0–15 分)。 |
|
||||
| 价格区间 | 如 `[0.92, 1]` 或「0.92 ~ 1」(maxPrice 为空时显示为 1)。 |
|
||||
| 投入方式 | 「比例 10%」或「固定 100 USDC」,用 `formatUSDC` 格式化金额。 |
|
||||
| 状态 | Tag 或 Switch:启用 / 停用。 |
|
||||
| 最近触发 | 最近一次触发时间(若有);无则「-」。 |
|
||||
| 操作 | 编辑、启用/停用、删除、查看触发记录。删除前 Popconfirm 二次确认。 |
|
||||
|
||||
### 2.3 与现有风格对齐
|
||||
|
||||
- 加载态:`Spin` 包裹列表。
|
||||
- 空状态:无数据时展示空状态插画 + 引导「新增策略」。
|
||||
- 响应式:`useMediaQuery({ maxWidth: 768 })`,桌面用 Table,移动用 Card + 操作折叠/抽屉。
|
||||
|
||||
参考:`CopyTradingList.tsx` 的 Table 列、Card 布局、筛选与 Modal 打开方式。
|
||||
|
||||
### 2.4 创建前检查:自动赎回配置(必须)
|
||||
|
||||
策略依赖**自动赎回**(需通过 Relayer/Builder API 提交链上赎回)。用户点击「新增策略」时:
|
||||
|
||||
1. **检查**:请求系统配置(如 `apiService.systemConfig.getConfig()` 或已有接口),判断是否已配置 Builder API Key(及可选:自动赎回已开启)。若 `builderApiKeyConfigured === false`(或后端约定之「未配置」状态),视为未配置。
|
||||
2. **未配置时**:不打开新增策略表单,改为弹出**简易弹窗**(Modal),内容建议:
|
||||
- **标题**:如「请先配置自动赎回」,`t('cryptoTailStrategy.redeemRequiredModal.title')`。
|
||||
- **正文**:简短说明尾盘策略依赖自动赎回,需要先在「系统设置」中配置 Builder API Key 及自动赎回。文案 `t('cryptoTailStrategy.redeemRequiredModal.description')`。
|
||||
- **操作**:
|
||||
- **去配置**:主按钮,点击后关闭弹窗并跳转到系统设置页(如 `/system-settings`,该页含 Relayer 配置与自动赎回开关)。
|
||||
- **取消**:次按钮或关闭图标,仅关闭弹窗。
|
||||
3. **已配置时**:正常打开新增策略表单弹窗。
|
||||
|
||||
弹窗保持简易,无需表单,仅提示 + 跳转;多语言键示例:`cryptoTailStrategy.redeemRequiredModal.title`、`cryptoTailStrategy.redeemRequiredModal.description`、`cryptoTailStrategy.redeemRequiredModal.goToSettings`、`cryptoTailStrategy.redeemRequiredModal.cancel`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 新增 / 编辑策略弹窗(Modal)
|
||||
|
||||
**组件**:如 `CryptoTailStrategyFormModal.tsx` 或内嵌在列表页的 Modal。
|
||||
|
||||
### 3.1 表单字段
|
||||
|
||||
| 表单项 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| **钱包提示(简短)** | **Alert(Warning)** | - | 在「选择账户」上方或表单单列顶部展示简短提示:建议使用**专用钱包**,避免手动操作等导致异常。文案如 `t('cryptoTailStrategy.form.walletTip')`。 |
|
||||
| 策略名称 | Input | 否 | 用于列表展示,可占位「自动生成」。 |
|
||||
| 选择账户 | Select | 是 | 下拉已导入账户(与跟单一致,来自 `useAccountStore()` 或接口)。 |
|
||||
| 选择市场 | 市场选择器 | 是 | 仅展示 5/15 分钟加密市场;支持搜索;展示市场标题 + 周期(5min/15min);一个策略绑一个市场。 |
|
||||
| **时间区间** | **开始 / 结束** | 是 | 仅在本周期内的该时间窗口内,价格满足时才下单;区间外不处理。见下方说明。 |
|
||||
| 区间开始 | 下拉(分 + 秒) | 是 | 从周期起点起算的「开始」偏移。5 分钟市场可选 0~5 分 + 0~59 秒(总不超过 5 分钟);15 分钟市场可选 0~15 分 + 0~59 秒(总不超过 15 分钟)。 |
|
||||
| 区间结束 | 下拉(分 + 秒) | 是 | 从周期起点起算的「结束」偏移。范围同上,且**区间开始不得大于区间结束**(前端校验)。 |
|
||||
| 最低价 minPrice | InputNumber | 是 | 0~1,精度 2~4 位小数;校验 minPrice ≤ 1。 |
|
||||
| 最高价 maxPrice | InputNumber | 否 | 0~1,占位「不填默认为 1」;若填则校验 minPrice ≤ maxPrice ≤ 1。 |
|
||||
| 投入方式 | Radio.Group | 是 | 选项:「按比例」「固定金额」。 |
|
||||
| 比例 % | InputNumber | 条件必填 | 选「按比例」时显示;0~100;可展示当前账户 USDC 余额与预估金额。 |
|
||||
| 固定金额 (USDC) | InputNumber | 条件必填 | 选「固定金额」时显示;≥ 最小下单额,≤ 账户余额;用 `formatUSDC` 展示。 |
|
||||
| 启用状态 | Switch | 否 | 新增默认开启;编辑可切换。 |
|
||||
|
||||
**时间区间说明**:例如 15 分钟市场配置「3 分 0 秒」~「12 分 0 秒」,表示从周期开始后第 3 分钟到第 12 分钟之间,若价格进入 [minPrice, maxPrice] 才下单;第 0~3 分钟、第 12~15 分钟即使价格满足也不下单。5 分钟市场同理,可选 0~5 分钟内的一段(如 0~2、2~5)。前端用下拉选择「分钟」+「秒」,后端存为相对周期起点的秒数(如 windowStartSeconds、windowEndSeconds)。
|
||||
|
||||
### 3.2 校验与提交
|
||||
|
||||
- 提交前:市场为 5/15 分钟、**时间区间开始 ≤ 时间区间结束**、时间区间不超出周期长度(5min 市场结束 ≤ 5 分 0 秒,15min 市场结束 ≤ 15 分 0 秒)、minPrice 合法、maxPrice 若填则 ≥ minPrice、余额/比例合法。
|
||||
- 提交后:关闭弹窗、刷新列表、`message.success`;失败在表单上展示接口错误信息。
|
||||
|
||||
参考:`CopyTradingOrders/AddModal.tsx` 的 Form 布局、`Form.Item` + `rules`、条件显示(比例/固定金额)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 触发记录
|
||||
|
||||
**入口**:列表行操作「查看触发记录」或单独 Tab/页。
|
||||
|
||||
### 4.1 展示方式(二选一或并存)
|
||||
|
||||
- **弹窗**:Modal 内 Table,按策略 ID 拉取该策略的触发记录。
|
||||
- **独立页**:路由如 `/crypto-tail-strategy/records/:strategyId`,页面内 Table 或 Card 列表。
|
||||
|
||||
### 4.2 记录列表字段
|
||||
|
||||
| 列/项 | 说明 |
|
||||
|-------|------|
|
||||
| 触发时间 | 时间戳格式化为本地时间。 |
|
||||
| 市场 | 市场标题 + 周期。 |
|
||||
| 方向 (outcome) | Up / Down。 |
|
||||
| 触发价格 | 当时进入区间的价格。 |
|
||||
| 投入金额 | USDC,用 `formatUSDC`。 |
|
||||
| 订单 ID | 若有;可截断 + Tooltip 全量。 |
|
||||
| 状态 | 成功 / 失败。 |
|
||||
|
||||
支持按时间范围、状态筛选;移动端用 Card 或折叠列表。
|
||||
|
||||
---
|
||||
|
||||
## 5. 组件与技术要点
|
||||
|
||||
| 要点 | 说明 |
|
||||
|------|------|
|
||||
| **钱包提示** | 列表页与新增/编辑表单**必须**包含「使用单独钱包」的 Alert 提示,避免用户用混用钱包导致异常;文案走多语言。 |
|
||||
| **创建前检查** | 点击「新增策略」时先检查自动赎回/Builder API 是否已配置;未配置则弹出简易「去配置」弹窗,引导用户到系统设置配置 API Key 与自动赎回,不打开策略表单。 |
|
||||
| 多语言 | 所有文案 `t('cryptoTailStrategy.xxx')`,在 `locales/zh-CN`、`zh-TW`、`en` 的 `common.json` 中增加键。需包含:`cryptoTailStrategy.list.walletTip`、`cryptoTailStrategy.form.walletTip`,以及 `cryptoTailStrategy.redeemRequiredModal.title`、`cryptoTailStrategy.redeemRequiredModal.description`、`cryptoTailStrategy.redeemRequiredModal.goToSettings`、`cryptoTailStrategy.redeemRequiredModal.cancel`。文案示例:列表页 `walletTip`:「请使用单独的钱包运行尾盘策略,避免该钱包用于手动交易、跟单等其他操作,否则可能导致余额或仓位变化,造成策略执行异常。」表单内 `walletTip`:「建议使用专用钱包,避免手动操作等导致余额或下单异常。」未配置赎回弹窗 `title`:「请先配置自动赎回」;`description`:「尾盘策略依赖自动赎回功能,请先在系统设置中配置 Builder API Key 并开启自动赎回。」;`goToSettings`:「去配置」;`cancel`:「取消」。 |
|
||||
| 金额 | 统一 `formatUSDC`(见 frontend.mdc)。 |
|
||||
| 响应式 | `useMediaQuery`;按钮触摸目标 ≥ 44px;移动端主操作突出。 |
|
||||
| 类型 | 不用 `any`;为策略、触发记录定义 TypeScript 类型。 |
|
||||
| API | 通过 `apiService` 封装(如 `apiService.cryptoTailStrategy.list/create/update/delete/records`)。 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 页面与文件建议对应
|
||||
|
||||
| 功能 | 建议路径/文件 |
|
||||
|------|----------------|
|
||||
| 列表页 | `frontend/src/pages/CryptoTailStrategyList.tsx` |
|
||||
| 未配置赎回时的简易弹窗 | 内嵌在列表页的 Modal,或 `CryptoTailStrategyList/RedeemRequiredModal.tsx` |
|
||||
| 新增/编辑弹窗 | `frontend/src/pages/CryptoTailStrategyList/FormModal.tsx` 或内嵌 Modal |
|
||||
| 触发记录 | `frontend/src/pages/CryptoTailStrategyList/TriggerRecordsModal.tsx` 或 `CryptoTailStrategyRecords.tsx` |
|
||||
| 路由 | `App.tsx` 中 `/crypto-tail-strategy`、可选 `/crypto-tail-strategy/records/:id` |
|
||||
| 菜单 | `Layout.tsx` 中增加「尾盘策略」菜单项 |
|
||||
| 类型 | `frontend/src/types/index.ts` 或 `types/cryptoTailStrategy.ts` 中增加策略与触发记录类型 |
|
||||
| 多语言 | `frontend/src/locales/{zh-CN,zh-TW,en}/common.json` 中增加 `cryptoTailStrategy.*` |
|
||||
|
||||
---
|
||||
|
||||
## 7. 小结:UI 包含的主要元素
|
||||
|
||||
- **导航**:主导航中「尾盘策略」入口。
|
||||
- **列表页**:标题、钱包提示 Alert、新增按钮(点击前先检查赎回配置,未配置则弹「去配置」简易弹窗)、筛选、表格/卡片(策略名、市场、价格区间、投入方式、状态、最近触发、操作)、加载与空状态。
|
||||
- **未配置赎回弹窗**:简易 Modal,提示依赖自动赎回、需先配置 Builder API Key 与自动赎回;按钮「去配置」(跳转 `/system-settings`)、「取消」。
|
||||
- **表单弹窗**:策略名、账户、市场选择、minPrice/maxPrice、投入方式(比例/固定)、启用开关、提交/取消。
|
||||
- **触发记录**:时间、市场、outcome、触发价格、金额、订单 ID、状态;支持弹窗或独立页。
|
||||
- **通用**:Ant Design 组件、响应式、多语言、formatUSDC、TypeScript 类型。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A 后端/产品要求:自动赎回须支持本策略仓位
|
||||
|
||||
自动赎回逻辑**必须支持赎回由尾盘策略产生的订单所对应的仓位**。即:本策略触发的市价买入会形成仓位,这些仓位在满足「可赎回」条件时,应被纳入现有自动赎回流程并正常发起赎回,不得因来源为「尾盘策略」而被排除。后端实现时需保证:
|
||||
|
||||
- 尾盘策略下单产生的仓位,与跟单/手动下单等来源的仓位一视同仁,参与可赎回查询与批量赎回;
|
||||
- 若当前自动赎回按账户或仓位类型过滤,需将「尾盘策略订单产生的仓位」包含在内。
|
||||
|
||||
这样前端所依赖的「自动赎回」对该策略才完整有效。
|
||||
@@ -1,452 +0,0 @@
|
||||
# 尾盘策略配置指南
|
||||
|
||||
## 一、什么是尾盘策略?
|
||||
|
||||
尾盘策略是一种自动化交易策略,专门用于 Polymarket 加密市场的 **5分钟** 或 **15分钟** "Up or Down" 市场。
|
||||
|
||||
**核心逻辑**:在指定时间窗口内,当市场价格进入您设定的价格区间时,系统会自动以固定价格(0.99)买入,无需手动操作。
|
||||
|
||||
**适用场景**:
|
||||
- 您希望捕捉市场在周期末段的价格波动
|
||||
- 您想自动化执行交易,避免手动盯盘
|
||||
- 您对市场走势有一定判断,希望设置条件自动触发
|
||||
|
||||
---
|
||||
|
||||
## 二、策略工作原理
|
||||
|
||||
### 2.1 基本流程
|
||||
|
||||
```
|
||||
周期开始 → 时间窗口内 → 价格进入区间 → 自动下单
|
||||
```
|
||||
|
||||
1. **周期**:每个市场按固定周期运行(5分钟或15分钟)
|
||||
- 5分钟市场:每5分钟为一个周期(如 10:00、10:05、10:10...)
|
||||
- 15分钟市场:每15分钟为一个周期(如 10:00、10:15、10:30...)
|
||||
|
||||
2. **时间窗口**:您可以在周期内设置一个时间段
|
||||
- 例如:15分钟市场,设置窗口为「3分钟~12分钟」
|
||||
- 表示:从周期开始后第3分钟到第12分钟之间才会触发
|
||||
|
||||
3. **价格区间**:设置触发价格范围
|
||||
- 例如:最低价 0.50,最高价 0.80
|
||||
- 表示:当市场价格在 0.50~0.80 之间时才会触发
|
||||
|
||||
4. **自动下单**:满足条件后,系统自动以 0.99 的价格买入
|
||||
|
||||
### 2.2 重要限制
|
||||
|
||||
- **每周期最多触发一次**:同一个周期内,即使多次满足条件,也只下单一次
|
||||
- **固定下单价格**:所有订单都以 0.99 的价格提交
|
||||
- **需要单独钱包**:建议使用专门的钱包运行尾盘策略,避免与其他操作(手动交易、跟单等)冲突
|
||||
|
||||
---
|
||||
|
||||
## 三、参数详细说明
|
||||
|
||||
### 3.1 基础参数
|
||||
|
||||
| 参数 | 说明 | 必填 | 示例 |
|
||||
|------|------|------|------|
|
||||
| **账户** | 选择用于交易的钱包账户 | ✅ | 账户A |
|
||||
| **策略名称** | 给策略起个名字,方便识别 | ❌ | "BTC 15分钟尾盘策略" |
|
||||
| **市场** | 选择要交易的市场(5分钟或15分钟) | ✅ | btc-updown-15m |
|
||||
|
||||
### 3.2 周期设置
|
||||
|
||||
| 参数 | 说明 | 必填 | 示例 |
|
||||
|------|------|------|------|
|
||||
| **周期长度** | 由选择的市场自动确定 | ✅ | 15分钟(900秒) |
|
||||
| **时间窗口开始** | 从周期起点算起,多少分钟后开始监听 | ✅ | 3分0秒 |
|
||||
| **时间窗口结束** | 从周期起点算起,多少分钟后停止监听 | ✅ | 12分0秒 |
|
||||
|
||||
**时间窗口说明**:
|
||||
- 5分钟市场:可选 0~5 分钟内的任意时间段
|
||||
- 15分钟市场:可选 0~15 分钟内的任意时间段
|
||||
- **开始时间必须 ≤ 结束时间**
|
||||
- 窗口外的时间即使价格满足也不会触发
|
||||
|
||||
**示例**:
|
||||
- 15分钟市场,窗口「3分0秒 ~ 12分0秒」
|
||||
- 周期开始后 0~3 分钟:不监听
|
||||
- 周期开始后 3~12 分钟:监听价格,满足条件即触发
|
||||
- 周期开始后 12~15 分钟:不监听
|
||||
|
||||
### 3.3 价格区间
|
||||
|
||||
| 参数 | 说明 | 必填 | 取值范围 | 示例 |
|
||||
|------|------|------|----------|------|
|
||||
| **最低价 (minPrice)** | 触发的最低价格 | ✅ | 0~1 | 0.50 |
|
||||
| **最高价 (maxPrice)** | 触发的最高价格 | ❌ | 0~1,默认1 | 0.80 |
|
||||
|
||||
**价格区间说明**:
|
||||
- 价格范围是 0~1 之间的小数
|
||||
- 当市场价格在 [最低价, 最高价] 区间内时才会触发
|
||||
- 如果不填最高价,默认使用 1.0(即只要价格 ≥ 最低价就触发)
|
||||
|
||||
**示例**:
|
||||
- 最低价 0.50,最高价 0.80
|
||||
- 价格 0.45:不触发(低于最低价)
|
||||
- 价格 0.60:触发 ✅(在区间内)
|
||||
- 价格 0.85:不触发(高于最高价)
|
||||
|
||||
### 3.4 投入金额
|
||||
|
||||
| 参数 | 说明 | 必填 | 示例 |
|
||||
|------|------|------|------|
|
||||
| **投入方式** | 选择按比例或固定金额 | ✅ | 按比例 / 固定金额 |
|
||||
| **比例 (%)** | 按账户余额的百分比投入 | 条件必填 | 10%(账户有100 USDC,投入10 USDC) |
|
||||
| **固定金额 (USDC)** | 每次固定投入的金额 | 条件必填 | 50 USDC |
|
||||
|
||||
**投入方式说明**:
|
||||
|
||||
**方式一:按比例 (RATIO)**
|
||||
- 每次触发时,按账户当前可用余额的百分比投入
|
||||
- 例如:账户有 100 USDC,设置比例 10%
|
||||
- 第1次触发:投入 10 USDC
|
||||
- 第2次触发:如果余额变为 90 USDC,投入 9 USDC
|
||||
- **优点**:自动适应账户余额变化
|
||||
- **缺点**:每次投入金额可能不同
|
||||
|
||||
**方式二:固定金额 (FIXED)**
|
||||
- 每次触发时,固定投入指定金额
|
||||
- 例如:设置固定金额 50 USDC
|
||||
- 每次触发都投入 50 USDC
|
||||
- **优点**:投入金额稳定,便于管理
|
||||
- **缺点**:需要确保账户余额充足
|
||||
|
||||
**注意事项**:
|
||||
- 最小下单金额:至少 1 USDC
|
||||
- 如果账户余额不足,下单会失败并记录失败原因
|
||||
|
||||
### 3.5 最小价差(高级功能)
|
||||
|
||||
最小价差功能用于过滤市场波动,只在币安 BTC/USDC 价格波动达到一定幅度时才触发。
|
||||
|
||||
| 参数 | 说明 | 必填 | 示例 |
|
||||
|------|------|------|------|
|
||||
| **最小价差模式** | 选择价差校验方式 | ✅ | 无 / 固定 / 自动 |
|
||||
| **最小价差值** | 固定模式时填写 | 条件必填 | 30 |
|
||||
|
||||
**三种模式说明**:
|
||||
|
||||
**模式一:无 (NONE)**
|
||||
- 不进行价差校验
|
||||
- 只要时间窗口和价格区间满足就触发
|
||||
- **适合**:不关心币安价格波动,只看 Polymarket 价格
|
||||
|
||||
**模式二:固定 (FIXED)**
|
||||
- 设置一个固定的最小价差值(单位:USDC)
|
||||
- 当币安 BTC/USDC 的 K 线价差(|收盘价 - 开盘价|)≥ 设定值时,才触发
|
||||
- **示例**:设置 30
|
||||
- 周期内币安 K 线价差 ≥ 30:触发 ✅
|
||||
- 周期内币安 K 线价差 < 30:不触发
|
||||
- **适合**:您知道期望的价差阈值
|
||||
|
||||
**模式三:自动 (AUTO)**
|
||||
- 系统根据历史 20 根 K 线自动计算最小价差
|
||||
- 计算逻辑:
|
||||
1. 获取最近 20 根 K 线(与策略周期一致)
|
||||
2. 按方向筛选(Up 方向只看上涨的 K 线,Down 方向只看下跌的 K 线)
|
||||
3. 剔除异常值(使用 IQR 方法)
|
||||
4. 计算平均价差 × 0.8 作为最小价差
|
||||
- **适合**:希望根据历史数据自动调整,无需手动设置
|
||||
|
||||
**价差说明**:
|
||||
- 价差 = |收盘价 - 开盘价|
|
||||
- 例如:开盘价 50000,收盘价 50030,价差 = 30
|
||||
- 价差越大,说明该周期内价格波动越大
|
||||
|
||||
---
|
||||
|
||||
## 四、配置示例
|
||||
|
||||
### 示例1:简单策略(5分钟市场)
|
||||
|
||||
**场景**:在 5 分钟市场的最后 2 分钟,如果价格低于 0.60,自动买入 10 USDC
|
||||
|
||||
**配置**:
|
||||
```
|
||||
账户:账户A
|
||||
策略名称:BTC 5分钟简单策略
|
||||
市场:btc-updown-5m
|
||||
时间窗口:3分0秒 ~ 5分0秒
|
||||
最低价:0.00
|
||||
最高价:0.60
|
||||
投入方式:固定金额
|
||||
固定金额:10 USDC
|
||||
最小价差模式:无
|
||||
启用状态:开启
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 周期开始后 0~3 分钟:不监听
|
||||
- 周期开始后 3~5 分钟:如果价格 ≤ 0.60,自动买入 10 USDC
|
||||
|
||||
---
|
||||
|
||||
### 示例2:比例投入策略(15分钟市场)
|
||||
|
||||
**场景**:在 15 分钟市场的中段(5~10分钟),如果价格在 0.40~0.70 之间,投入账户余额的 15%
|
||||
|
||||
**配置**:
|
||||
```
|
||||
账户:账户B
|
||||
策略名称:BTC 15分钟比例策略
|
||||
市场:btc-updown-15m
|
||||
时间窗口:5分0秒 ~ 10分0秒
|
||||
最低价:0.40
|
||||
最高价:0.70
|
||||
投入方式:按比例
|
||||
比例:15%
|
||||
最小价差模式:无
|
||||
启用状态:开启
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 假设账户余额 100 USDC
|
||||
- 周期开始后 5~10 分钟:如果价格在 0.40~0.70 之间,自动买入约 15 USDC(100 × 15%)
|
||||
|
||||
---
|
||||
|
||||
### 示例3:带价差过滤的策略(15分钟市场)
|
||||
|
||||
**场景**:在 15 分钟市场的后段(10~14分钟),如果价格在 0.50~0.80 之间,且币安价差 ≥ 50,投入 20 USDC
|
||||
|
||||
**配置**:
|
||||
```
|
||||
账户:账户C
|
||||
策略名称:BTC 15分钟价差策略
|
||||
市场:btc-updown-15m
|
||||
时间窗口:10分0秒 ~ 14分0秒
|
||||
最低价:0.50
|
||||
最高价:0.80
|
||||
投入方式:固定金额
|
||||
固定金额:20 USDC
|
||||
最小价差模式:固定
|
||||
最小价差值:50
|
||||
启用状态:开启
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 周期开始后 10~14 分钟:同时满足以下条件才触发
|
||||
1. 价格在 0.50~0.80 之间 ✅
|
||||
2. 币安 BTC/USDC 价差 ≥ 50 ✅
|
||||
- 如果价差只有 30,即使价格满足也不会触发
|
||||
|
||||
---
|
||||
|
||||
### 示例4:自动价差策略(15分钟市场)
|
||||
|
||||
**场景**:在 15 分钟市场的前段(2~8分钟),如果价格在 0.30~0.90 之间,投入账户余额的 20%,价差由系统自动计算
|
||||
|
||||
**配置**:
|
||||
```
|
||||
账户:账户D
|
||||
策略名称:BTC 15分钟自动价差策略
|
||||
市场:btc-updown-15m
|
||||
时间窗口:2分0秒 ~ 8分0秒
|
||||
最低价:0.30
|
||||
最高价:0.90
|
||||
投入方式:按比例
|
||||
比例:20%
|
||||
最小价差模式:自动
|
||||
启用状态:开启
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 系统会根据历史 20 根 K 线自动计算最小价差
|
||||
- 周期开始后 2~8 分钟:同时满足以下条件才触发
|
||||
1. 价格在 0.30~0.90 之间 ✅
|
||||
2. 币安 BTC/USDC 价差 ≥ 系统计算的最小价差 ✅
|
||||
|
||||
---
|
||||
|
||||
## 五、常见问题
|
||||
|
||||
### Q1:策略什么时候会触发?
|
||||
|
||||
**A**:需要同时满足以下条件:
|
||||
1. ✅ 当前时间在时间窗口内
|
||||
2. ✅ 市场价格在 [最低价, 最高价] 区间内
|
||||
3. ✅ 本周期尚未触发过(每周期最多触发一次)
|
||||
4. ✅ 如果设置了最小价差,币安价差也需要满足条件
|
||||
|
||||
### Q2:为什么我的策略没有触发?
|
||||
|
||||
**可能原因**:
|
||||
1. **时间窗口不对**:当前时间不在设定的时间窗口内
|
||||
2. **价格不在区间**:市场价格不在 [最低价, 最高价] 范围内
|
||||
3. **本周期已触发**:该周期已经触发过一次,不会再触发
|
||||
4. **价差不满足**:如果设置了最小价差,币安价差未达到要求
|
||||
5. **账户余额不足**:账户余额小于设定的投入金额
|
||||
6. **策略未启用**:检查策略的启用状态是否为"开启"
|
||||
|
||||
### Q3:每周期最多触发一次是什么意思?
|
||||
|
||||
**A**:每个周期(5分钟或15分钟)内,即使多次满足条件,也只下单一次。
|
||||
|
||||
**示例**:
|
||||
- 15分钟市场,周期从 10:00 开始
|
||||
- 10:05 时价格满足条件,触发下单 ✅
|
||||
- 10:08 时价格再次满足条件,但不会再次下单(本周期已触发)
|
||||
- 10:15 开始新周期,可以再次触发
|
||||
|
||||
### Q4:固定金额和按比例有什么区别?
|
||||
|
||||
**固定金额**:
|
||||
- 每次触发都投入相同金额
|
||||
- 例如:设置 50 USDC,每次都是 50 USDC
|
||||
- 需要确保账户余额充足
|
||||
|
||||
**按比例**:
|
||||
- 每次触发时按账户余额的百分比投入
|
||||
- 例如:设置 10%,账户有 100 USDC 时投入 10 USDC,余额变为 90 USDC 后下次投入 9 USDC
|
||||
- 自动适应余额变化
|
||||
|
||||
### Q5:最小价差功能有什么用?
|
||||
|
||||
**A**:最小价差用于过滤市场波动,只在币安 BTC/USDC 价格波动达到一定幅度时才触发。
|
||||
|
||||
**场景**:
|
||||
- 如果市场波动很小(价差 < 设定值),可能不值得交易
|
||||
- 通过设置最小价差,可以避免在波动过小时触发
|
||||
|
||||
**三种模式选择建议**:
|
||||
- **无**:不关心币安价格波动,只看 Polymarket 价格
|
||||
- **固定**:您知道期望的价差阈值(例如:至少波动 30 USDC 才值得交易)
|
||||
- **自动**:希望根据历史数据自动调整,无需手动设置
|
||||
|
||||
### Q6:为什么建议使用单独的钱包?
|
||||
|
||||
**A**:避免以下问题:
|
||||
1. **余额变化**:如果钱包同时用于手动交易,余额变化可能影响策略执行
|
||||
2. **仓位冲突**:手动交易和策略交易可能产生冲突
|
||||
3. **管理混乱**:难以区分哪些订单是策略产生的,哪些是手动产生的
|
||||
|
||||
**建议**:创建一个专门的钱包,只用于尾盘策略。
|
||||
|
||||
### Q7:下单价格为什么是固定的 0.99?
|
||||
|
||||
**A**:这是策略的设计特点:
|
||||
- 0.99 是市场中的最高价格(接近 1.0)
|
||||
- 以最高价买入可以确保订单快速成交
|
||||
- 虽然买入价格较高,但策略的核心是捕捉市场波动,而非追求最优价格
|
||||
|
||||
### Q8:策略需要依赖自动赎回功能吗?
|
||||
|
||||
**A**:是的,尾盘策略依赖自动赎回功能。
|
||||
|
||||
**原因**:
|
||||
- 策略下单后会形成仓位
|
||||
- 这些仓位需要在市场结算后自动赎回
|
||||
- 如果未配置自动赎回,仓位可能无法及时赎回
|
||||
|
||||
**配置要求**:
|
||||
- 在「系统设置」中配置 Builder API Key
|
||||
- 开启自动赎回功能
|
||||
|
||||
---
|
||||
|
||||
## 六、注意事项
|
||||
|
||||
### 6.1 账户要求
|
||||
|
||||
- ✅ 账户必须配置 API Key、API Secret、API Passphrase
|
||||
- ✅ 账户必须有足够的 USDC 余额
|
||||
- ✅ 建议使用专门的钱包,避免与其他操作冲突
|
||||
|
||||
### 6.2 时间窗口设置
|
||||
|
||||
- ⚠️ 开始时间必须 ≤ 结束时间
|
||||
- ⚠️ 时间窗口不能超出周期长度(5分钟市场 ≤ 5分钟,15分钟市场 ≤ 15分钟)
|
||||
- ⚠️ 建议设置合理的时间窗口,避免在周期开始或结束时触发
|
||||
|
||||
### 6.3 价格区间设置
|
||||
|
||||
- ⚠️ 最低价必须 ≤ 最高价
|
||||
- ⚠️ 价格范围是 0~1 之间的小数
|
||||
- ⚠️ 建议根据市场情况设置合理的价格区间
|
||||
|
||||
### 6.4 投入金额设置
|
||||
|
||||
- ⚠️ 最小下单金额:至少 1 USDC
|
||||
- ⚠️ 确保账户余额充足,避免下单失败
|
||||
- ⚠️ 按比例模式:注意账户余额变化对投入金额的影响
|
||||
|
||||
### 6.5 最小价差设置
|
||||
|
||||
- ⚠️ 固定模式:需要填写合理的价差值(单位:USDC)
|
||||
- ⚠️ 自动模式:系统会在周期开始时自动计算,无需手动设置
|
||||
- ⚠️ 价差设置过大可能导致策略难以触发
|
||||
|
||||
### 6.6 其他注意事项
|
||||
|
||||
- ⚠️ 策略创建后默认启用,如需暂停可以关闭"启用状态"
|
||||
- ⚠️ 每周期最多触发一次,请合理设置触发条件
|
||||
- ⚠️ 策略依赖自动赎回功能,请确保已配置 Builder API Key
|
||||
- ⚠️ 建议定期查看触发记录,了解策略执行情况
|
||||
|
||||
---
|
||||
|
||||
## 七、策略管理
|
||||
|
||||
### 7.1 查看策略列表
|
||||
|
||||
在「尾盘策略」页面可以查看所有策略:
|
||||
- 策略名称
|
||||
- 市场信息
|
||||
- 时间窗口
|
||||
- 价格区间
|
||||
- 投入方式
|
||||
- 启用状态
|
||||
- 最后触发时间
|
||||
- 总收益、胜率等统计信息
|
||||
|
||||
### 7.2 查看触发记录
|
||||
|
||||
点击策略可以查看详细的触发记录:
|
||||
- 触发时间
|
||||
- 市场价格
|
||||
- 投入金额
|
||||
- 订单ID
|
||||
- 订单状态(成功/失败)
|
||||
- 结算信息(盈亏、胜率等)
|
||||
|
||||
### 7.3 编辑策略
|
||||
|
||||
可以随时修改策略参数:
|
||||
- 时间窗口
|
||||
- 价格区间
|
||||
- 投入方式
|
||||
- 最小价差设置
|
||||
- 启用状态
|
||||
|
||||
**注意**:修改后的策略会在下一个周期生效。
|
||||
|
||||
### 7.4 删除策略
|
||||
|
||||
删除策略后:
|
||||
- 策略配置会被删除
|
||||
- 历史触发记录会保留
|
||||
- 已下单的订单不受影响
|
||||
|
||||
---
|
||||
|
||||
## 八、总结
|
||||
|
||||
尾盘策略是一个强大的自动化交易工具,可以帮助您:
|
||||
|
||||
1. **自动化交易**:无需手动盯盘,系统自动执行
|
||||
2. **精准控制**:通过时间窗口和价格区间精确控制触发条件
|
||||
3. **灵活配置**:支持比例和固定金额两种投入方式
|
||||
4. **风险过滤**:通过最小价差功能过滤市场波动
|
||||
|
||||
**使用建议**:
|
||||
- 初次使用建议从简单策略开始(无价差过滤)
|
||||
- 熟悉后再尝试添加价差过滤功能
|
||||
- 定期查看触发记录,根据实际情况调整策略参数
|
||||
- 使用专门的钱包,避免与其他操作冲突
|
||||
|
||||
**祝您交易顺利!** 🚀
|
||||
Reference in New Issue
Block a user