dev 6
This commit is contained in:
372
docs/AUDIT_AND_REMEDIATION.md
Normal file
372
docs/AUDIT_AND_REMEDIATION.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# big-qmt 项目审计与整改建议
|
||||
|
||||
- 审计日期:2026-08-28
|
||||
- 审计范围:服务端 `api/`、客户端 `py-client/`
|
||||
- 审计方式:静态代码检查、调用链核对、只读语法编译
|
||||
- 当前状态:仅供人工确认,尚未实施代码整改
|
||||
|
||||
## 一、总体结论
|
||||
|
||||
当前版本不建议直接进入实盘运行。
|
||||
|
||||
服务端存在文件编码导致的启动级错误,且所有 QMT 调用和大对象序列化都在 Tornado 主线程同步执行。客户端的持仓管理、止盈、补仓、撤单和下单确认链存在多处必现错误或状态不一致风险。
|
||||
|
||||
建议按以下顺序处理:
|
||||
|
||||
1. 恢复服务端和客户端的基本可运行性。
|
||||
2. 修复交易安全相关的订单确认、撤单和持仓数据模型。
|
||||
3. 为止盈、补仓、订单状态机建立测试。
|
||||
4. 在确认 QMT 线程约束后优化服务端响应速度。
|
||||
5. 最后进行结构简化和重复代码清理。
|
||||
|
||||
---
|
||||
|
||||
## 二、P0:启动及交易安全问题
|
||||
|
||||
### 2.1 服务端文件编码不一致,程序无法正常编译
|
||||
|
||||
位置:`api/QMT_API.py:1`
|
||||
|
||||
现状:
|
||||
|
||||
```python
|
||||
# -*- coding: gbk -*-
|
||||
```
|
||||
|
||||
文件实际内容包含 UTF-8 字节,只读编译时报错:
|
||||
|
||||
```text
|
||||
SyntaxError: 'gbk' codec can't decode byte ...
|
||||
```
|
||||
|
||||
影响:服务端可能在载入阶段直接退出,所有 API 不可用。
|
||||
|
||||
解决方案:
|
||||
|
||||
如果运行环境强制要求 GBK,则必须把整个文件真实转换为 GBK,不能只修改声明。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 中文日志和错误响应无乱码。
|
||||
|
||||
### 2.2 客户端持仓对象被错误当成字典和二元组使用
|
||||
|
||||
位置:`py-client/strategy/trend/positions.py:32-40`
|
||||
|
||||
现状:`client.positions()` 返回 `list[Position]`,但代码同时使用:
|
||||
|
||||
```python
|
||||
for idx, pos in positions:
|
||||
code = pos["stock_code"]
|
||||
avg_price = pos.get("avg_price", 0)
|
||||
```
|
||||
|
||||
行情结果同样是 `Tick` dataclass,却使用字典的 `.get()`。
|
||||
|
||||
影响:进入持仓管理后必然抛出 `TypeError` 或 `AttributeError`,止盈和补仓完全无法执行。
|
||||
|
||||
解决方案:
|
||||
|
||||
全项目统一使用 SDK dataclass + __slots__,不再混用原始字典。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 使用真实 `Position`、`Tick` 对象执行一轮不抛异常。
|
||||
|
||||
### 2.3 `handle_profit` 调用参数和函数签名不一致
|
||||
|
||||
位置:
|
||||
|
||||
- 调用:`py-client/strategy/trend/positions.py:58`
|
||||
- 定义:`py-client/strategy/trend/positions.py:73`
|
||||
|
||||
影响:修复持仓遍历后,下一步仍会立即触发 `TypeError`。
|
||||
|
||||
解决方案:
|
||||
删除未使用的 `open_price`、`strategy_name` 或把它们纳入统一模型。
|
||||
|
||||
推荐接口:
|
||||
|
||||
```python
|
||||
def handle_profit(
|
||||
runtime: Runtime,
|
||||
position: Position,
|
||||
tick: Tick,
|
||||
pnl_rate: float,
|
||||
) -> ProfitDecision:
|
||||
...
|
||||
```
|
||||
|
||||
验收标准:
|
||||
|
||||
- 静态类型检查能够发现参数数量错误。
|
||||
- ARMED、RAISED、STEADY、RETREAT 四种状态都有测试。
|
||||
|
||||
### 2.4 补仓流程存在多处必现错误
|
||||
|
||||
位置:`py-client/strategy/trend/positions.py:118-159`
|
||||
|
||||
问题包括:
|
||||
|
||||
- `StateItem` 被当成字典调用 `.get()`。
|
||||
- `orders.busy()` 多传入一个 `run` 参数。
|
||||
- 某些分支只返回 `False`,调用方却解包两个值。
|
||||
- 使用不存在的 `run.state.STATUS_ING`。
|
||||
- `state.added_num = +1` 每次都赋值为 1,并非累加。
|
||||
- `LOSS_TIERS[added_num]` 可能数组越界。
|
||||
- 下单后没有扣减本轮剩余预算,多持仓可能超额补仓。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 所有 `StateItem` 字段改为属性访问。
|
||||
2. `orders.busy(code, "BUY")` 使用正确签名。
|
||||
3. 所有返回分支统一返回结构,推荐使用 dataclass + __slots__:
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class TradeDecision:
|
||||
submitted: bool
|
||||
message: str = ""
|
||||
reserved_cash: float = 0.0
|
||||
```
|
||||
|
||||
4. 使用模块常量 `STATUS_ING`,或把状态定义成 `Enum`。
|
||||
5. 补仓次数使用 `state.added_num += 1`。
|
||||
6. 当 `added_num >= len(LOSS_TIERS)` 时明确禁止继续补仓。
|
||||
7. `RunOnce` 创建本轮 `remaining_cash`,每次成功提交补仓后立即扣减。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 第 0、1、2 次补仓边界均有测试。
|
||||
- 超过最大补仓次数不会抛异常或继续下单。
|
||||
|
||||
### 2.5 止盈跟踪器每轮重建,无法形成跨轮回撤
|
||||
|
||||
位置:`py-client/strategy/trend/positions.py:31`
|
||||
|
||||
影响:每轮都会清空最高盈利网格,止盈状态无法从 ARMED/RAISED 演进至 RETREAT。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. `GridTrailingTracker` 应作为 `Runtime` 字段,在策略启动时只创建一次。
|
||||
2. 检查是否有定时清理的功能
|
||||
|
||||
验收标准:
|
||||
|
||||
- 连续输入 2.1%、3.1%、2.9% 能产生 ARMED、RAISED、RETREAT。
|
||||
- 相同股票不同账户的峰值互不污染。
|
||||
- 清仓后重新建仓不会继承旧峰值。
|
||||
|
||||
### 2.6 “取消过期订单”只查询可撤状态,没有执行撤单
|
||||
|
||||
位置:
|
||||
|
||||
- 客户端:`py-client/strategy/trend/order.py:74-88`
|
||||
- 服务端:`api/QMT_API.py:962-969`
|
||||
|
||||
影响:过期订单一直保留,订单锁可能长期阻止新交易。
|
||||
|
||||
解决方案:
|
||||
|
||||
方案 A,推荐:新增按真实委托号撤单接口。
|
||||
|
||||
```text
|
||||
POST /api/order/cancel_by_id
|
||||
body: {order_id, account_type}
|
||||
```
|
||||
|
||||
服务端先执行 `can_cancel_order()`,可撤时调用真正的 `cancel()`,并返回撤单请求结果。
|
||||
|
||||
|
||||
验收标准:
|
||||
|
||||
- 暂时不做验证,后期验证
|
||||
|
||||
### 2.7 低现金资金闸同时跳过卖出管理
|
||||
|
||||
位置:`py-client/strategy/trend/boot.py:126-134`
|
||||
|
||||
影响:可用资金不足时直接结束整轮流程,持仓止盈和风险退出也被禁止。
|
||||
|
||||
解决方案:
|
||||
|
||||
把“是否允许新开仓/补仓”和“是否允许卖出”拆成不同条件。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 可用现金低于阈值时不开仓、可补仓。
|
||||
- 同一情况下满足止盈条件的持仓仍然能够提交卖单。
|
||||
|
||||
|
||||
### 2.9 客户端订单标签未真正传给 QMT
|
||||
|
||||
位置:
|
||||
|
||||
- 客户端发送:`py-client/sdk/trade.py:10-18`
|
||||
- 服务端丢弃:`api/QMT_API.py:663`
|
||||
|
||||
现状:客户端发送 `strategyName`,服务端调用 `passorder()` 时却硬编码为 `qmt`。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 统一:strategy_name 为信号的key,m_strRemark为本地业务订单号。
|
||||
2. 同时修改QMT_API.py
|
||||
|
||||
验收标准:
|
||||
|
||||
- 下单后在 QMT 委托明细中可以看到客户端标签。
|
||||
- 能从本地订单 ID 追踪到真实委托号和最终成交。
|
||||
|
||||
---
|
||||
|
||||
## 三、P1:服务端响应速度整改
|
||||
|
||||
|
||||
验收标准:
|
||||
|
||||
- 一轮策略账户查询由三次以上 QMT 调用下降为一次快照调用。
|
||||
- 下单后下一次快照不会返回过期的订单状态。
|
||||
|
||||
### 3.3 大量使用 `dir()` 和 `getattr()` 反射序列化
|
||||
|
||||
位置:
|
||||
|
||||
- `api/QMT_API.py:920-930`
|
||||
- `api/QMT_API.py:943-950`
|
||||
- `api/QMT_API.py:979-1031`
|
||||
- `api/QMT_API.py:1454-1473`
|
||||
|
||||
影响:对每个对象遍历全部属性、捕获异常并转字符串,CPU 开销大,返回字段也不稳定。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 为订单、成交、资产、持仓等类型定义固定字段映射。
|
||||
2. 只返回客户端实际使用的字段。
|
||||
3. 使用统一的轻量转换函数,不在每个 Handler 复制反射循环。
|
||||
4. 对未知扩展类型单独保留调试接口,不进入高频生产路径。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 高频订单查询不再调用 `dir()`。
|
||||
- 返回 JSON 字段固定并有接口契约测试。
|
||||
- 相同数据量下序列化 CPU 时间明显下降。
|
||||
|
||||
|
||||
### 3.5 回调同步写 JSON 文件
|
||||
|
||||
位置:`api/QMT_API.py:1475-1516`
|
||||
|
||||
影响:目录创建、反射序列化和格式化写盘可能阻塞 QMT 回调线程。
|
||||
|
||||
解决方案:
|
||||
|
||||
3. 生产环境关闭 `indent=4`。
|
||||
4. 使用临时文件替换,避免半写文件。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 回调函数本身在毫秒级返回。
|
||||
- 磁盘慢或不可写时不会阻塞交易回调。
|
||||
- 写入失败可监控且不会静默丢失。
|
||||
|
||||
### 3.6 客户端 HTTP 没有连接池
|
||||
|
||||
位置:`py-client/sdk/client.py:28-47`
|
||||
|
||||
影响:每次 `urlopen()` 都可能新建连接,高频轮询产生额外 TCP 开销。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 改用支持连接池的 HTTP 客户端,如 `httpx.Client` 或 `requests.Session`。
|
||||
2. 整个策略生命周期复用一个 Client。
|
||||
3. 设置连接、读取和总超时,不只设置单一 timeout。
|
||||
4. 只对幂等查询配置有限重试;下单和撤单不能自动盲重试。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 连续请求复用 TCP 连接。
|
||||
- 查询超时能重试,下单超时进入“结果未知、需对账”状态而不是重复下单。
|
||||
|
||||
---
|
||||
|
||||
## 四、P1:客户端其他逻辑与可靠性问题
|
||||
|
||||
### 4.1 新开仓订单锁可能在同一轮失效
|
||||
|
||||
位置:
|
||||
|
||||
- `py-client/strategy/trend/open.py:24`
|
||||
- `py-client/strategy/trend/order.py:61-65`
|
||||
- `py-client/strategy/trend/order.py:99-101`
|
||||
|
||||
现状:开仓检查 `busy()`,该方法只查看 `data`;新下单后只把键加入 `index`,没有加入 `data`。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 统一锁判断,只保留一个权威接口。
|
||||
2. 下单成功后立即插入本地 pending `OrderItem`。
|
||||
3. 信号进入处理前按证券代码去重。
|
||||
4. 每轮刷新券商订单后用真实订单覆盖本地 pending 状态。
|
||||
|
||||
验收标准:同一轮两个来源返回同一证券信号时最多提交一笔买单。
|
||||
|
||||
### 4.2 `Runtime` 文档和字段不一致
|
||||
|
||||
位置:`py-client/strategy/trend/runtime.py`
|
||||
|
||||
现状:文档描述 `peak_grids`,实际 dataclass 没有该字段;持仓代码仍可能访问它。
|
||||
|
||||
解决方案:
|
||||
|
||||
2. 如果统一使用 `GridTrailingTracker`,删除 `peak_grids` 及所有引用。
|
||||
3. 不应同时保留两套止盈峰值实现。
|
||||
|
||||
验收标准:项目中只有一种网格峰值状态来源。
|
||||
|
||||
### 4.3 `ping_api_host()` 参数无效且吞掉退出信号
|
||||
|
||||
位置:`py-client/main.py:59-76`
|
||||
|
||||
问题:
|
||||
|
||||
- `rpc_host` 参数没有使用。
|
||||
- `connect_timeout` 参数没有使用。
|
||||
- 使用裸 `except:`,会捕获 `KeyboardInterrupt` 和 `SystemExit`。
|
||||
- 无限重试没有最大日志节流或取消事件。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 函数重命名为 `wait_for_qmt_api()`,删除无用参数。
|
||||
2. 仅捕获网络类异常和 `APIError`。
|
||||
3. 允许 `KeyboardInterrupt` 正常终止。
|
||||
4. 使用 `threading.Event.wait()` 或可取消等待。
|
||||
|
||||
验收标准:API 不可用时可以通过 Ctrl+C 立即退出。
|
||||
|
||||
|
||||
|
||||
### 4.5 状态文件缺少完整对账和生命周期
|
||||
|
||||
位置:`py-client/strategy/trend/state.py`
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 启动时用真实持仓、订单和成交三方对账。
|
||||
2. `ING` 状态必须根据真实订单结果转为 `OK`、`FAILED`、`CANCELED` 或 `UNKNOWN`。
|
||||
3. 已清仓证券应从状态中删除,并清除观察器和止盈峰值。
|
||||
4. 状态文件不增加版本号,不增加新字段。
|
||||
|
||||
验收标准:程序在下单后崩溃并重启,能够从券商真实状态恢复,而不会重复下单。
|
||||
|
||||
### 4.6 日志调用格式错误且异常上下文不足
|
||||
|
||||
位置:`py-client/`
|
||||
|
||||
现状:不符合 logging 格式化规则。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 统一优化日志打印同时输出至文本文件(每天一个文件)。
|
||||
|
||||
验收标准:日志输出期间不出现 logging 自身的格式化异常。
|
||||
|
||||
204
docs/todo.md
Normal file
204
docs/todo.md
Normal file
@@ -0,0 +1,204 @@
|
||||
|
||||
### 2.8 服务端与客户端可能把失败下单当成成功
|
||||
|
||||
位置:
|
||||
|
||||
- 服务端:`api/QMT_API.py:652-669`
|
||||
- 客户端:`py-client/strategy/trend/order.py:90-102`
|
||||
|
||||
现状:服务端在 `order_ref` 为空时仍返回 `status=success` 和 `order_ref=unknown`;客户端不检查响应,直接返回 `True`。
|
||||
|
||||
影响:真实订单未提交,本地状态却进入 `ING`,后续可能长期锁仓或重复判断错误。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 服务端只有在获得有效订单引用或明确成功码时返回成功。
|
||||
2. 无订单引用时返回非 2xx,或返回 `status=failed` 并包含错误原因。
|
||||
3. SDK 将下单响应解析成明确的 `OrderResult` dataclass。
|
||||
4. `OrderBook.place()` 验证 `status` 和 `order_ref` 后才能写入锁并返回成功。
|
||||
5. 下单异常不能更新 `StateItem`。
|
||||
|
||||
验收标准:
|
||||
|
||||
- `order_ref=None`、空字符串、`unknown` 均被识别为失败。
|
||||
- 失败时本地订单锁和状态文件均不发生变化。
|
||||
- 成功时保存服务端返回的真实订单引用。
|
||||
|
||||
|
||||
### 3.1 同步 QMT 调用阻塞 Tornado 主线程
|
||||
|
||||
位置:所有同步 Handler,例如:
|
||||
|
||||
- `api/QMT_API.py:235`,行情查询
|
||||
- `api/QMT_API.py:279`,FullTick
|
||||
- `api/QMT_API.py:1095`,持仓查询
|
||||
- `api/QMT_API.py:1124`,资产查询
|
||||
- `api/QMT_API.py:1547-1553`,单 IOLoop 启动
|
||||
|
||||
影响:任意一个慢请求都会阻塞其他资产、行情和交易请求。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 首先确认 QMT API 是否允许跨线程调用,以及是否要求在策略主线程执行。
|
||||
2. 如果 QMT 要求固定线程:建立单一 QMT Worker 和任务队列,HTTP Handler 异步等待任务结果。
|
||||
3. 如果部分查询允许跨线程:仅将线程安全的查询放到受控线程池。
|
||||
4. 下单、撤单等有顺序要求的操作仍通过单一串行交易队列执行。
|
||||
5. 给每类任务设置超时、最大队列长度和请求标识。
|
||||
6. 不允许无限堆积;队列满时返回明确的 503。
|
||||
|
||||
推荐结构:
|
||||
|
||||
```text
|
||||
HTTP Handler
|
||||
-> Query Worker Pool(线程安全的只读查询)
|
||||
-> Trade Command Queue(串行下单/撤单)
|
||||
-> Short TTL Snapshot Cache
|
||||
```
|
||||
|
||||
验收标准:
|
||||
|
||||
- 一个耗时 2 秒的历史行情请求不会阻塞资产接口。
|
||||
- 下单和撤单仍保持提交顺序。
|
||||
- 压测期间队列长度和超时可观测。
|
||||
|
||||
|
||||
|
||||
### 3.2 资产、持仓和订单被重复查询
|
||||
|
||||
影响:客户端每轮会分别查询订单、资产、持仓和行情,产生多次 HTTP 与 QMT 往返。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 增加账户快照接口,一次返回资产、持仓和活动订单。
|
||||
2. 对同一账户的查询建立 100–500ms 短周期缓存。
|
||||
3. 交易命令执行后主动使相关缓存失效。
|
||||
4. 缓存只用于查询,不能缓存下单和撤单结果。
|
||||
5. 快照中返回统一的 `snapshot_time`,客户端可以判断数据新鲜度。
|
||||
|
||||
|
||||
### 3.4 大行情响应在主线程转换和编码
|
||||
|
||||
位置:`api/QMT_API.py:235-276`
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 限制股票数量、字段数量、日期跨度和最大响应体。
|
||||
2. 使用明确的 DataFrame 转换方向和紧凑 JSON 格式。
|
||||
3. 大结果支持分页、分批或文件下载。
|
||||
4. 启用 gzip/br 压缩,但要衡量 QMT 机器 CPU。
|
||||
5. 移除生产接口中的泛化 `default=str`,避免无意返回巨型对象字符串。
|
||||
6. 将允许异步处理的转换和 JSON 编码移出 IOLoop。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 超出范围的请求快速返回 400,不拖垮服务。
|
||||
- 大行情接口有响应大小和耗时指标。
|
||||
- 资产、下单等小请求的 P95 不受大查询明显影响。
|
||||
|
||||
### 4.4 信号配置被硬编码且运行期间不刷新
|
||||
|
||||
位置:`py-client/strategy/trend/boot.py:83-84`
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 使用账户配置中的 `signal_allow`,不要硬编码信号名。
|
||||
2. 明确刷新周期,例如每 1–5 分钟重新拉取。
|
||||
3. 拉取失败时保留最近一次成功快照,并记录快照时间。
|
||||
4. 信号按 `(signal_key, code)` 去重。
|
||||
5. 过期信号必须根据服务端 `updated` 或有效期淘汰。
|
||||
|
||||
验收标准:修改 YAML 后重启即可生效,长时间运行能获取新信号且不会重复下单。
|
||||
|
||||
|
||||
### 5.1 服务端 Handler 重复代码过多
|
||||
|
||||
现状:每个接口重复执行 JSON 解码、默认值转换、异常捕获和 JSON 编码。
|
||||
|
||||
解决方案:
|
||||
|
||||
1. `BaseHandler` 增加 `read_json()`、参数校验和 `write_json()`。
|
||||
2. 使用 dataclass 或轻量 schema 定义请求参数。
|
||||
3. 统一异常映射:参数错误 400、认证错误 401、业务冲突 409、服务不可用 503、未知错误 500。
|
||||
4. 抽取固定字段对象转换函数。
|
||||
5. 不要让 `safe_call()` 把所有错误统一变成 `None`。
|
||||
|
||||
收益:减少接口行为差异,降低维护成本,并使性能监控更容易统一接入。
|
||||
|
||||
|
||||
### 5.2 客户端 SDK 过度使用单行函数和动态字典
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 高频账户、持仓、订单和交易接口优先使用明确 dataclass。
|
||||
2. 长单行函数拆成可读的请求构造、发送和响应解析步骤。
|
||||
3. 为 `Client` 增加统一响应校验。
|
||||
4. 区分查询异常、业务失败、订单结果未知和明确拒单。
|
||||
5. 给所有交易方法增加输入校验:代码、方向、整手数量和金额。
|
||||
|
||||
|
||||
|
||||
### 5.3 止盈逻辑存在两套状态实现
|
||||
|
||||
现状:项目同时存在 `GridTrailingTracker` 和 `Runtime.peak_grids` 的设计痕迹。
|
||||
|
||||
解决方案:保留 `GridTrailingTracker` 作为唯一实现,将其放入 `Runtime`;删除旧字典逻辑和重复函数。
|
||||
|
||||
### 5.4 入口和配置使用全局可变状态
|
||||
|
||||
解决方案:
|
||||
|
||||
1. `config.load()` 返回配置后,由 `main()` 显式传给策略启动器。
|
||||
2. `StartTrend(global_cfg, account_cfg)` 不直接读取模块全局变量。
|
||||
3. 测试时可注入临时配置和模拟客户端。
|
||||
|
||||
|
||||
|
||||
### 5.5 缺少正式自动化测试
|
||||
|
||||
当前 `test.py` 是人工连通性脚本,不是完整测试套件。
|
||||
|
||||
建议至少建立:
|
||||
|
||||
- SDK 请求载荷和响应解析测试。
|
||||
- `Position`、`Tick`、`StateItem` 模型测试。
|
||||
- 时间段和交易时间测试。
|
||||
- 开仓去重测试。
|
||||
- 下单失败不更新状态测试。
|
||||
- 过期撤单测试。
|
||||
- 网格止盈状态机测试。
|
||||
- 补仓次数和预算测试。
|
||||
- 崩溃重启后的订单对账测试。
|
||||
- 服务端账户快照和固定字段序列化测试。
|
||||
|
||||
|
||||
### 阶段 1:建立基线
|
||||
|
||||
1. 为每个 Handler 记录请求总耗时、QMT 调用耗时、序列化耗时和响应大小。
|
||||
2. 记录并发请求数、任务队列长度、超时数和错误率。
|
||||
3. 分别测量资产、持仓、订单、FullTick、历史行情接口的 P50/P95/P99。
|
||||
|
||||
|
||||
### 阶段 2:低风险优化
|
||||
|
||||
1. 固定字段序列化,删除 `dir()` 反射。
|
||||
2. 合并账户快照接口。
|
||||
3. 添加短 TTL 查询缓存。
|
||||
4. 限制大查询范围和响应大小。
|
||||
5. 客户端启用连接池。
|
||||
|
||||
|
||||
### 阶段 3:并发模型优化
|
||||
|
||||
1. 先验证 QMT 的线程安全和线程亲和性。
|
||||
2. 建立查询 Worker 或单 QMT Worker 队列。
|
||||
3. 下单、撤单保持串行和幂等保护。
|
||||
4. 对大数据转换使用独立执行资源。
|
||||
|
||||
|
||||
|
||||
### 阶段 4:压力验证
|
||||
|
||||
1. 同时执行慢历史行情与高频资产查询。
|
||||
2. 在压力期间提交模拟下单和撤单。
|
||||
3. 验证交易请求延迟不会因数据查询无限增长。
|
||||
4. 验证服务端重启、超时和队列满时行为。
|
||||
Reference in New Issue
Block a user