Browse Source

Merge branch 'main' of http://developer.mtdao.io/Malone/grid_skeleton # Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.

shawn 2 months ago
parent
commit
48da495249
3 changed files with 126 additions and 19 deletions
  1. 1 1
      README.md
  2. 29 18
      apps/runner/src/index.ts
  3. 96 0
      docs/MICRO_GRID_EXECUTION_SCHEDULE.md

+ 1 - 1
README.md

@@ -48,7 +48,7 @@ pnpm typecheck
 
 ## Reference Documentation
 - **快速上手**:`docs/CONFIG_GUIDE.md`
-- **架构与规划**:`docs/ARCHITECTURE_DESIGN.md`, `docs/IMPLEMENTATION_PLAN.md`, `docs/MICRO_GRID_CONTROL.md`, `docs/MICRO_GRID_ROADMAP.md`
+- **架构与规划**:`docs/ARCHITECTURE_DESIGN.md`, `docs/IMPLEMENTATION_PLAN.md`, `docs/MICRO_GRID_CONTROL.md`, `docs/MICRO_GRID_ROADMAP.md`, `docs/MICRO_GRID_EXECUTION_SCHEDULE.md`
 - **接口规范**:`docs/API_CONNECTOR_SPEC.md`, `docs/MODULE_INTERFACES.md`
 - **流程与配置**:`docs/SEQUENCE_FLOW.md`, `docs/CONFIG_REFERENCE.md`
 - **质量保障与运维**:`docs/TESTING_PLAN.md`, `docs/OPERATIONS_PLAYBOOK.md`

+ 29 - 18
apps/runner/src/index.ts

@@ -768,11 +768,11 @@ async function setupWsOrderGateways(options: {
         });
         await gateway.connect();
         adapterRegistry.get(entry.id).attachWsGateway(gateway);
-        const fillsChannel = `fills.${address}`;
-        const ordersChannel = `orders.${address}`;
-        wsClient.subscribeAuthenticated(fillsChannel);
-        wsClient.subscribeAuthenticated(ordersChannel);
-        logger.info({ accountId: entry.id, fillsChannel, ordersChannel }, 'WS order gateway connected and subscribed to channels');
+        const tradesParams = { source: 'account_trades', account: address };
+        const ordersParams = { source: 'account_order_updates', account: address };
+        wsClient.subscribeAuthenticated('account_trades', tradesParams);
+        wsClient.subscribeAuthenticated('account_order_updates', ordersParams);
+        logger.info({ accountId: entry.id, tradesParams, ordersParams }, 'WS order gateway connected and subscribed to channels');
       } catch (error) {
         logger.error({ accountId: entry.id, error }, 'Failed to initialize WS order gateway');
         throw error;
@@ -813,11 +813,12 @@ function handlePrivateMessage(
   const channel = parsed?.channel;
   if (typeof channel !== "string") return;
 
-  if (channel.startsWith("fills.") && channel.endsWith(address)) {
+  if (channel === "account_trades" || (channel.startsWith("fills.") && channel.endsWith(address))) {
     const payload = parsed.data;
     const records = Array.isArray(payload) ? payload : [payload];
     logger.info({ accountId, channel, recordCount: records.length }, 'Received fill message from WebSocket');
     for (const record of records) {
+      if (record?.u && record.u !== address) continue;
       const fill = mapFillPayload(record);
       if (!fill) {
         logger.warn({ accountId, record }, 'Failed to map fill payload');
@@ -831,11 +832,12 @@ function handlePrivateMessage(
     return;
   }
 
-  if (channel.startsWith("orders.") && channel.endsWith(address)) {
+  if (channel === "account_order_updates" || (channel.startsWith("orders.") && channel.endsWith(address))) {
     const payload = parsed.data;
     const records = Array.isArray(payload) ? payload : [payload];
     logger.info({ accountId, channel, recordCount: records.length }, 'Received order update message from WebSocket');
     for (const record of records) {
+      if (record?.u && record.u !== address) continue;
       const update = record ?? {};
       logger.info({ accountId, orderUpdate: update }, 'Processing order update event');
       routeOrder(accountId, update);
@@ -850,25 +852,34 @@ function handlePrivateMessage(
     });
     return;
   }
+
+  logger.debug({ accountId, channel, payload: parsed?.data ?? parsed }, 'Unhandled Pacifica WS message');
 }
 
 function mapFillPayload(data: any): Fill | undefined {
   if (!data) return undefined;
-  const orderId = data.order_id ?? data.orderId;
-  const symbol = data.symbol;
-  const rawSide = (data.side ?? data.direction ?? "").toString().toLowerCase();
-  const side = rawSide === "bid" || rawSide === "buy" ? "buy" : rawSide === "ask" || rawSide === "sell" ? "sell" : undefined;
-  const price = Number(data.price ?? data.px);
-  const sizeRaw = Number(data.size ?? data.amount);
+  const orderId = data.order_id ?? data.orderId ?? data.i ?? data.orderId;
+  const symbol = data.symbol ?? data.s;
+  const rawSideInput = (data.side ?? data.direction ?? data.d ?? data.ts ?? '').toString().toLowerCase();
+  let side: 'buy' | 'sell' | undefined;
+  if (rawSideInput === 'bid' || rawSideInput === 'buy' || rawSideInput.includes('long')) {
+    side = 'buy';
+  } else if (rawSideInput === 'ask' || rawSideInput === 'sell' || rawSideInput.includes('short')) {
+    side = 'sell';
+  }
+  const price = Number(data.price ?? data.px ?? data.p);
+  const sizeRaw = Number(data.size ?? data.sz ?? data.amount ?? data.a);
   if (!orderId || !symbol || !side || !Number.isFinite(price) || !Number.isFinite(sizeRaw)) {
     return undefined;
   }
   const size = Math.abs(sizeRaw);
-  const fee = data.fee !== undefined ? Number(data.fee) : 0;
-  const liquidityRaw = (data.liquidity ?? '').toString().toLowerCase();
-  const liquidity = liquidityRaw === 'taker' ? 'taker' : 'maker';
-  const tradeId = String(data.trade_id ?? data.tradeId ?? `${orderId}-${Date.now()}`);
-  const tsValue = data.ts !== undefined ? Number(data.ts) : Date.now();
+  const fee = data.fee !== undefined ? Number(data.fee) : data.f !== undefined ? Number(data.f) : 0;
+  const liquidityRaw = (data.liquidity ?? data.te ?? '').toString().toLowerCase();
+  const liquidity = liquidityRaw.includes('taker') ? 'taker' : 'maker';
+  const tradeId = String(data.trade_id ?? data.tradeId ?? data.h ?? `${orderId}-${Date.now()}`);
+  const tsValue = data.ts !== undefined && Number.isFinite(Number(data.ts)) ? Number(data.ts)
+    : data.t !== undefined ? Number(data.t)
+    : Date.now();
   const ts = Number.isFinite(tsValue) ? tsValue : Date.now();
   return {
     orderId: String(orderId),

+ 96 - 0
docs/MICRO_GRID_EXECUTION_SCHEDULE.md

@@ -0,0 +1,96 @@
+# 微网格闭环执行排期(2025 Q4)
+
+> 基于 `MICRO_GRID_CONTROL.md` 与 `MICRO_GRID_ROADMAP.md` 的任务拆解,结合当前日期(2025-10-09)给出未来 8 周的实际排期。时间以自然周为单位,可根据人力调整;若节假日影响,请在周会评估后更新本文件。
+
+---
+
+## 0. 前提与角色
+- **起始日期**:2025-10-09(周四),排期从 10 月第 2 周开始。
+- **Sprint 长度**:2 周,周一开始、周五验收。
+- **关键角色**  
+  - `Core Strat`:策略开发负责人  
+  - `Infra`:指标、监控、部署  
+  - `Risk`:RiskEngine / Kill-switch / 风控流程  
+  - `QA/Sandbox`:仿真、沙盒演练、验收  
+  - `Ops`:运维、报警、Runbook
+
+---
+
+## 1. Sprint 1(Telemetry 基线)
+- **时间**:2025-10-13(周一) ~ 2025-10-24(周五)  
+- **目标**:完成 T1 指标采集与监控,打通 Prometheus → Grafana → Alertmanager。
+
+| 日期 | 工作项 | 责任人 | 备注 |
+| --- | --- | --- | --- |
+| 10/13-17 | Prometheus Exporter 接入新指标(`grid_fills_per_minute`, `maker_ratio`, `risk_*` 等)并补充采集窗口配置 | Infra | 对齐 `packages/telemetry` 中新增 Gauge |
+| 10/17 | Grafana 面板原型(库存、成交率、Kill-switch、对冲效率) | Infra / QA | 面板链接 + 面板配置导出 |
+| 10/20-22 | Alertmanager 规则:低成交率、maker 占比、Kill-switch 触发、数据断流 | Infra / Ops | 输出报警清单 & 测试截图 |
+| 10/23 | 10 分钟长跑验证,记录指标截图与报警触发结果 | QA/Sandbox | 包含日志与 Prometheus 截图 |
+| 10/24 | Sprint review:提交验收报告 + README 指标入口更新 | 所有人 | 与 `MICRO_GRID_ROADMAP` T1 验收项对齐 |
+
+**交付物**:Grafana dashboard JSON、Alertmanager 规则、验收报告(附指标截图)。
+
+---
+
+## 2. Sprint 2(核心闭环上线)
+- **时间**:2025-10-27(周一) ~ 2025-11-07(周五)  
+- **目标**:完成 T2/T3 —— 库存闭环与成交率 PI 控制在仿真/沙盒中验证。
+
+| 日期 | 工作项 | 责任人 | 备注 |
+| --- | --- | --- | --- |
+| 10/27-29 | 库存偏置执行器调参(价格外移、数量缩放、taker 对冲) | Core Strat | 结合当前 `GridMaker` 逻辑,确认阈值 |
+| 10/30-31 | 成交率 PI 控制回放脚本(常规 / 高波动场景) | QA/Sandbox | 输出 KPI 曲线 (`δ`, `fills/min`) |
+| 11/03 | 沙盒 12h 演练(低资金账户) | QA/Sandbox / Infra | 记录 `grid_*` & `risk_*` 指标 |
+| 11/04 | 风控联调:确认对冲限额、Kill-switch 记录 | Risk / Core Strat | 产出风控确认纪要 |
+| 11/05-06 | 问题修复与二次验证 | Core Strat / QA | 整理异常与 patch 记录 |
+| 11/07 | Sprint review:验收报告 + 关键日志/截图存档 | 全体 | 参考 `MICRO_GRID_ROADMAP` T2/T3 Checklist |
+
+**交付物**:仿真/沙盒报告、库存偏移配置示例、成交率调节日志。
+
+---
+
+## 3. Sprint 3(结构跟随 + 费用拨盘)
+- **时间**:2025-11-10(周一) ~ 2025-11-21(周五)  
+- **目标**:实现 T4/T5 —— 深度驱动调节、费用拨盘逻辑及可视化。
+
+| 日期 | 工作项 | 责任人 | 备注 |
+| --- | --- | --- | --- |
+| 11/10-12 | 盘口深度采样与调节策略实现(Q 与层数协调) | Core Strat | 与库存闭环共存测试 |
+| 11/13-14 | 费用模型(返佣/手续费/资金费)统一 & 拨盘逻辑 | Core Strat / Risk | 配置化阈值 |
+| 11/17 | Grafana 费用/深度面板、报警阈值 | Infra | `grid_depth_ratio`, `fee_net_bps` 等 |
+| 11/18-20 | 沙盒验证(高深度 / 低深度 / 费用为负场景) | QA/Sandbox | 提供 24h 指标曲线 |
+| 11/21 | Sprint review:调节日志、面板链接、报警记录 | 全体 | 复核 T4/T5 Checklist |
+
+---
+
+## 4. Sprint 4(稳定性护栏)
+- **时间**:2025-11-24(周一) ~ 2025-12-05(周五)  
+- **目标**:落实 T6/T7 —— 队列退避、Kill-switch 演练、性能压测、Runbook。
+
+| 日期 | 工作项 | 责任人 | 备注 |
+| --- | --- | --- | --- |
+| 11/24-27 | 队列排位采集与自动退避策略实现 & 指标输出 | Infra / Core Strat | 集成 rate limiter / GridMaker |
+| 11/28 | Kill-switch 演练(对冲失败、数据断流、Delta 失控) | Risk / Ops / QA | 提供演练报告(含报警截图) |
+| 12/01-03 | 性能压测(多实例 / 高频)、CPU/内存 Profiling | Infra | 调优结论与建议 |
+| 12/04 | Ops Playbook 更新:Kill-switch、降级、回滚步骤 | Ops / Core Strat | 文档路径 `docs/OPERATIONS_PLAYBOOK.md` |
+| 12/05 | Sprint review:提交演练记录、性能报告、Playbook 更新 | 全体 | 完成 T6/T7 验收 |
+
+---
+
+## 5. 持续任务(Post-M4)
+- 多标的协同、极限模式灰度、参数自学习、灰度上线流程等工作,按需求拆入 2025-12 之后的迭代。
+- 保持与 `MICRO_GRID_CONTROL.md` 同步:每次新增策略/监控逻辑需回写文档并更新 Roadmap。
+
+---
+
+## 6. 更新流程
+- 若人力、节奏或优先级变化,需在每周例会上回顾并更新表格,同时:
+  1. 修改 `MICRO_GRID_ROADMAP.md` 对应 Sprint/任务状态;
+  2. 在项目管理工具(issue board)同步任务状态;
+  3. 将变更记录在本文件底部的 **Revision Log**。
+
+### Revision Log
+| 日期 | 描述 | 记录人 |
+| --- | --- | --- |
+| 2025-10-09 | 初版排期(Sprint1~Sprint4) | Codex |
+