# Algonics MVP 产品需求文档（PRD）

| 项 | 内容 |
|---|---|
| 版本 | v1.2（MVP，按 2026-10-27 线框原型冻结）。v1.1：Karlo 已定 C1（不做 Git）、C2（账户平台以 06b 为准），见 13.1。v1.2：一期只支持 Local runner（Lean 用 Podman 跑在 mid-tier 主机上，见第 3 章第 4 条）；远程 runner（VPS）延后，方向见第 11 章 |
| 依据 | 线框原型 https://algonics-wireframes.pages.dev/ （35 个页面/变体，含页内 Annotations） |
| 读者 | 实现方（Cursor 编码代理）、Karlo |
| 约定 | 正文中文；所有 UI 文案、按钮、字段、列名、状态值、标识符保持英文，与原型逐字一致。标注 **假设** 的内容是原型没有明说、由本文补充的推断，实现前可以推翻。 |

> 使用方式：第 7 章每个 Feature（F1–F14）都能单独作为一个实现任务交给 Cursor。领域模型和数据库 schema 由实现方根据第 5 章自行推导，本文不规定表结构、字段类型和 SQL。

---

## 1. 概述与目标

### 1.1 产品定位
Algonics 是 Karlo 个人使用的量化交易平台，底层是 QuantConnect **Lean** 引擎，中间层是一个 **Java mid-tier**。闭环如下：

1. 建自己的行情库（DuckDB）。
2. 注册编译好的 C# 策略 DLL。
3. 回测，然后激活（Activate）通过验证的版本。
4. 用激活版本建 Paper bot 跑模拟。
5. 把 Paper bot 升级（Promote）为 Live bot，下真实订单。
6. 在 Dashboard 监控，必要时一键暂停、停止或清仓。

### 1.2 MVP 目标
- 一条完整的端到端主流程，见第 6 章。
- 钱的安全优先：
  - Live 和 Paper 严格分开。
  - 一个 Live 账户同时只服务一个未停止的 Live bot。
  - 危险操作都要二次确认，清仓类操作要输入 `LIQUIDATE`。
- Dashboard 先回答「一切正常吗？」，再回答「赚了多少？」。

### 1.3 范围
| 范围内（MVP） | 范围外（MVP 不做） |
|---|---|
| Dashboard（Live / Paper 视图、Needs attention、Pause all、Emergency） | Alerts（整个模块砍掉） |
| Data：行情库、Add data、Dataset detail、gap 自动补、Sources（第三方数据商） | 登录、注册、退出（用 Karlo 现有站点，见 F1） |
| Strategies：注册 DLL、版本、回测、结果、激活 | 回测缺数据的提示状态（Missing data / Fill now，已延后） |
| Bots：列表加筛选、添加和编辑、启停、风控、Promote to Live、详情（Overview / Trades / Logs） | Compare（回测对比）、参数表单、Tags、Resume all |
| Accounts（券商和交易所账户） | 账户详情页 |
| Channels（Email / Telegram / Webhook / DingTalk / Lark） | Settings 页（已规划未画，见 F1 依赖） |
| | 审计日志、Duplicate bot、max open trades、消息模板、免打扰、限流 |

---

## 2. 术语

| 术语 | 含义 |
|---|---|
| Library | 自有行情库，存在 Algonics 服务器上的 DuckDB 里。回测和 Paper bot 都从这里读数据，从不直接读 Source。 |
| Dataset | 行情库的一行，唯一键是 `Asset class · Market · Resolution`，例如 `Equity · US · Daily`。 |
| Resolution | 只有 `Minute` / `Hour` / `Daily` 三种。5m、15m、30m 由 Minute 合成；2h、4h 由 Hour 合成；1w 由 Daily 合成。没有月线、tick 和秒级数据。 |
| Source | 行情的上游。分两类：Brokers & exchanges（用 Accounts 里的密钥，Binance 公共接口不需要密钥），以及 Data vendors（第三方数据商，在 Sources 页管理）。 |
| Gap | 数据范围内缺失的 bar 区间。状态有 open（`Retrying (n/3)`）、`Source has no data`、`Not requested`。 |
| Lean cache | 回测前从 DuckDB 导出的 Lean zip 文件，是可随时丢弃的内部缓存，UI 不展示。 |
| Strategy | 一个 Lean 算法类（完整类名），来自一个程序集（DLL）。 |
| Version (vN) | 策略的不可变版本。算法指纹变化时才生成新版本。 |
| Activated | 版本唯一的一个标记。只有被激活的版本才能用来建 bot。 |
| Run / Backtest | 一次回测，编号 `Run #N`，在同一个策略内唯一。 |
| Bot | 一个运行实例，包括：固定的版本、参数、账户、Mode（`Paper` / `Live`）、风控和 Channels。 |
| Paper | 用实时行情、在本地模拟成交，不下真单。 |
| Live | 在账户上下真实订单。 |
| Pause | 只停止开新仓。策略继续管理已有持仓，止盈止损照常执行，不平仓。 |
| Stop | 算法退出。可以选择撤单、平仓。 |
| Liquidate | 撤掉全部挂单，按市价平掉全部持仓，然后变为 Stopped。 |
| Account | 券商或交易所账户（代码和路由里叫 connections）。 |
| Channel | 通知渠道。 |

---

## 3. 架构约束（必须遵守）

1. **行情库**用 DuckDB，放在 Algonics 服务器上。**Java mid-tier 是唯一写入者**。下载、补 gap、删除都只能经 mid-tier 写入。
2. **回测数据流**依次为：`Backtest` → 检查行情库覆盖 → 导出到 Lean cache（zip）→ 运行 Lean。
   - 缓存按 symbol × resolution × 日期组织，**不按策略组织**。
   - 只有新增或变更的数据才重新导出。
3. **策略**是外部编译好的 .NET DLL。
   - 扫描只用 `System.Reflection.MetadataLoadContext` 读元数据，**不执行任何策略代码**。
   - 首个版本不运行 `dotnet build`（在 Rider 里构建）。
   - DLL 按 SHA-256 存进平台存储。
4. **Runner**：一期只有 Local runner，是隐式的（界面上不出现 runner 选择）。
   - Lean 在 Java mid-tier 所在主机上本地运行，容器用 **Podman**（不用 Docker）。回测和 bot 都走这个 Local runner。
   - 远程 runner（VPS）延后，见第 11 章；一期的 runner 调用最好封装在一个接口后面，方便以后接远程 runner（**假设**）。
5. **密钥**：账户密钥、数据商 API key、Channel 密钥都加密存储，保存后不再明文返回，只显示末 4 位。
6. **时区**：界面时间一律用 Taipei 时间（UTC+8）。"Today" 从 00:00 Taipei 起算。
7. **服务重启**：重启前处于 Running 或 Paused 的 bot 自动恢复到原状态。
8. **UI 规范**：
   - MUI 默认尺寸，卡片用 Portenza 风格（圆角 15px，轻阴影）。
   - 英文界面，标题用 sentence case。
   - 新增类按钮一律叫 "Add"，不用 "Create" 或 "New"。
   - 钱一律叫 "Balance"，不用 "Equity"。
   - P&L 标签只用 `P&L Today` / `P&L YTD` / `P&L Total`。
9. **认证**：沿用 Karlo 现有站点的登录（见 F1）。偏好设置"按用户记住"，所以仍然需要用户身份。

---

## 4. 信息架构与导航

### 4.1 侧边栏
```
Dashboard
QUANT
  Data          (/data；Sources 页没有侧边栏项，打开时仍高亮 Data)
  Strategies    (/strategies)
  Bots          (/bots)
CONFIG
  Accounts      (原型路由 06-connections)
  Channels
  Settings      (规划中，未画)
```

### 4.2 页面清单
线框基址是 `https://algonics-wireframes.pages.dev/`。

| ID | 名称 | 类型 | 线框链接 | Feature |
|---|---|---|---|---|
| 01 | Dashboard（Live，默认视图，含状态 A/B/C/D） | 页面 | [01-dashboard](https://algonics-wireframes.pages.dev/01-dashboard) | F14 |
| 01b | Pause all 确认 | 对话框 | [01b-pause-all](https://algonics-wireframes.pages.dev/01b-pause-all) | F14 |
| 01c | Stop all 确认 | 对话框 | [01c-stop-all](https://algonics-wireframes.pages.dev/01c-stop-all) | F14 |
| 01d | Liquidate all 确认 | 对话框 | [01d-liquidate-all](https://algonics-wireframes.pages.dev/01d-liquidate-all) | F14 |
| 01e | Dashboard，Paper 视图 | 页面状态 | [01e-dashboard-paper](https://algonics-wireframes.pages.dev/01e-dashboard-paper) | F14 |
| 01f | Dashboard 磁贴版（仅作对比，不实现） | 对比稿 | [01f-dashboard-tiles](https://algonics-wireframes.pages.dev/01f-dashboard-tiles) | — |
| 02 | Data 列表 | 页面 | [02-data](https://algonics-wireframes.pages.dev/02-data) | F4 |
| 02b | Add data | 对话框 | [02b-data-add](https://algonics-wireframes.pages.dev/02b-data-add) | F4 |
| 02c | Dataset detail | 页面 | [02c-data-detail](https://algonics-wireframes.pages.dev/02c-data-detail) | F5 |
| 02d | Sources | 页面 | [02d-data-sources](https://algonics-wireframes.pages.dev/02d-data-sources) | F3 |
| 02e | Add source | 对话框 | [02e-data-add-source](https://algonics-wireframes.pages.dev/02e-data-add-source) | F3 |
| 03 | Strategies 列表 | 页面 | [03-strategies](https://algonics-wireframes.pages.dev/03-strategies) | F6 |
| 03b | Add strategy，第 1 步 Source | 对话框 | [03b-add-strategy-source](https://algonics-wireframes.pages.dev/03b-add-strategy-source) | F6 |
| 03c | Add strategy，第 2 步 Review | 对话框 | [03c-add-strategy-review](https://algonics-wireframes.pages.dev/03c-add-strategy-review) | F6 |
| 03d | List settings | 对话框 | [03d-list-settings](https://algonics-wireframes.pages.dev/03d-list-settings) | F6 |
| 03e | 策略页 / 回测结果 Overview | 页面 | [03e-strategy-results](https://algonics-wireframes.pages.dev/03e-strategy-results) | F7、F8 |
| 03f | Version history | 对话框 | [03f-version-history](https://algonics-wireframes.pages.dev/03f-version-history) | F7 |
| 03g | Backtests drawer | 抽屉 | [03g-backtests-drawer](https://algonics-wireframes.pages.dev/03g-backtests-drawer) | F8 |
| 03i | Run backtest（Run with options…） | 对话框 | [03i-new-backtest](https://algonics-wireframes.pages.dev/03i-new-backtest) | F8 |
| 03j | 回测 Trades 子标签 | 页面状态 | [03j-strategy-results-trades](https://algonics-wireframes.pages.dev/03j-strategy-results-trades) | F8 |
| 03k | 回测 Logs 子标签 | 页面状态 | [03k-strategy-results-logs](https://algonics-wireframes.pages.dev/03k-strategy-results-logs) | F8 |
| 03l | Deactivate 确认 | 对话框 | [03l-deactivate-confirm](https://algonics-wireframes.pages.dev/03l-deactivate-confirm) | F7 |
| 04 | Bots 列表 | 页面 | [04-bots](https://algonics-wireframes.pages.dev/04-bots) | F9 |
| 04g | Bots 列表（带筛选进入） | 页面状态 | [04g-bots-filtered](https://algonics-wireframes.pages.dev/04g-bots-filtered) | F9 |
| 04b | Add bot / Edit bot | 对话框 | [04b-new-bot](https://algonics-wireframes.pages.dev/04b-new-bot) | F10 |
| 04c | Bot 页 Overview | 页面 | [04c-bot-detail](https://algonics-wireframes.pages.dev/04c-bot-detail) | F12 |
| 04d | Bot 页 Trades | 页面状态 | [04d-bot-trades](https://algonics-wireframes.pages.dev/04d-bot-trades) | F12 |
| 04e | Bot 对话框（Stop、Live start、Close、Liquidate、Promote、Restart、Delete） | 对话框集 | [04e-bot-dialogs](https://algonics-wireframes.pages.dev/04e-bot-dialogs) | F11 |
| 04f | Bot 页 Logs | 页面状态 | [04f-bot-logs](https://algonics-wireframes.pages.dev/04f-bot-logs) | F12 |
| 06 | Accounts 列表 | 页面 | [06-connections](https://algonics-wireframes.pages.dev/06-connections) | F2 |
| 06b | Add account / Edit account | 对话框 | [06b-add-connection](https://algonics-wireframes.pages.dev/06b-add-connection) | F2 |
| 06c | Delete account（被阻止 / 正常） | 对话框 | [06c-delete-connection](https://algonics-wireframes.pages.dev/06c-delete-connection) | F2 |
| 07 | Channels 列表 | 页面 | [07-channels](https://algonics-wireframes.pages.dev/07-channels) | F13 |
| 07b | Add channel / Edit channel | 对话框 | [07b-add-channel](https://algonics-wireframes.pages.dev/07b-add-channel) | F13 |
| 07c | Delete channel（被阻止 / 正常） | 对话框 | [07c-delete-channel](https://algonics-wireframes.pages.dev/07c-delete-channel) | F13 |

没有 05 号页面（原 Alerts 已移除）。

### 4.3 URL 约定（原型中已出现的）
- `/`：Dashboard。
- `/data`：Data 列表。
- `/strategies`：策略列表。
  - `/strategies/:slug`：显示最近一次已完成的回测。
  - `/strategies/:slug/runs/:n`：显示某次回测，可加 `/trades`、`/logs` 子标签。
- `/bots`：Bots 列表，筛选参数是 `?mode=live&status=error&strategy=vegas-tunnel`。
- `/bots/:slug`：Bot 页。
- **假设**：Dataset detail 用 `/data/:datasetId`，Sources 用 `/data/sources`，Accounts 用 `/accounts`，Channels 用 `/channels`。

---

## 5. 业务对象与关系

> 只描述业务事实、关系、不变量和生命周期，表结构由实现方推导。

### 5.1 关系总览
```
User ─1:1─ Preferences (Dashboard 模式与区间、Show archived 等)
Account 1 ── * Bot                    (Bot 的 Account 创建后不可改)
Account 1 ── 0..1 "当前 Live bot"     (未 Stopped 的 Live bot 至多 1 个)
Account 0..1 ── * Dataset.Source      (Broker/Exchange 类 Source 借用账户密钥)
DataVendor 0..1 ── * Dataset.Source   (第三方数据商；每个 vendor 至多一个)
Dataset 1 ── * DatasetSymbol 1 ── * GapRange
Dataset 1 ── * DownloadJob
Strategy 1 ── * StrategyVersion 1 ── * BacktestRun
Strategy 1 ── * Bot ; StrategyVersion(activated) 1 ── * Bot (pinned)
Bot * ── * Channel
Bot 1 ── * Position / Order / ClosedTrade / LogLine / BalanceSnapshot
```

### 5.2 实体

**Account（券商或交易所账户）**
- 属性：
  - `Name`：唯一。
  - `Platform`：`Alpaca` / `Tradier` / `Binance` / `Kraken`。只支持这四个平台（已定 D2）。保存后不可改。
  - `Environment`：`Live` / `Paper` / `Testnet`。MVP 全是 Live，另外两个是预留值。
  - 凭证：按平台不同，加密存储。
  - `Notes`。
  - Last test：结果、时间、错误信息。
- 不变量：
  - 同时最多有 1 个**未 Stopped** 的 Live bot 使用它（Starting、Running、Paused、Stopping、Error 都算"未 Stopped"）。
  - Paper bot 共享账户的数量不限，它们只读行情。
  - 只要有未 Stopped 的 bot（Live 或 Paper）在用，就不能删除。
- 不保存余额、权限、手续费等级等能力信息。余额由 Dashboard 和 Bot 运行时实时从券商读取。

**DataVendor（Source，第三方数据商）**
- 属性：
  - Vendor：`MarketData.app`（Equity、Option）或 `Polygon`（Crypto、Equity、Option）。每个 vendor 最多一条记录。
  - API key：加密存储。
  - Supports：支持的资产类别。
  - Last test。
- 只提供数据，从不交易。
- 有 Dataset 把它作为 Source 时不能删除。

**Dataset**
- 唯一键：`Asset class` × `Market` × `Resolution`。
- 属性：
  - 默认 Source：Account、Binance public 或 DataVendor 之一。
  - `Auto-update`：默认关闭。
  - Last updated：完整的日期时间。
  - Size：在 DuckDB 中的大小。
  - Range：所有 symbol 中最早 bar 到最晚 bar。
  - Bars。
  - 状态：派生值，见 5.3.4。
- 对同一组合再次 Add data，只会扩展这一行，不会新建。
- Available timeframes 是派生值：
  - Minute → `1m · 5m · 15m · 30m`
  - Hour → `1h · 2h · 4h`
  - Daily → `1d · 1w`
- Option 数据集只允许 Daily。

**DatasetSymbol**
- Symbol 属性：From、To、Bars、Gaps（只计 open gap 的数量）、Size。
- Option 数据集记录的是 Underlying，另外带 Filter（`≤ N DTE · ±P%`）。
  - 合约按 underlying 一起存，每个合约每天一行。
  - Filter 在添加时确定，只能通过再次 Add data 放宽。
  - 过滤规则逐日生效：每天只取到期 ≤ N 天、行权价在当天收盘价 ±P% 以内的合约。

**GapRange**：某个 symbol 的缺失区间。状态有 open（带重试次数 n/3）、`Source has no data`、`Not requested`，状态机见 5.3.5。

**DownloadJob**
- 触发方式：Add data、Update now、Auto-update（每天 06:00 Taipei）、Fill gaps。
- 范围：一个 dataset，或其中选中的 symbols。
- 有进度百分比。失败时记录失败原因，例如 `Alpaca 429 Too Many Requests, 5 retries`。
- 在后台运行，用户可以离开页面。

**Strategy**
- 属性：
  - Display name：默认取类名，去掉末尾的 `Strategy` / `Algorithm`。
  - 完整类名（Lean `algorithm-type-name`）。
  - Description。
  - Source：`Local folder`（服务器上的项目路径）或 `Upload DLL`。只有这两种，不支持 Git（已定 D1）。
  - Registered 日期。
  - Status：状态机见 5.3.1。
  - Data requirements：首次回测后学到的 symbols 和 resolution，用来显示数据覆盖提示。
  - 上次回测设置：最近一次使用的 period。

**StrategyVersion (vN)**
- 属性：
  - 程序集 SHA-256，DLL 按这个哈希存储。
  - 算法指纹：类的 IL、字段、参数签名。
  - Created 时间。
  - 来源：`Folder registered`、`Folder auto on backtest` 或 `Upload`。
  - Built 时间：DLL 构建时间。
  - 参数清单及默认值（从 DLL 扫描得到）。
  - 策略声明的指标（例如 EMA12/144/169），用于 Bot 页图例。
  - `Activated` 标记和激活时间。
- 不变量：
  - 版本不可编辑。
  - 只要有回测或 bot 引用它，就不能删除。
  - 程序集哈希和算法指纹**都变化**才生成 vN+1。不使用 AssemblyVersion。

**BacktestRun**
- 属性：
  - `Run #N`：在策略内唯一。URL 只用 run 号，版本可由 run 推出。
  - 版本。
  - Period：来自代码（`from code`），或在 Run with options 里指定。
  - Started、Ended、Duration。
  - 状态：Running、Completed、Failed。
  - 进度和 ETA。
  - Lean statistics：共 30 项，其中 8 项核心指标。
  - 资金曲线、回撤、成交（round trips）、完整 Lean 日志、结果 JSON。
- 删除回测不影响版本的激活标记。

**Bot**
- 属性：
  - `Name`：唯一。
  - Strategy：创建后不可改。
  - Version：固定版本，只能选已激活版本，可以编辑。
  - Mode：`Paper` / `Live`，创建后不可改；转 Live 要通过 Promote，Promote 会新建一个 bot。
  - Account：创建后不可改。
  - Parameters。
  - Limits：
    - Live：Max capital（金额或账户百分比；留空表示整个账户）。
    - Paper：Initial cash，默认 100,000，单位是账户币种。
    - Max loss：金额或百分比，附带 "also close all positions" 选项。
    - Daily max loss：金额或百分比。
  - Channels：多选。
  - Description。
  - Status：状态机见 5.3.3。
  - 状态原因：例如 `Daily loss limit · 13:42`、`Max loss limit · 10-24 03:15`，或报错信息。
  - 运行时信息：heartbeat、last error、uptime（本次 Start 或 Resume 以来的时长）。
  - 首次启动时间：用于 P&L Total 和 YTD。
- 派生值：
  - `Balance` = Max capital（Paper 时为 Initial cash）+ P&L，用账户币种。
  - P&L Today、P&L YTD、P&L Total 的定义见 F14。
- 不变量：
  - 永远不会自动切换到最新版本。
  - 编辑 Version、Parameters、Limits 要重启才生效；Name、Description、Channels 立即生效。

**Position / Order / ClosedTrade / LogLine（属于 Bot）**
- Position：symbol、类型（Stock / Crypto / Option）、方向、数量、均价、最新价、市值、未实现 P&L、止损价。
  - 只显示 bot 自己开的仓位。在券商端手动开的仓不显示。
- Order：下单时间、方向、symbol、类型（Market、Stop-loss limit 等）、数量、价格、状态（Open / Filled / Canceled）、备注（手续费等）。
- ClosedTrade（一买一卖为一个 round trip）：开仓和平仓时间、持仓时长、入场价、出场价、数量、扣费后的 P&L、退出原因（target、exit signal、stop-loss、Close by hand、Liquidate）。
- LogLine：时间、级别、类别（Signals / Orders / Warnings & errors）、文本。保留 30 天。

**Channel**
- 属性：
  - `Name`：唯一。
  - `Type`：`Email` / `Telegram` / `Webhook` / `DingTalk` / `Lark`。保存后不可改。
  - 各类型专属字段：密钥加密存储。
  - `Enabled`：默认开启。
  - Last test。
  - `Notes`。
- 不变量：只要被任何 bot 引用（不论 bot 是否在运行），就不能删除。

**Preferences（按用户）**
- Dashboard 的 Live / Paper 选择（默认总是 Live）。
- Total balance 的时间区间，默认 1M。
- `Show archived strategies`，默认关闭。

### 5.3 状态机

#### 5.3.1 Strategy.Status
```
            first version activated
  Draft ───────────────────────────────▶ Active
    ▲  last activated version deactivated   │
    └───────────────────────────────────────┘
  Active ──Set inactive…──▶ Inactive ──Reactivate──▶ Active
  Draft/Active/Inactive ──Archive… (无 running bot)──▶ Archived ──Unarchive──▶ 归档前的状态
```
- Draft 和 Active 只由版本激活情况派生，不能手动设置。Inactive 和 Archived 只能手动设置。
- 版本激活变化时，手动设置的 Inactive 或 Archived 保持不变。
- 各状态的含义：
  - Draft：可以编辑和回测，不能建 bot。
  - Inactive：不能新增 bot，运行中的 bot 继续运行。
  - Archived：只读；默认在列表和策略选择器里隐藏。

#### 5.3.2 StrategyVersion 激活
- `not activated` → **Activate vN**：只能在**已完成**回测的 run card 上操作，不需要确认，Snackbar 带 `UNDO`。激活后变为 `Activated`。
- `Activated` → **Deactivate**：先弹出 03l 确认，然后变回 `not activated`。
  - 只有当没有任何 bot 引用这个版本时才可以。running 和 stopped 的 bot 都算引用，已删除的 bot 不算。
  - 服务端在确认时再校验一次。
- 多个版本可以同时处于激活状态。

#### 5.3.3 Bot.Status
```
 (Add) ─▶ Stopped ──Start──▶ Starting… ──checks ok──▶ Running
                              │checks fail → 回到 Stopped（Live 在确认框内就拦下）
 Running ──Pause / Daily max loss──▶ Paused ──Resume──▶ Starting… ─▶ Running
 Running/Paused/Starting ──Stop / Max loss / Liquidate──▶ Stopping… ──▶ Stopped
 任意运行态 ──运行错误──▶ Error ──Restart──▶ Starting…
 Error ──Stop?──（见开放问题）
```
- 状态值：`Starting…`、`Running`、`Paused`、`Stopping…`、`Stopped`、`Error`。
- Start、Resume、Restart 都会先跑 pre-start checks（见 F11）。
- 自动转换：
  - 达到 Daily max loss → 自动 Paused，原因是 `Daily loss limit`。必须手动 Resume，最早次日。
  - 达到 Max loss → 自动 Stopped，原因是 `Max loss limit`。如果勾选了相应选项，还会平掉全部持仓。
- 服务器重启后，原来 Running 或 Paused 的 bot 自动恢复到原状态。
- 只有 Stopped 的 bot 才能 Delete。
- Stopping… 的处理：执行 Stop 对话框里勾选的撤单和平仓。
- 股票类 bot 只在常规交易时段交易。

#### 5.3.4 DownloadJob 与 Dataset 状态
```
 Queued(假设) ─▶ Running (Updating n%) ─▶ Succeeded
                                     └──▶ Failed (原因；可 RETRY)
```
Dataset 的 Status 按以下优先级派生：
1. 有任务在跑：`Updating n%`，带进度条。
2. 最近一次失败：`✕ Failed`，悬停显示原因。
3. 存在 open gap：`! n gaps`（橙色）。
4. 以上都没有：`✓ Up to date`。

（优先级顺序为 **假设**，原型中每一行只示例了一种状态。）

#### 5.3.5 GapRange
```
 发现缺失 ─▶ Open: Retrying (1/3) ─(每次 Update 重试失败)─▶ Retrying (2/3) ─▶ … ─3 次失败─▶ Source has no data
     │                                                                     │
     └──── 任意一次补成功 ─▶ 消失（不再是 gap）                              │
 Fill gaps（手动）: Open 或 Source has no data ─▶ Open，重新给 3 次机会 ◀──┘
 用户在 Add data 中把 From 后移跳过的区间 ─▶ Not requested（永不自动补；仅 Add data 或 Fill gaps 可取回，见矛盾 C3）
```
- 每次 Update（定时 Auto-update 或 Update now）都会顺带重试 open gap。
- 计数规则：Gaps 计数和汇总条只统计 open gap。`Source has no data` 和 `Not requested` 不计入，只在 symbol 展开后显示为灰色。

#### 5.3.6 BacktestRun
`Running (n% · ETA)` → `Completed`，或者 → `Failed`（带错误摘要）。
- 判断"Latest"和打开不带 run 号的策略 URL 时，都跳过 Running 和 Failed 的回测。

---

## 6. 核心端到端流程

```
① Add account (06b) ─▶ ② Add data (02b, 下载到 DuckDB) ─▶ ③ Add strategy (03b/03c, 注册 DLL → Draft)
   ─▶ ④ Backtest (03e/03i, 首次回测学到数据需求) ─▶ ⑤ Activate vN (03e, 策略变 Active)
   ─▶ ⑥ Add bot Paper (04b, Stopped) ─▶ Start (Paper 检查全过则无对话框) ─▶ Running
   ─▶ ⑦ Promote to Live (04e, 选 Live 账户与名字, 新 Live bot 为 Stopped) ─▶ Edit 设 Max capital/limits/channels ─▶ Start (Live 必弹确认)
   ─▶ ⑧ 监控 Dashboard (01, Needs attention, 持仓) ─▶ 必要时 Pause all / Emergency ▾ Stop all / Liquidate all
```
- 新用户在 Dashboard 的状态 A 中，按 4 步 checklist 引导完成这条流程：Download data → Add strategy → Add account → Add bot。
- Binance 公共行情不需要账户，所以第②步可以排在第①步前面。

---

## 7. 功能需求（Feature）

> 每个 Feature 的结构都是：目标 → 页面 → 组件与行为 → 规则 → 状态 → 校验 → 验收清单 → 依赖。
> 通用约定：
> - 列表的空状态带主操作按钮。
> - 异步操作的结果用 Snackbar 反馈。
> - 破坏性操作用 error 色按钮。
> - 所有 Dialog 都有 `Cancel`。

### F1 应用框架、导航与通用约定

**目标**：搭好整个应用的外壳和通用约定，让各 Feature 可以直接挂载上来。

**页面**：所有页面共用左侧 Sidebar，参考任意截图，例如 ![01 Dashboard](/prd-images/01-dashboard.png)

**行为**
- Sidebar 的分组和条目见 4.1，当前页高亮。
- 打开 Sources（02d）时高亮 Data。打开 Bot 页和策略页时，分别高亮 Bots 和 Strategies。
- `Settings` 菜单项保留，指向一个占位页。**假设**：占位页显示 "Coming soon"。
- 页头 = 标题 + 副标题 + 右侧操作区；详情页带 Breadcrumbs（如 `Data / Sources`、`Bots / VegasTunnel · BTC`）。
- **认证集成**：
  - 不做登录 UI，复用 Karlo 现有站点的会话和用户身份。
  - 未登录时跳转到现有站点的登录页。
  - **假设**：mid-tier 通过现有站点的 session 或 JWT 识别用户。
  - Preferences 按这个用户保存。
- 格式：
  - 时间统一显示 Taipei 时间。列表里用 `YYYY-MM-DD HH:mm`；相对时间用 "3 s ago" 或 "5 min ago" 这种写法。
  - 金额带币种后缀，例如 `12,348.59 USDT`。
  - 盈亏带正负号，正数绿色、负数红色。

**依赖 Settings（未画，后续实现，先用配置项代替）**
- Default currency，默认 USD，用于 Dashboard 总余额换算。
- FX rates，由管理员维护。
- SMTP sender，供 Email channel 使用。
- **假设**：MVP 先用 mid-tier 的配置文件或环境变量提供这三项。

**状态**：全局 loading、404、离线状态原型没画，见开放问题 Q11。**假设**：页面级用 MUI Skeleton；接口错误用 Snackbar 显示错误并提供 `RETRY`。

**验收**
- [ ] Sidebar 结构和高亮规则与 4.1 一致，Accounts 指向账户页。
- [ ] 未登录会跳转到现有站点登录，登录后能拿到用户 id。
- [ ] 所有时间都按 Asia/Taipei 显示。
- [ ] 金额、正负号、颜色格式统一。

**依赖**：无。这是第一个要实现的 Feature。

---

### F2 Accounts（券商和交易所账户）

**目标**：管理交易账户和凭证。这些账户用于 Live 交易、Paper 行情来源，以及作为 Data 的 Source。

**页面**
- 06 Accounts 列表：![06 Accounts](/prd-images/06-connections.png) https://algonics-wireframes.pages.dev/06-connections
- 06b Add / Edit account：![06b Add account](/prd-images/06b-add-connection.png) https://algonics-wireframes.pages.dev/06b-add-connection
- 06c Delete：![06c Delete account](/prd-images/06c-delete-connection.png) https://algonics-wireframes.pages.dev/06c-delete-connection

**组件、字段与列**
- 06 页头：`Accounts`，右侧 `+ Add account`。
- 表格列：
  - `Name`
  - `Platform`：logo + 名称。
  - `Environment`：MVP 全部为 `Live`。
  - `Last test`：`Success` / `Failed` / `Not tested` / `Testing…`，附时间。Failed 悬停显示错误。
  - `Used by`：bot 数，或 bot 名称链接。
  - `Notes`
  - `⋮`
- 行 `⋮` 菜单：`Test connection`、`Edit`、`Delete`。
- 06b 对话框字段：
  - `Platform`：`Alpaca` / `Tradier` / `Binance` / `Kraken`，只支持这四个（已定 D2）。编辑时锁定。
  - `Name`：必填，唯一。
  - 凭证，按平台不同：
    - Binance：`API key`、`Secret key`
    - Alpaca：`API key ID`、`Secret`
    - Tradier：`Access token`、`Account ID`
    - Kraken：`API key`、`Private key`
  - `Notes`
  - 按钮：`Test connection`、`Save`。

**行为**
- `Test connection`：用表单当前值做一次只读调用。结果显示在表单内（✓ 或 ✕ 加错误信息），同时更新 Last test。
- `Save`：必填项都填了就可以保存，不要求先测试。
- 编辑时，凭证字段显示 `Saved · ends with xxxx`。字段留空表示保留原值。
- 行内 `Test connection`：Last test 先显示 `Testing…`，然后显示结果；Snackbar 反馈。
- `Delete`：
  - 有未 Stopped 的 bot 在用时：弹出 06c 阻止框，列出这些 bot，按钮为 `View bots`（打开 04 并按账户筛选，**假设**）和 `OK`。
  - 否则弹出普通确认框，说明会删除凭证。

**规则**
- 一个账户同一时间最多有 1 个未 Stopped 的 Live bot（在 F10、F11 中强制执行）。
- 账户测试失败会进入 Dashboard 的 Needs attention（F14）。

**状态**：空状态显示 "No accounts yet"，带 `+ Add account`（**假设**文案）；Testing 时有 loading；测试失败时显示内联错误。

**校验**：Name 必填且唯一；所选平台的凭证全部必填；保存时去掉首尾空格。

**验收**
- [ ] 四个平台各自的凭证字段名与上文完全一致。
- [ ] 凭证加密存储，任何接口都不返回明文，只返回末 4 位。
- [ ] 不测试也能保存；测试结果写入 Last test，并显示在列表里。
- [ ] 有未 Stopped 的 bot 在用时 Delete 被阻止，并列出这些 bot。

**依赖**：F1。

---

### F3 Data Sources（第三方数据商）

**目标**：管理只提供行情的第三方 vendor 的 API key，供 Add data 选用。

**页面**
- 02d Sources：![02d Sources](/prd-images/02d-data-sources.png) https://algonics-wireframes.pages.dev/02d-data-sources
- 02e Add source：![02e Add source](/prd-images/02e-data-add-source.png) https://algonics-wireframes.pages.dev/02e-data-add-source

**组件**
- 入口在 02 工具栏右侧的图标按钮（DisplaySettings 图标，tooltip `Sources`）。
- Breadcrumb 为 `Data / Sources`，页头按钮 `+ Add source`。
- 表格列：
  - `Vendor`
  - `Credentials`：`API key ••••1234`
  - `Supports`：资产类别 chips
  - `Last test`
  - `⋮`
- 尚未添加的 vendor（如 Polygon）显示为灰色行 `Not added · Add source`。
- 行 `⋮` 菜单：`Test connection`、`Edit`、`Remove`。
- 02e 对话框：
  - `Vendor` 下拉，已添加的 vendor 置灰。
  - `API key`：密码框。
  - `Test connection`：做一次只读报价请求。
  - `Add`。

**规则**
- 每个 vendor 最多添加一个。
- 有 Dataset 用它做 Source 时不能 `Remove`。阻止框原型没画，见 Q10。
- Brokers 和 exchanges 不在这个页面出现，它们来自 Accounts。

**验收**
- [ ] 支持 MarketData.app（Equity、Option）和 Polygon（Crypto、Equity、Option）。
- [ ] 不能重复添加同一个 vendor。
- [ ] Test connection 只发起一次只读调用。
- [ ] 被 Dataset 使用的 vendor 不能删除。

**依赖**：F1。

---

### F4 Data library 与下载任务

**目标**：在 DuckDB 中建立和维护行情库：列表、添加数据、后台下载、自动更新、gap 重试。

**页面**
- 02 Data：![02 Data](/prd-images/02-data.png) https://algonics-wireframes.pages.dev/02-data
- 02b Add data：![02b Add data](/prd-images/02b-data-add.png) https://algonics-wireframes.pages.dev/02b-data-add

**02 组件**
- 页头：`Data`，副标题说明数据存放在 Algonics 服务器上的自有库，只有 Minute / Hour / Daily 三种 resolution。
- Summary strip：
  - `Library size`
  - `Symbols`：总数，加 Crypto / Equity / Option 分项。
  - `Gaps`：有 gap 的 dataset 数，加 gap 总数。
- 工具栏：
  - `+ Add data`
  - 筛选 chips：`All` / `Crypto` / `Equity` / `Option`，带计数。
  - 右侧 `Sources` 图标按钮。
- 表格列：
  - `Dataset`：`Asset class · Market · Resolution`
  - `Symbols`
  - `Range`
  - `Size`
  - `Source`
  - `Last updated`：`YYYY-MM-DD HH:mm`
  - `Status`
  - `Auto-update`：Switch
  - `⋮`
- 行 `⋮` 菜单：`Update now`、`Fill gaps`（扳手图标）、`Edit`（打开 02c，见矛盾 C10）、`Remove`。
- 每页 50 行。点击行打开 02c。

**02 行为**
- `Update now`：
  - 从每个 symbol 最后一根 bar 下载到现在，同时重试 open gap。
  - Status 变为 `Updating n%`。
  - Snackbar 显示 `Downloading … 12%`；完成后显示结果。
  - 失败时显示 `update failed` 和 `RETRY`。
- `Fill gaps`：
  - 对 open gap 和 `Source has no data` 区间强制重试，并重新给 3 次机会。
  - 没有这类 gap 时禁用。
  - 完成后 Snackbar 显示 `15 gaps filled`。
- `Auto-update`：
  - 默认关闭；打开后每天 06:00 Taipei 执行一次 Update。
  - **假设**：06:00 在 Settings 实现前固定不变。
- `Remove`：确认文案与 02c 的 Remove dataset 相同。

**02b Add data（Dialog sm）**
- `Source`：分组下拉。
  - `Brokers & exchanges`：
    - `Binance (public)`：无需密钥。
    - `Alpaca`：标注 via Alpaca main。
    - `Tradier`：标注 via Tradier cash。
    - 只列 06b 支持的平台（已定 D2）。`Kraken` 暂不列出：Kraken 账户能否作为数据源待定，见 Q14。
  - `Data vendors`：
    - `MarketData.app`
    - `Polygon`：未添加时置灰，提示 `Add source first`。
  - 规则：一个账户或 vendor 可以作为 Source 时才可选，否则置灰并提示原因。
- `Asset class`：所选 Source 不支持的类别置灰。
- `Market`：由 Source 和 Asset class 决定，例如 Binance → `Binance`，Alpaca + Equity → `US`。
- `Symbols`：chip 输入，用逗号、空格或换行分隔。Source 不认识的 symbol 显示为红色 chip。
- `Resolution`：`Minute` / `Hour` / `Daily`。Helper 文案：`5m, 15m, 30m are built from Minute; 2h, 4h from Hour; 1w from Daily.`。不提供月线。
- Option 专属（Option 只能选 Daily）：
  - `Underlyings`
  - `Expiry within (days)`：必填。
  - `Strikes within ±%`：必填。
  - 过滤逐日生效。
- `From` / `To`：
  - From 默认取所选 symbol 在库中最后一根 bar 的次日；新 dataset 或新 symbol 默认一年前。To 默认今天。
  - 下方显示 `Library has …` 现有范围。
  - 如果 From 晚于库中已有数据的末端，会产生跳过的区间。此时显示 gap 警告和 `Include gap`；不勾选的话，这段区间记为 `Not requested`。
- 估算行：显示 bars、size、time。超过 1 GB 时显示橙色警告，但不阻止提交。
- 信息行：`Adds to <dataset> … bars you already have are skipped`。
- `Add data`：
  - 创建后台 DownloadJob，关闭对话框，Snackbar 显示进度。
  - 如果组合已存在，就扩展已有 dataset，否则新建。

**规则**
- 回测和 Paper 只从库里读数据，绝不直接读 Source。
- 已经有的 bar 跳过，下载是幂等的。
- 下载失败不影响已有数据。
- 只有 mid-tier 写 DuckDB。DownloadJob 由 mid-tier 串行或排队执行（**假设**：同一个 dataset 同时只跑一个任务）。

**状态**
- 空状态：`No data yet`，带 `+ Add data`。
- 下载中：行内进度条。
- 失败：`✕ Failed`，悬停显示原因（例如 `Alpaca 429 Too Many Requests, 5 retries`）。

**校验**：Source、Asset class、Market、Resolution、至少 1 个有效 symbol 都必填；From 不晚于 To；Option 的两个过滤值必填，且必须是正数。

**验收**
- [ ] 同一个 Asset class × Market × Resolution 只会有一个 dataset，再次 Add data 会扩展它。
- [ ] Symbol 校验：未知 symbol 显示为红色，并阻止提交（**假设**：只有删掉红色 chip 才能提交）。
- [ ] 默认 From/To 和 `Library has` 显示正确；跳过的区间生成 Not requested gap。
- [ ] 超过 1 GB 有警告但不阻止提交。
- [ ] Update now、Auto-update 会重试 open gap，3 次失败后转为 Source has no data。
- [ ] Status 的四种取值和 Summary strip 计数正确，计数只算 open gap。
- [ ] 后台任务不依赖页面保持打开，进度可以查询。

**依赖**：F1、F2（Broker Source）、F3（Vendor Source）。

---

### F5 Dataset detail 与 gap 管理

**目标**：在 symbol 粒度查看数据覆盖，并按 symbol 更新、补 gap、增删。

**页面**：02c Dataset detail：![02c Dataset detail](/prd-images/02c-data-detail.png) https://algonics-wireframes.pages.dev/02c-data-detail

**组件**
- 页头：Breadcrumb `Data / <Dataset>`，加 Dataset 名称和 Status。
- 统计条：
  - `Symbols`：例如 `308 (15 gaps)`
  - `Range`
  - `Bars`
  - `Size`
  - `Source`
  - `Last updated`
  - `Auto-update`
  - `Available timeframes`
- 工具栏：
  - `+ Add symbols`（primary）：打开 Add data，其中 Source、Asset class、Market、Resolution 固定不可改。
  - `Update now`
  - `More ▾`：`Fill gaps`、`Remove selected`、`Export symbols (CSV)`、`Edit dataset`、`Remove dataset`。
- `n selected` 指示。
- Chips：`All` / `With gaps`。
- `Search symbols`。
- 表格列：checkbox、`Symbol`、`From`、`To`、`Bars`、`Gaps`、`Size`。所有列可排序；每页 50 行。
- 行展开：
  - 覆盖条（时间轴上标出有数据和缺失的区段）。
  - Gap 列表，每项一种状态：
    - `Retrying (2/3)`，附下次重试时间；
    - `Source has no data`，附 `3 tries failed · not counted in Gaps`，灰色；
    - `Not requested`，灰色。
  - `Fill gaps for <SYMBOL>` 按钮。
- Option 变体：行是 Underlying，额外显示 `Filter`（`≤ 60 DTE · ±20%`）。

**行为**
- `Update now` 和 `Fill gaps` 作用于选中的 symbols；没有选中时作用于整个 dataset。
- `Remove selected`：删除选中 symbol 的数据。确认框原型没画，见 Q10；**假设**沿用 Remove dataset 的文案模式。
- `Export symbols (CSV)`：导出当前列表，内容是 Symbol、From、To、Bars、Gaps、Size。
- `Edit dataset`：**假设**只能修改默认 Source 和 Auto-update（原型没画编辑框）。
- `Remove dataset`：
  - 确认框标题 `Remove <ds>?`。
  - 正文 `Removes this data from your library. Backtests that need it will ask you to download it again.`。
  - 删除后回到 02。

**状态**：加载用 Skeleton；搜索无结果显示 "No symbols match"（**假设**）；`With gaps` 为空时显示空表。

**验收**
- [ ] 有选择时操作作用于选中项，无选择时作用于整个 dataset。
- [ ] Gap 的三种状态显示正确，只有 open gap 计入 `Gaps`。
- [ ] Fill gaps 会把 Source has no data 重置为 open，并重新给 3 次机会；没有可补的 gap 时禁用。
- [ ] Option dataset 按 underlying 展示，并显示 Filter。
- [ ] Remove dataset 后 Lean cache 中对应内容失效。

**依赖**：F4。

---

### F6 策略注册与列表

**目标**：把外部编译的策略 DLL 注册成策略，并管理策略列表和状态。

**页面**
- 03 Strategies：![03 Strategies](/prd-images/03-strategies.png) https://algonics-wireframes.pages.dev/03-strategies
- 03b Add strategy，第 1 步 Source：![03b](/prd-images/03b-add-strategy-source.png) https://algonics-wireframes.pages.dev/03b-add-strategy-source
- 03c Add strategy，第 2 步 Review：![03c](/prd-images/03c-add-strategy-review.png) https://algonics-wireframes.pages.dev/03c-add-strategy-review
- 03d List settings：![03d](/prd-images/03d-list-settings.png) https://algonics-wireframes.pages.dev/03d-list-settings

**03 组件**
- 筛选卡：`Name`、`Status`（`Draft` / `Active` / `Inactive`）、`Search`、`Reset`。
- 工具栏：`+ Add strategy`，右侧齿轮打开 03d。
- 表格列：
  - `Strategy`：Display name，加描述。
  - `Algorithm`：完整类名，下一行显示 `xxx.dll · vN · n params`。
  - `Status`：Chip。
  - `Bots`：数字链接，点击打开 `/bots?strategy=<slug>`（04g）。
  - `Actions`：图标按钮 `Edit`、`Backtest`、`Archive`/`Unarchive`。
- 每页 10 行。点击行打开 03e。

**03 行为**
- `Backtest` 图标：
  - 不弹对话框，直接用上次设置运行。
  - Snackbar 显示 `Run #43 started · VIEW`，`VIEW` 跳转到这次 run。
- `Edit`：编辑 Display name 和 Description。编辑框原型没画，见 Q10。
- `Archive`：只有没有 running bot 时可用，否则禁用并显示 tooltip。确认框原型没画。
- 03d 的 `Show archived strategies`：默认关闭，按用户保存。打开后，Archived 行显示 `Unarchive`。

**03b / 03c Add strategy（Stepper：Source → Review）**
- Source 二选一（只有这两种，不做 Git 导入，已定 D1）：
  - `Local folder`：
    - 填写服务器上的 `.csproj` 或项目目录。
    - 系统在 `bin/**` 下取最新的 dll。
    - 找不到时显示 `No build found · build in Rider first`，带 `Rescan`。
    - 不执行 `dotnet build`。
  - `Upload DLL`：
    - 接受 `.dll` 或包含依赖的 `.zip`，最大 50 MB。
    - 按 SHA-256 存储。
- Review：
  - 扫描程序集（MetadataLoadContext），列出其中所有 Lean 算法类，默认全部勾选。
  - 每个类显示状态：
    - `New`：新策略。
    - `Changed`：已注册过，但算法指纹变了，注册后生成 vN+1。
    - `Unchanged`：程序集哈希相同，或指纹相同，不生成新版本。
- 按钮 `Register N strategies`：新策略为 Draft，并生成 v1。

**规则**：先比较程序集哈希；哈希不同再比较算法指纹（IL + 字段 + 参数签名），指纹相同不生成新版本。

**状态**
- 空列表："No strategies yet"，带 `+ Add strategy`（**假设**文案）。
- 扫描中：loading。
- 扫描不到算法类：显示错误（**假设**文案 "No Lean algorithm found in this assembly"）。

**校验**：上传文件类型和大小（50 MB）；路径必须存在。

**验收**
- [ ] 两种 Source 都能注册；Local folder 正确选出 `bin/**` 下最新的 dll。
- [ ] 界面和文案里没有 Git 导入入口（D1）。
- [ ] New / Changed / Unchanged 判断正确；Unchanged 不生成版本。
- [ ] 不执行任何策略代码，也不调用 dotnet build。
- [ ] 状态规则与 5.3.1 一致；有 running bot 时不能 Archive。
- [ ] Bots 列的链接打开 04g，并带上 strategy 筛选。

**依赖**：F1。

---

### F7 策略页、版本与激活

**目标**：在一个页面里查看策略信息、切换版本和回测，激活或停用版本，管理策略状态。

**页面**
- 03e 策略页：![03e Strategy page](/prd-images/03e-strategy-results.png) https://algonics-wireframes.pages.dev/03e-strategy-results
- 03f Version history：![03f](/prd-images/03f-version-history.png) https://algonics-wireframes.pages.dev/03f-version-history
- 03l Deactivate 确认：![03l](/prd-images/03l-deactivate-confirm.png) https://algonics-wireframes.pages.dev/03l-deactivate-confirm

**组件**
- 页头：
  - 策略名和 Status chip，chip 的 tooltip 解释当前状态的含义。
  - 描述可以行内编辑。
  - `Backtest` split button：主按钮直接运行，下拉 `▼ Run with options…` 打开 03i。
  - `Edit` 图标。
  - `⋮` 菜单：`Set inactive…` 或 `Reactivate`，以及 `Archive…`。
- 信息条：
  - `Class`
  - `Source`
  - `Registered`
  - `Versions`，带 `View history`（打开 03f）
  - `Activated`：已激活版本的 chips，每个 chip 菜单里有 `Deactivate`，点击打开 03l。
  - `Bots`，带 `View in Bots →`（打开 `/bots?strategy=<slug>`，即 04g）
- Run card 头部：
  - 级联选择器 `vN / Run #N ▾`：分组为 Latest、Activated，以及折叠起来的其它 draft 版本；支持搜索版本。
  - 版本行：显示 `Built` 时间，再加以下二者之一：
    - 按钮 `Activate vN`：不需要确认，Snackbar 带 `UNDO`；
    - chip `✓ Activated ▾`：菜单里有 Deactivate。
  - `All backtests (14)`：打开 03g。
  - Run `⋮` 菜单：`Rerun this version`、`Download results JSON`、`Delete`（确认框原型没画，见 Q10）。
  - Details：`Period`、`Started`、`Ended`、`Duration`。
- 03f 表格列：
  - `Version`
  - `dll SHA-256 + fp`
  - `Created`
  - `Source`：`Folder auto on backtest`、`Folder registered` 或 `Upload`
  - `Activated`
- 03f 其它：
  - 被 bot 引用的版本，`Deactivate` 禁用，提示 `Used by 1 bot · …`。
  - Upload 类策略额外显示 `Upload new DLL`。
- 03l 确认框：
  - 标题 `Deactivate vN?`
  - 正文 `It can't be picked when adding a bot.`
  - 服务端校验失败时显示错误 `Used by 1 bot`。

**规则**
- 版本激活规则见 5.3.2，策略状态规则见 5.3.1。
- 只有已完成的 run 可以执行 Activate。
- 停用最后一个已激活版本时，策略回到 Draft。
- `Set inactive…`：确认框原型没画。生效后不能再新建 bot，运行中的 bot 不受影响。

**验收**
- [ ] 级联选择器正确列出版本和 run，搜索可用。
- [ ] Activate 无需确认，可以 UNDO，执行后状态 chip 和信息条立即更新。
- [ ] 版本被 bot 引用时无法 Deactivate，前端和服务端都要校验。
- [ ] 停用最后一个激活版本后，策略回到 Draft。
- [ ] `View in Bots →` 打开带 strategy 筛选的 Bots 列表。

**依赖**：F6、F8（run card 显示的是回测结果）。

---

### F8 回测：运行、结果、历史

**目标**：运行 Lean 回测，展示统计、曲线、成交和日志，并提供回测历史。

**页面**
- 03e Overview：![03e](/prd-images/03e-strategy-results.png) https://algonics-wireframes.pages.dev/03e-strategy-results
- 03i Run backtest：![03i](/prd-images/03i-new-backtest.png) https://algonics-wireframes.pages.dev/03i-new-backtest
- 03g Backtests drawer：![03g](/prd-images/03g-backtests-drawer.png) https://algonics-wireframes.pages.dev/03g-backtests-drawer
- 03j Trades：![03j](/prd-images/03j-strategy-results-trades.png) https://algonics-wireframes.pages.dev/03j-strategy-results-trades
- 03k Logs：![03k](/prd-images/03k-strategy-results-logs.png) https://algonics-wireframes.pages.dev/03k-strategy-results-logs

**运行**
- 主按钮 `Backtest` 和列表页的 Backtest 图标：直接用上次设置运行，Snackbar 显示 `Run #43 started · VIEW`。
- 03i `Run with options…` 对话框（Dialog sm），只有两部分：
  - `Version`：
    - Local folder 策略：默认 `Latest from source folder`。提交时重新读取最新 dll，指纹变化时自动生成新版本（来源 `Folder auto on backtest`）并用它运行；否则用当前最新版本。
    - 也可以在 `Pin an existing version` 下固定一个已有版本，此时不读文件夹。已激活的版本带 `Activated` 标记。
    - Upload 策略：默认最新版本；最后一项是 `Upload new DLL…`，上传规则与 F6 相同，接受后预选新生成的版本。
  - `Period (optional)`：
    - 默认留空，留空时使用代码里的日期。
    - Chips `1Y` / `3Y` / `5Y` / `YTD` / `Max`：以最后一根可用 bar 为终点填充 Start/End；`Max` 表示整个覆盖范围。
    - 范围超出数据覆盖时报错。
- Data hint：
  - 首次运行前显示 `Data requirements unknown until first run`。
  - 之后显示所需 symbols 和 resolution 在库里的覆盖情况。
- 不提供参数、初始资金、手续费、runner、run 名称这些字段。它们都由代码或默认值决定。
- 提交后页面切到新 run，显示 Running 状态。

**执行管线（mid-tier）**
1. 解析版本（可能生成新版本）。
2. 检查行情库覆盖。
3. 导出所需 symbol × resolution × 日期到 Lean cache，只导出新增或变更的部分。
4. 用 Local runner 运行 Lean。
5. 回传进度和日志。
6. 保存 statistics、曲线、成交、日志和 JSON。
7. 首次运行时记录策略的数据需求。

缺数据时的 UI（Missing data / Fill now）延后实现。**假设**：MVP 在缺数据时让 run 失败，并给出清楚的错误摘要（例如 "Missing data for BTCUSDT Minute 2023-01..2023-03"）。

**Overview 结果**
- `Statistics` 卡：
  - 默认折叠，只显示 8 个核心指标：`Total Trades`（round trips = W + D + L）、`Net Profit`（金额和 %）、`CAGR`、`Win Rate`（wins / draws / losses）；`Sharpe`、`Sortino`、`Profit Factor`、`Max Drawdown · Recovery n d`（Lean DrawdownRecovery）。
  - `More ▾` 展开其余 22 项，例如 Total Orders、End Equity、Total Fees、Calmar、Alpha/Beta、Buy & Hold 等。
  - Crypto 策略的 Total Fees 为 0 时，在卡片上方显示琥珀色 Alert（提醒检查手续费模型）。
- `Balance & drawdown` 图：资金曲线对比 Buy & Hold 基准，下方是回撤。
- 子标签 `Overview` / `Trades` / `Logs`，各自对应 URL 子路径。

**Trades（03j）**
- 左侧是 symbol 列表：交易次数、胜场、P&L，按 P&L 排序。
- 右侧是 K 线图，叠加入场和出场标记、策略指标，并有成交导航列表。
- 成交表：
  - 列：`Open time`、`Close time`、`Symbol`、`Side`、`Qty`、`Entry`、`Exit`、`P/L`、`Fees`、`Duration`、`Exit reason`。
  - 每页 10 行，带 `Export CSV`。

**Logs（03k）**
- `Search`。
- 级别 chips：`All` / `Info` / `Warning` / `Error`。
- `Download`：下载完整日志。

**03g Backtests drawer（右侧，720px）**
- `Search`：按 Run # 或版本搜索。
- `Sort`：`Newest` / `Net Profit` / `Max DD`。
- Version chips、Status chips。
- 列：`Run`、`Version`、`Status`、`Net Profit`、`CAGR`、`Sharpe`、`Max DD`、`Trades`、`Created`。
- 页脚：`14 runs`。
- 点击一行，切换到该 run。

**状态**
- 无回测：`No backtest yet`，带 `Run first backtest`。
- Running：
  - 显示进度和 ETA；Logs 实时滚动。
  - Overview 和 Trades 禁用。
- Failed：显示错误摘要，默认打开 Logs。
- 查看旧 run：显示提示条 `You are viewing Run #38 (v5). Latest is Run #42 · Back to latest`。

**验收**
- [ ] "Latest from source folder" 在 dll 变化时自动生成版本，没变化时复用当前版本。
- [ ] Period 为空时使用代码日期；chips 计算正确；超出覆盖范围时报错。
- [ ] Lean cache 增量导出，可以整体删除后重建。
- [ ] 8 个核心指标、More 展开的 22 项、Fees=0 时的 Alert 都正确。
- [ ] Trades 和 Logs 的筛选、分页、导出可用。
- [ ] Running、Failed、旧 run、无回测四种状态都按原型显示。
- [ ] URL `/strategies/:slug/runs/:n[/trades|/logs]` 可直接访问，刷新后状态保持。

**依赖**：F4（库数据）、F6、F7。

---

### F9 Bots 列表与筛选

**目标**：集中查看所有 bot，并在列表里执行启停操作；支持用 URL 带入筛选条件进入列表。

**页面**
- 04 Bots：![04 Bots](/prd-images/04-bots.png) https://algonics-wireframes.pages.dev/04-bots
- 04g Bots，筛选进入：![04g Bots filtered](/prd-images/04g-bots-filtered.png) https://algonics-wireframes.pages.dev/04g-bots-filtered

**组件**
- 页头：`Bots`，副标题 `Strategies running on an account, paper or live`。
- **Filter card**：一张独立卡片，放在列表上方，与 03 Strategies 的筛选卡同一模式。
  - `Mode` select：`All` / `Live` / `Paper`
  - `Status` select：`All`、`Starting`、`Running`、`Paused`、`Stopping`、`Stopped`、`Error`
  - `Strategy` select：`All`，之后是所有已有 bot 的策略，显示 display name。
  - 三个 select 默认都是 `All`。
  - 按钮：`Search`、`Reset`。
- 列表卡工具栏只有 `+ Add bot` 和计数。
  - 计数格式为 `n of 6 bots`（有筛选时）。
  - **假设**：无筛选时显示 `6 bots · n running`。
- 表格列：
  - `Name`：链接到 04c。
  - `Status`：chip，第二行显示状态原因。
  - `Mode`
  - `Strategy`：`DisplayName · vN`。
  - `Account`
  - `Balance`
  - `P&L Today`
  - `P&L Total`
  - `Uptime`：本次运行的时长，不在运行时显示 `—`。
  - 操作按钮。
  - `⋮`
- 按 Name 排序；v1 不分页。
- 操作按钮随状态变化：

| Status | 行内按钮 |
|---|---|
| Running | `Pause`, `Stop` |
| Paused | `Resume`, `Stop` |
| Stopped | `Start` |
| Error | `Restart` |
| Starting… | `Stop` |
| Stopping… | 无（**假设**） |

- `⋮` 菜单：
  - `Promote to Live`：仅 Paper bot。
  - `Liquidate now`
  - `Delete`：仅 Stopped bot。
- 列表上没有 Edit 入口，编辑统一在 Bot 页（04c）完成。

**筛选行为**
- 修改 select 不会立即生效。点击 `Search` 后才筛选列表，并把条件写进 URL：`/bots?mode=live&status=error&strategy=vegas-tunnel`。值为 All 的参数省略（**假设**）。
- `Reset`：三个 select 都恢复为 All，然后重新加载列表（全部 bot），URL 中的参数清空。
- 带参数的 URL 打开时，筛选卡已预填，列表已按条件筛好。Back 和刷新后条件保持。
- 从 Sidebar 进入时不带任何筛选。
- 其它页面的入口链接：

| 入口 | URL 参数 | 04g 变体 |
|---|---|---|
| 03 `Bots` 列 / 03e `View in Bots →` | `strategy=<slug>` | Strategy = VegasTunnel → `4 of 6 bots`（Live + Paper） |
| 01 / 01e 状态行计数（如 `1 error`） | `mode=<当前视图>&status=<状态>` | Live + Error → 1 行 |
| — | 例：`mode=paper&status=error` | 无匹配：列表卡保留工具栏，显示 `No bots match these filters` + `Reset filters`（作用同 Reset） |

**规则**
- `Balance` = 分配资金（Live 为 Max capital，Paper 为 Initial cash）+ P&L，单位是账户币种。
- P&L 口径见 F14。原型 04 写的 "since added" 和 "local time" 与此有出入，见矛盾 C8。
- Start、Resume、Restart 都会运行 pre-start checks。Live 启动一定会弹确认框（F11）。
- 状态在服务器重启后恢复（5.3.3）。

**状态**
- 首次空状态：`No bots yet`，带 `+ Add bot`。只在一个 bot 都没有时出现。
- 筛选后无匹配：见上表。
- 操作结果用 Snackbar 反馈。

**验收**
- [ ] 改 select 不触发筛选，点 `Search` 才筛选并更新 URL；`Reset` 恢复为 All 并显示全部 bot。
- [ ] 直接打开带参数的 URL 时，筛选卡已预填、列表已筛好；Back 和刷新后保持。
- [ ] 来自 Dashboard、03、03e 的链接带上正确的参数。
- [ ] 无匹配状态和首次空状态区分开。
- [ ] 每种状态下的按钮和 ⋮ 菜单项与上表一致。
- [ ] 状态原因（Daily loss limit、Max loss limit、错误信息）显示在第二行。

**依赖**：F10、F11（操作行为）、F7（策略 slug）。

---

### F10 Add bot / Edit bot

**目标**：用最少的字段创建 bot；在 Bot 页编辑参数、风控和通知。

**页面**：04b：![04b Add/Edit bot](/prd-images/04b-new-bot.png) https://algonics-wireframes.pages.dev/04b-new-bot

**Add bot 字段**
- `Name`：必填，唯一，按策略和 symbol 预填（**假设**格式 `<Strategy> · <Symbol>`）。
- `Strategy`：只列 Active 策略（不含 Draft、Inactive、Archived）。
- `Version`：只列该策略已激活的版本。默认值见矛盾 C4。
- `Mode`：`Paper`（默认）/ `Live`。
- `Account`：
  - 列出所有账户，不按策略类型过滤。
  - Mode = Live 时，如果账户已被一个未 Stopped 的 Live bot 占用，该选项禁用并显示 `Used by <bot>`。
- 信息行：
  - Paper：`Added as Stopped with the strategy's default parameters, 100,000 USDT initial cash, no loss limits and no notifications.`
  - Live：显示 whole account 版本的文案。
- 提交：`Add`。bot 以 Stopped 状态创建。默认值：
  - 参数取 DLL 中的默认值。
  - Live 的 Max capital 为空，即使用整个账户。
  - Paper 的 Initial cash 为 100,000，单位是账户币种。
  - 不设任何 limits，不选 Channels。

**Edit bot（入口只在 04c 的 `Edit`）**
- 锁定字段：`Strategy`、`Mode`、`Account`。
- 可编辑字段：
  - `Name`
  - `Version`：已激活版本。
  - `Parameters`：按 DLL 扫描出的参数列出，带 `Reset to defaults`。
  - `Limits`：
    - Live：`Max capital`，填金额或账户的 %；留空表示整个账户。
    - Paper：`Initial cash`。
    - `Max loss`：金额或 %，附复选框 `also close all positions`。
    - `Daily max loss`：金额或 %。
  - `Channels`：多选；不选表示不发通知。
  - `Description`
- 保存：
  - 停止状态的 bot：`Save`。
  - 运行中的 bot：`Save` / `Save & restart`。
- 生效规则：
  - Name、Description、Channels 立即生效。
  - Version、Parameters、Limits 要重启后才生效。只点 Save 时显示 Restart notice（F11 ⑥）。

**校验**
- Name 唯一。
- 金额 > 0；百分比在 0–100 之间。
- 参数类型与 DLL 签名一致（**假设**：按 int / decimal / bool / string 渲染输入框）。
- 服务端再校验一遍账户占用规则。

**验收**
- [ ] Strategy 只列 Active 策略，Version 只列已激活版本。
- [ ] Live 模式下被占用的账户禁用，并显示 `Used by`；服务端同样拒绝。
- [ ] 新 bot 状态为 Stopped，默认值正确。
- [ ] Edit 中锁定字段不可修改；Reset to defaults 可用。
- [ ] 立即生效和重启生效的字段区分正确。

**依赖**：F2、F7、F13（Channels）。

---

### F11 Bot 生命周期与对话框

**目标**：实现 bot 的启停、风控自动处理、平仓、清仓、Promote、重启、删除，以及它们的对话框和运行时。

**页面**：04e：![04e Bot dialogs](/prd-images/04e-bot-dialogs.png) https://algonics-wireframes.pages.dev/04e-bot-dialogs

**对话框与行为**
1. **Stop**
   - 两个复选框 `Cancel open orders`、`Close all positions`，默认都不勾选。不勾选时，订单和持仓留在账户上。
   - 状态经过 `Stopping…` 变为 `Stopped`。
2. **Pre-start checks**：每次 Start、Resume、Restart 都要检查以下四项：
   - 账户最近一次测试成功；
   - 账户能交易策略的 symbols，例如 crypto 策略放在只支持股票的券商上会失败；
   - 可用余额 ≥ Max capital；
   - 没有冲突的 Live bot 占用这个账户（口径见矛盾 C5）。
   
   弹框规则：
   - Live 启动一定弹确认框。四项全通过时，按钮 `Start live` 为 error 色。有任一项失败时，该项标红并说明怎么修复，`Start live` 禁用。
   - Paper 启动时检查全通过就直接启动，不弹框。**假设**：有失败项时弹同一个框，按钮为 `Start`。
3. **Close position**：确认后对该持仓全数量下市价单。策略在下一个信号之前不会重新开仓。
4. **Liquidate now**：
   - 危险确认，输入 `LIQUIDATE` 后按钮才启用。
   - 执行：撤销全部订单，平掉全部持仓，bot 变为 Stopped。
5. **Promote to Live**（仅 Paper bot）：
   - 新建一个 Live bot，复制原 bot 的配置（版本、参数、limits、channels、description）。
   - 只需要填写：
     - Live `Account`：被占用的账户禁用；
     - `Name`；
     - `Stop the paper bot`：默认不勾选。
   - 新的 Live bot 状态为 Stopped。Max capital 留空，即整个账户；Initial cash 不复制。
   - 启动前需要在 Edit 里设置 Max capital。
6. **Restart notice**：在运行中的 bot 上保存了 version、parameters 或 limits 后弹出，选项为 `Later` / `Restart now`。选 Later 时，旧值继续运行，直到下次启动。
7. **Delete**：仅 Stopped bot。确认后删除 bot，它的历史数据随之删除（**假设**）。

**运行时规则（mid-tier / Lean live）**
- Pause：停止开新仓，策略继续管理已有持仓，退出和止损照常执行。
- 风控：
  - Daily max loss 达到时自动 Paused，原因 `Daily loss limit · HH:mm`。
  - Max loss 达到时自动 Stopped，原因 `Max loss limit · MM-DD HH:mm`。勾选了 also close 时同时平仓。
- Heartbeat：
  - 超过 60 s 没有心跳：显示 Near a limit / No heartbeat。
  - 超过 5 min：显示 Needs attention。
- 股票只在常规交易时段交易。休市时 Close 和 Liquidate now 禁用（04c）。
- 服务器重启后，Running 和 Paused 的 bot 恢复原状态。
- **通知**：
  - 只发给 bot 选中的、Enabled 的 Channels。
  - 事件清单原型没有给出，见 Q7。
  - **假设**：MVP 发送的事件为 Error、自动 Paused（Daily loss limit）、自动 Stopped（Max loss limit）、No heartbeat，以及 Start/Stop 的结果。

**验收**
- [ ] 状态转换与 5.3.3 一致。Stopping… 期间按 Stop 对话框的勾选撤单、平仓。
- [ ] 四项 pre-start checks 都实现；Live 必弹确认，Paper 全通过时不弹框。
- [ ] 输入 LIQUIDATE 才能清仓；清仓后 bot 为 Stopped。
- [ ] Promote 复制配置，新建的 Live bot 为 Stopped 且 Max capital 为空；原 Paper bot 按勾选决定是否停止。
- [ ] 两种自动风控在达到阈值时触发，并写入状态原因。
- [ ] 服务器重启后状态恢复。

**依赖**：F2、F10、F13。

---

### F12 Bot 页（Overview / Trades / Logs）

**目标**：监控单个 bot 的健康状况、盈亏、持仓、订单和日志。

**页面**
- 04c Overview：![04c](/prd-images/04c-bot-detail.png) https://algonics-wireframes.pages.dev/04c-bot-detail
- 04d Trades：![04d](/prd-images/04d-bot-trades.png) https://algonics-wireframes.pages.dev/04d-bot-trades
- 04f Logs：![04f](/prd-images/04f-bot-logs.png) https://algonics-wireframes.pages.dev/04f-bot-logs

**共用头部（三个标签页相同）**
- Breadcrumb `Bots / <Name>`。
- 名称、Status chip、Mode chip。
- Description：为空时显示灰色的 `Add a description`，点击打开 Edit。
- 状态按钮同列表（F9），`⋮` 菜单里有 `Liquidate now` 和 `Delete`。
- **Health strip**：整个 bot 用一种颜色。
  - 绿色 `Healthy`。
  - 琥珀色 `Near a limit`：任一 limit 用到 80% 以上，或 heartbeat 超过 60 s。
  - 红色 `Needs attention`：Error 状态、limit 已触发、heartbeat 超过 5 min，或最近 15 min 内有交易所错误。
  - 内容：
    - Daily loss 和 Max loss 占各自 limit 的 %；Position value 占 Max capital 的 %；自添加以来的 Max drawdown。
    - 未设置的 limit 显示 `No limit`，不画进度条。
    - 小字行显示 heartbeat 和 last error，并带 Logs 链接。
- KPI：
  - `Balance`：附起始值，不画曲线。
  - `P&L Today`：从 00:00 Taipei 起算。
  - `P&L Total`：拆分为 realized 和 unrealized。
- 标签：`Overview` · `Trades` · `Logs`。没有 Config 标签页。

**Overview**
- Symbols 列：
  - 列出 bot 交易过或持有的每个 symbol：交易次数、胜场、P&L（含已平仓和持仓，扣费后），按 P&L 排序，持仓中的标 `open`。
  - 超过 5 个 symbol 时，出现 Search 和 `All symbols` 合计。
- 图表：
  - Timeframe 默认取策略的周期。
  - Legend 可切换：K 线、成交量、策略指标、入场和出场标记、MACD 和 RSI 副图。
  - 持仓画出入场线和止损线。
- `Open positions` 表：
  - 只显示本 bot 的持仓。
  - 每行有 `Close` 按钮，打开 F11 ③。
  - 挂单数链接到 Trades 标签页的 Orders。
- `Configuration` 卡（只读）：
  - Strategy · version：有更新的激活版本时显示提示。
  - Account：链接。
  - 本次运行时长。
  - Channels。
  - Limits：用账户币种；未设置时显示 `Whole account` / `No limit`；`+ close` 表示 Max loss 时同时平仓。
  - `Edit` 打开 04b，这是编辑 bot 的唯一入口。
- 股票类 bot 在休市时显示 banner，内容为下次开盘时间，同时禁用 Close 和 Liquidate now。

**Trades（04d）**
- `Closed trades`：round trips。
- `Orders`：chips `All` / `Open` / `Filled` / `Canceled`。
- 每次显示 10 条，`Load more` 加载更多。

**Logs（04f）**
- Chips：`All` / `Signals` / `Orders` / `Warnings & errors`。
- `Search`。
- `Pause`：暂停自动滚动。
- `Download 7 days`。
- 日志保留 30 天。

**验收**
- [ ] Health strip 三种颜色的判定与上文阈值一致。
- [ ] KPI 和 Configuration 卡显示正确；未设置的 limit 文案正确。
- [ ] 持仓 Close 走确认流程；休市时 Close 和 Liquidate 禁用并显示 banner。
- [ ] Trades、Logs 的筛选、分页、下载可用；超过 30 天的日志清理掉。
- [ ] 数据实时或准实时刷新（**假设**：轮询间隔 ≤ 5 s，或用推送）。

**依赖**：F9、F10、F11。

---

### F13 Channels（通知渠道）

**目标**：配置 bot 的通知渠道。

**页面**
- 07 Channels：![07](/prd-images/07-channels.png) https://algonics-wireframes.pages.dev/07-channels
- 07b Add / Edit channel：![07b](/prd-images/07b-add-channel.png) https://algonics-wireframes.pages.dev/07b-add-channel
- 07c Delete：![07c](/prd-images/07c-delete-channel.png) https://algonics-wireframes.pages.dev/07c-delete-channel

**组件**
- 列表列：`Name`、`Type`、`Enabled`（Switch）、`Last test`、`Used by`、`Notes`、`⋮`。
- 行 `⋮` 菜单：`Send test message`（发一条固定的短测试消息，结果用 Snackbar 反馈）、`Edit`、`Delete`。
- 07b 字段按 Type 不同：

| Type | 字段 |
|---|---|
| Email | `Recipients`（发件人用 Settings 的 SMTP sender） |
| Telegram | `Bot token`, `Chat ID` |
| Webhook | `Method`, `URL`, `Headers`, `Signing secret`（HMAC-SHA256 签名） |
| DingTalk | `Robot webhook URL`, `Sign secret` |
| Lark | `Robot webhook URL`, `Signature secret` |

- 07b 中的 `Send test`：用表单当前值发送，结果显示在表单内：`✓ Sent`，或 ✕ 加错误信息，相关字段标红（例如 Telegram 401 时 token 字段标红）。

**规则**
- 被 bot 引用时不能删除。07c 阻止框提供 `Turn off`（关闭 Enabled）和 `OK`。
- 关闭 Enabled 后不再发送通知，但引用关系保留。
- 已启用的渠道最近一次测试失败时，进入 Dashboard 的 Needs attention。

**验收**
- [ ] 五种类型的字段与上表一致；密钥加密存储，只显示末 4 位。
- [ ] Webhook 带 HMAC-SHA256 签名头（**假设**头名 `X-Algonics-Signature`）。
- [ ] Send test 的结果写入 Last test。
- [ ] 被引用的渠道删除被阻止，阻止框提供 Turn off。

**依赖**：F1。Email 依赖 SMTP sender 配置（见 F1）。

---

### F14 Dashboard 与紧急操作

**目标**：一眼看出所有 bot 和账户是否正常、总资金和盈亏如何，并提供全局暂停和紧急停止、清仓。

**页面**
- 01 Dashboard，Live：![01](/prd-images/01-dashboard.png) https://algonics-wireframes.pages.dev/01-dashboard
- 01e Paper 视图：![01e](/prd-images/01e-dashboard-paper.png) https://algonics-wireframes.pages.dev/01e-dashboard-paper
- 01b Pause all：![01b](/prd-images/01b-pause-all.png) https://algonics-wireframes.pages.dev/01b-pause-all
- 01c Stop all：![01c](/prd-images/01c-stop-all.png) https://algonics-wireframes.pages.dev/01c-stop-all
- 01d Liquidate all：![01d](/prd-images/01d-liquidate-all.png) https://algonics-wireframes.pages.dev/01d-liquidate-all
- 01f 磁贴版：仅作对比，**不实现**。https://algonics-wireframes.pages.dev/01f-dashboard-tiles

**页头**
- 标题。
- `Live` / `Paper` 切换：默认总是 Live，选择按用户记住，没有 All 选项。
- `Updated 3 s ago`：自动刷新。**假设**刷新周期为 5 s。

**Needs attention banner（MUI Alert）**
- 覆盖**所有** bot，Paper 和 Live 都算，与切换无关。
- 颜色取最严重的一项。默认折叠，只显示最严重的一项加 `+N more`，点 chevron 展开。
- 条目类型和对应链接：

| 条目 | 链接 |
|---|---|
| bot 处于 Error | `Open bot`（04c） |
| bot 因 Daily max loss 自动暂停 | `Open bot` |
| bot 因 Max loss 自动停止 | `Open bot` |
| 运行中的 bot 的策略有更新的激活版本 | `Open bot` |
| `No heartbeat`（超过 60 s） | `Open bot` |
| 账户最近测试失败 | `Open account`（06） |
| 已启用的渠道最近测试失败 | `Open channel`（07） |

- 没有条目时显示绿色 `All systems normal`。

**状态行**
- 按状态显示计数：running / paused / starting / error / stopped，计数为 0 的不显示。
- 每个计数链接到 `/bots?mode=<当前视图>&status=<状态>`（04g）。
- `View all bots ›` 打开不带筛选的 04。
- `Pause all`（01b）和 `Emergency ▾`（`Stop all` 01c、`Liquidate all` 01d）只在 Live 视图出现；Paper 视图和状态 A/B 中隐藏。

**第 1 行（左右两半，固定高 300 px）**
- **Total balance**：
  - 所有账户余额由后端换算为 Default currency 后求和。不显示 ≈ 符号，不按币种拆分。
  - 趋势图，区间 `1W` / `1M` / `YTD` / `All`，默认 1M，按用户记住。
  - `P&L Today` / `P&L YTD` / `P&L Total`。
- **Open positions**：
  - 按 |unrealized P&L| 排序，每页 4 条，用分页点切换。
  - 类型 chip：`Stock` / `Crypto` / `Option`。
  - 每条两行：
    - 第一行：左边 Symbol 和 chip，右边未实现 P&L 金额和 %。
    - 第二行：Bot 链接 · Side Qty，以及账户币种的市值。
  - 不显示手动开的仓位。

**第 2 行：Accounts 卡（每张占 50% 宽）**
- `Balance`、`Allocated`（Max capital 之和，或 `All`）、`Available`（可用现金）。
- 64 px 趋势图。
- P&L Today / YTD / Total，以占 Allocated 的 % 显示。
- bot 列表：Live 视图的卡只显示当前的 Live bot；Paper 视图的卡显示所有 Paper bot。
- 读不到账户时，显示橙色 `as of 15:10`（最后一次成功读取的时间）。

**P&L 口径（全局统一）**
- 按 bot 计算，然后汇总。出入金永远不计入 P&L。
- `P&L Today`：从 00:00 Taipei 起算，以 00:00 时的 balance 为基数算 %。
- `P&L YTD`：从 1 月 1 日起算；bot 晚于这一天启动的，从首次启动起算。
- `P&L Total`：从 bot 首次启动起算，已实现 + 未实现，扣除手续费和 funding。
- Paper 视图：Paper balance = Initial cash + P&L。

**数据状态**
- A：新用户，只显示 4 步 checklist：`Download data`（Open Data）、`Add strategy`、`Add account`、`Add bot`。每步完成后打勾（**假设**）。
  - 第 2 步说明文案：`Register a strategy from a local folder or an uploaded DLL, backtest it, then activate a version (only an Active version can run in a bot)`（D1）。
- B：有账户但没有 Live bot。卡片为空，显示 `No live bots yet` / `No open positions`。
- C：一个 bot。
- D：多个 bot。

**紧急对话框**
- **01b Pause all**：
  - 列出 Running 和 Starting 状态的 Live bot。
  - 按钮为 primary 色。
  - 完成后 Snackbar 显示 `2 bots paused · exits keep running`。
- **01c Stop all**：
  - 作用于 Running、Paused、Starting 状态的 Live bot。
  - 复选框 `Cancel open orders`、`Close all positions`，默认都不勾选。
  - 按钮为 error 色。bot 经过 Stopping… 变为 Stopped。
  - Snackbar 显示 `2 bots stopped`。
- **01d Liquidate all**：
  - 输入 `LIQUIDATE` 后按钮才启用。
  - 表格列：`Bot` / `Positions` / `Market value` / `Open orders`。
  - 有市场休市时显示警告，这些 bot 只停止、不平仓（见矛盾 C7）。
  - Snackbar 显示 `Liquidating 2 bots…`。

**验收**
- [ ] Needs attention 覆盖全部条目类型，颜色、折叠、链接正确；空时显示 All systems normal。
- [ ] 状态行计数链接带正确参数（mode 和 status）到 04g。
- [ ] Pause all 和 Emergency 只在 Live 视图出现，作用范围与上文一致。
- [ ] Total balance 换算到 Default currency；区间选择按用户记住。
- [ ] P&L 三个口径与上文一致，出入金不影响 P&L。
- [ ] 账户读取失败时显示 `as of HH:mm`。
- [ ] A/B/C/D 四种数据状态都正确；Live/Paper 选择按用户记住。

**依赖**：F2、F9、F11、F12、F13；F1（Default currency、FX）。

---

## 8. 业务规则汇总

| # | 规则 | 来源 |
|---|---|---|
| R1 | 一个账户同时最多有 1 个未 Stopped 的 Live bot；Paper bot 不受限 | 04b、06 |
| R2 | 只有 Active 策略的已激活版本才能用来建 bot；bot 固定版本，不会自动升级 | 04b、03e |
| R3 | 被任何 bot 引用的版本不能 Deactivate；停用最后一个激活版本时策略回到 Draft | 03f、03l |
| R4 | 有 running bot 的策略不能 Archive；Inactive 策略不能新建 bot，已运行的继续运行 | 03 |
| R5 | 版本只在程序集哈希和算法指纹都变化时生成；版本不可变 | 03c、03i |
| R6 | 回测和 Paper 只从 Library 读数据；只有 Java mid-tier 写 DuckDB | 02 |
| R7 | Dataset 唯一键是 Asset class × Market × Resolution；Option 只能选 Daily | 02、02b |
| R8 | 每次 Update 重试 open gap，最多 3 次，之后转为 Source has no data；Fill gaps 重新给 3 次机会；Not requested 永不自动补；Gaps 计数只算 open gap | 02、02c |
| R9 | Start、Resume、Restart 都运行 pre-start checks；Live 启动必须确认 | 04e |
| R10 | Daily max loss → 自动 Paused，至少到次日、且必须手动 Resume；Max loss → 自动 Stopped，可选同时平仓 | 04、04b |
| R11 | Pause 只停止开新仓，止损和退出继续执行 | 04 |
| R12 | 清仓类操作（Liquidate now、Liquidate all）要输入 `LIQUIDATE` | 01d、04e |
| R13 | 只有 Stopped 的 bot 能删除；有未 Stopped 的 bot 在用的账户不能删除；被 bot 引用的渠道和被 dataset 使用的 vendor 不能删除 | 04、06c、07c、02d |
| R14 | P&L 按 bot 计算，出入金不计入；Today 从 00:00 Taipei 起算；Total 从首次启动起算，扣费 | 01 |
| R15 | 股票只在常规交易时段交易；休市时禁用 Close 和 Liquidate | 04、04c |
| R16 | Version、Parameters、Limits 修改后需重启生效；Name、Description、Channels 立即生效 | 04b |
| R17 | 服务器重启后，Running 和 Paused 的 bot 恢复原状态 | 04 |
| R18 | 密钥加密存储，不回显明文，只显示末 4 位 | 06b、02d、07b |
| R19 | 每个 vendor 最多添加一次 Source | 02e |
| R20 | 新 bot 一律以 Stopped 创建；Promote 生成的 Live bot 也是 Stopped，且 Max capital 为空 | 04b、04e |

---

## 9. 非功能要求
- 安全：
  - 所有凭证静态加密。
  - 日志里不打印密钥。
  - Live 下单前的确认在服务端强制执行，不能只靠前端。
- 可靠：
  - bot 运行时与 Web 进程解耦。
  - mid-tier 重启时 bot 状态可以恢复。
  - 下载任务可以续跑，或者能安全地重新执行（幂等）。
- 性能：
  - Data 列表每页 50 行，Strategies 每页 10 行。
  - Bots 列表不分页（数量少）。
  - Dashboard 刷新延迟 ≤ 5 s（**假设**）。
- 保留期：
  - bot 日志 30 天。
  - 回测结果和日志永久保留，直到手动 Delete（**假设**）。

---

## 10. 建议实现顺序

| 阶段 | Feature | 理由 / 可交付 |
|---|---|---|
| 0 | F1 外壳、认证集成、格式约定 | 所有页面都依赖它 |
| 1 | F2 Accounts、F3 Sources、F13 Channels | 都是独立的 CRUD 加连通性测试，后续功能依赖它们 |
| 2 | F4 Data library + 下载任务 → F5 Dataset detail | 先打通 DuckDB 单写者和后台任务；回测依赖它 |
| 3 | F6 策略注册（MetadataLoadContext 扫描、指纹） | 有了版本才能回测 |
| 4 | F8 回测管线和结果 → F7 策略页、版本激活 | 先做 Lean cache 导出加 Local runner，再做 UI；激活版本是建 bot 的前提 |
| 5 | F10 Add/Edit bot → F11 生命周期（先做 Paper） → F9 列表与筛选 → F12 Bot 页 | 先让 Paper 端到端跑通 |
| 6 | F11 中的 Live 部分（pre-start checks、Live 确认、风控、Promote） | 涉及真钱，放在 Paper 稳定之后 |
| 7 | F14 Dashboard 与紧急操作 | 汇总前面所有数据；Emergency 依赖 F11 |
| 8 | 通知事件接入（F11 / F13）、收尾和验收 | — |

---

## 11. 不在范围 / 后续
- Alerts 模块。
- 回测：
  - 缺数据状态（02 ⑬ `Missing data` / `Fill now`）；
  - Compare（回测对比）；
  - 参数表单；
  - 在回测里设置初始资金、手续费模型；
  - 多 runner（远程 runner 见下方 Remote runners）。
- 策略：
  - Tags；
  - Git 来源（已定不做，D1）；
  - `dotnet build`；
  - 文件夹监听（folder watching）。
- Dashboard：
  - Resume all；
  - 账户详情页；
  - 01f 磁贴版。
- Bots：
  - 审计和活动日志；
  - Duplicate；
  - max open trades。
- Channels：
  - 消息模板；
  - 免打扰；
  - 限流。
- Settings 页：Default currency、FX rates、SMTP sender。MVP 先用配置项代替。
- 账户环境 Paper / Testnet。
- 06b 之外的账户平台（已定只支持 Alpaca / Tradier / Binance / Kraken，D2）。
- 月线、tick 和秒级数据。
- **Remote runners（VPS）**：一期只支持 Local runner。已商定的后续方向：
  - 每台 VPS 跑一个 **Go 写的 runner agent**，通过 **rootless Podman**（Quadlet / systemd 管理）控制 Lean。
  - agent **主动向外**连 Java mid-tier，用 **gRPC 双向流**，token 认证。
  - 走私有网络：同机房用内网 IP；跨机房用 AWS 跨区域 VPC peering 或 WireGuard。
  - 用 **Ansible** 做 VPS 的初始化和升级。
  - **交易不能依赖与 mid-tier 的连接**：
    - agent 本地缓存事件，带序号，重连后按序号补发（replay）；
    - 下发的命令要幂等；
    - mid-tier 收不到心跳时，把该 runner 上 bot 的状态标为 stale（过期），而不是当作已停止。

---

## 12. 待定问题（Open questions）

| # | 问题 |
|---|---|
| Q1 | Dashboard 账户卡头部的对齐方式 |
| Q2 | 是否需要 `P&L Open`（持仓未实现合计）汇总 |
| Q3 | 连接测试的文案统一（Test connection / Send test message / Testing… 结果文案） |
| Q4 | Needs attention 里用 `Open account` 还是 `View account` |
| Q5 | 账户卡增减时的副标题文案 |
| Q6 | 01f 的 Paper 变体是否需要（目前不实现） |
| Q7 | 通知事件清单（本文 F11 给了**假设**清单） |
| Q8 | Stopped bot 引用的账户被删除后怎么处理（目前规则只阻止未 Stopped 的 bot 引用的账户被删除） |
| Q9 | 04e 只画了余额不足这一种失败；连接测试失败、symbol 不支持、账户被占用的失败文案没画 |
| Q10 | 未画的对话框：03 Edit / Set inactive / Archive；03e 删除 run；02c Remove selected；02d Edit API key 和阻止删除框；07 Used by 列表 |
| Q11 | 全局 loading、404、离线状态 |
| Q12 | 原型里有部分按钮没有链接目标（例如 06c `View bots` 的去向；本文假设为按账户筛选的 Bots 列表，而 04 的筛选目前没有 Account 维度） |
| Q13 | Error 状态的 bot 是否也提供 Stop（原型中 Error 只有 Restart） |
| Q14 | Kraken 账户是否作为 02b Add data 的数据源（Source）。目前 02b 不列 Kraken（来自 D2 的决定） |

---

## 13. 原型之间的矛盾

### 13.1 已定（Karlo 已确认）

| # | 原矛盾 | 决定 | 已同步 |
|---|---|---|---|
| D1（原 C1） | 01 首次运行文案说策略可以 "from a local folder or Git"，但 03b 只有 Local folder / Upload DLL | **不做 Git**。策略只能通过 `Local folder` 或 `Upload DLL`（03b）添加 | 01 文案改为 "from a local folder or an uploaded DLL"；F6、F14、第 11 章 |
| D2（原 C2） | 02b 的 Source 列表有一个 06b 不支持的券商（IBKR），没有 Kraken；06b 的平台只有 Alpaca / Tradier / Binance / Kraken | **不支持 IBKR**。账户平台以 06b 为准：Alpaca、Tradier、Binance、Kraken。Kraken 是否作为数据源仍待定（Q14），02b 暂不加 Kraken | 02b 去掉该选项；03 示例数据改为 Alpaca main；F2、F4、第 11 章 |

### 13.2 待定（需 Karlo 定夺）

| # | 矛盾 | 本文暂定 |
|---|---|---|
| C3 | 02b 说 Not requested 区间可以用 Add data 或 Fill gaps 取回；02 ⑪ 说 Fill gaps 只处理 open gap 和 Source has no data | 暂定 Fill gaps **不**处理 Not requested，只能用 Add data 取回 |
| C4 | 示例里 bot 固定在 v7，但 v7 未激活（激活的是 v5、v3），而建 bot 只能选激活版本。04b 显示 "v7 (latest)"，默认值写的是 "the latest"；03e 说默认是最近激活的版本。01 写 "v7 is Active"，04c 写 "v7 available" | 默认取**最近激活的版本**；提示文案改成"有更新的激活版本"。示例数据有误 |
| C5 | 冲突口径：04e 写 "no other **running** Live bot uses the account"，04b、06 写 "one Live bot that is **not Stopped**" | 以 "not Stopped" 为准（更严格） |
| C6 | 04e 的 Promote to Live 把账户筛成 "Crypto accounts"，04b 说账户不按策略过滤 | 不过滤；交给 pre-start check 拦截 |
| C7 | 01d 说休市时清仓只停止这些 bot（"same rule as 04c"），而 04c 是直接禁用 Liquidate now | 单个 bot 禁用，全局清仓对休市市场只停止 |
| C8 | 04 写 P&L Total "since the bot was added"、P&L Today "since 00:00 local time"；01 和 04c 写 "since first start" 和 Taipei | 以 01 为准：首次启动起算，Taipei 时间 |
| C9 | 01e 提到 "Alpaca paper" 账户，但 06 说所有账户都是 Live | 账户全部 Live；Paper 是 bot 的模式 |
| C10 | 02 ⑪ 说行 `Edit` 打开 02c；02c 又把 `Edit dataset` 放在 More 菜单里 | 行 Edit 打开 02c 并弹出 Edit dataset |
| C11 | 02 ⑬ 和 03i 的注释引用了没画的 Missing data / Fill now 状态 | 已延后；MVP 中缺数据时 run 失败 |
| C12 | 01 首次运行写 "· 1m ·"，按 resolution 命名应为 Minute | 用 `Minute` |
| C13 | 01 的 "All systems normal" 写 "5 accounts"，其它处是 4 个有 bot 的账户、共 5 个账户 | 示例数据，影响小 |
| C14 | 03 和 03f 的示例数据（Bybit、vegas-btc-bybit）与 04、06（Binance main、sub-account、Alpaca main）不一致。（03 中 MomentumRotation 的 bot 示例已随 D2 改为 Alpaca main） | 只是示例数据，以 04 和 06 为准 |
