name: run-simulations description: “在运行交易引擎模拟之前和之后使用。帮助:(1) 设置 - 选择配置,通过段集合选择段,批次大小(推荐 2,000-3,000 次运行);(2) 执行 - 使用 --collection 运行批次模拟;(3) 分析 - 运行后进行全面诊断。触发词:‘运行模拟’、‘测试配置’、‘批次模拟’、‘分析模拟结果’、‘测试哪些配置’、‘多少段’、‘模拟设置’。”
运行模拟技能
您正在帮助设置和运行交易引擎模拟,然后执行全面的诊断分析。
当前阶段:双重焦点(验证 + 优化)
系统置信度:约 85% 准确。 模拟系统大部分可信,但差异仍然存在且至关重要。
优先级 #1:准确性差异(最重要)
任何预期与实际行为之间的差距都是潜在的错误或误解。这些必须突出显示:
- 行为是否匹配配置参数?
- 数据中是否存在逻辑不一致?
- 结果是否与策略应该产生的一致?
当您发现差异时,请突出标记。 即使是小的准确性问题在数千次交易中也会累积。
优先级 #2:利润优化(主动研究)
在可信的系统下,我们现在主动研究:
- 哪些配置表现最佳?
- 哪些市场条件有利于哪些策略?
- 参数如何影响盈亏?
突出显示有趣的利润模式和准确性问题。两者现在都很重要。
重要:此技能应在运行模拟之前调用,以帮助设置决策(配置选择、通过集合选择段、批次大小)。
推荐批次大小
当前推荐:每批次 2,000-3,000 次模拟。
- 运行快速(使用 tick 缓存约 30-60 秒)
- 提供统计上有意义的结果
- 在当前阶段,超过 3,000 次模拟不会增加太多价值
- 公式:配置数 × 段数 = 总运行数(例如,20 配置 × 100 段 = 2,000 次运行)
阶段 1:设置与运行
⚠️ 用户输入优先
如果用户在调用此技能时提供任何参数、指令或上下文,这些绝对优先于以下所有默认值。
- 用户说 “运行配置 X” → 运行配置 X,忽略默认配置选择
- 用户说 “测试这 5 个段” → 测试那 5 个段,忽略默认段查询
- 用户问特定问题 → 回答那个问题,不要运行完整的默认工作流程
- 用户提供部分指令 → 用默认值填充空白,但尊重指定的内容
以下默认值仅在技能调用时没有任何参数时才使用。
默认行为(仅当无参数时)
使用 段集合 选择多样的配置和段(传统过滤器已弃用):
- 查询可用配置:
SELECT name, config FROM strategy_configs ORDER BY name - 选择 10-20 个覆盖不同指标类型的配置(EMA、RSI、布林带、组合)
- 选择或创建段集合(v1 中仅 AND 注释):
bun src/systems/trading-engine/bd-segments.ts list bun src/systems/trading-engine/bd-segments.ts create --name=high-activity --annotations=high_activity --limit=100 bun src/systems/trading-engine/bd-segments.ts preview --name=high-activity - 运行批次模拟:
bun src/systems/trading-engine/batch-runner.ts --config-pattern="<pattern>" --collection=high-activity --save
批次运行器 CLI 参考
位置: src/systems/trading-engine/batch-runner.ts
用法:
bun src/systems/trading-engine/batch-runner.ts [options]
选项:
| 选项 | 短选项 | 描述 |
|---|---|---|
--config=ID |
-c |
策略配置 ID(可指定多个) |
--all-configs |
-a |
运行所有可用策略配置 |
--config-pattern=PAT |
-p |
运行名称匹配模式的配置(SQL LIKE) |
--strategy-type=TYPE |
按策略类型过滤配置(波动性、套利、网格) | |
| `–collection=NAME | ID` | -C |
--show-selection |
预览集合选择并退出 | |
--capital=CENTS |
初始资本以分计(默认:10000 = $100) | |
--workers=N |
-w |
并行工作器数量(默认:8) |
--save |
-S |
将结果保存到数据库 |
--dry-run |
显示将要运行的内容而不执行 | |
--warmup-candles=N |
在段之前预加载 N 根蜡烛以进行指标预热(默认:50) | |
--db-pool-max=N |
主批次进程的 DB 池大小(默认:10) | |
--db-pool-max-worker=N |
工作器进程的 DB 池大小(默认:3) |
示例:
# 在段集合上运行一个配置
bun src/systems/trading-engine/bd-segments.ts create --name=swings100 --annotations=high_activity
bun src/systems/trading-engine/batch-runner.ts --config=abc123 --collection=swings100 --save
# 在集合上运行所有配置
bun src/systems/trading-engine/batch-runner.ts --all-configs --collection=swings50 --save
# 在特定段 ID 上运行所有配置(快照集合)
bun src/systems/trading-engine/bd-segments.ts create --name=segment-set --segment-ids=949,950,951,952 --mode=snapshot
bun src/systems/trading-engine/batch-runner.ts --all-configs --collection=segment-set --save
# 在 high_activity 段上运行匹配 "RSI-*" 的配置
bun src/systems/trading-engine/bd-segments.ts create --name=rsi-activity --annotations=high_activity
bun src/systems/trading-engine/batch-runner.ts --config-pattern="RSI-%" --collection=rsi-activity --save
# 干运行以查看将要执行的内容
bun src/systems/trading-engine/batch-runner.ts --all-configs --collection=swings100 --dry-run
关键 CLI 标志(快速参考)
--collection=NAME|ID或-C:必需的段集合选择器--show-selection:预览解析后的段列表并退出--config-pattern="TEST-%"或-p:按名称模式过滤配置--strategy-type=grid:按策略类型过滤配置
阶段 2:全面分析(始终运行)
模拟完成后,运行以下所有诊断查询。以表格形式呈现结果。
2.1 核心统计
SELECT
sc.name,
COUNT(*) as runs,
ROUND(AVG(trade_count)::numeric, 1) as avg_trades,
ROUND(STDDEV(trade_count)::numeric, 1) as stddev_trades,
MIN(trade_count) as min_trades,
MAX(trade_count) as max_trades
FROM (
SELECT r.id, sc.name, COUNT(t.id) as trade_count
FROM sim_runs r
JOIN strategy_configs sc ON r.config_id = sc.id
LEFT JOIN sim_trades t ON t.run_id = r.id
WHERE r.created_at > NOW() - INTERVAL '1 hour'
GROUP BY r.id, sc.name
) sub
JOIN strategy_configs sc ON sc.name = sub.name
GROUP BY sc.name
ORDER BY avg_trades;
2.2 入场信号频率(红旗:中位数 < 60 秒可疑)
WITH entries AS (
SELECT
t.run_id, sc.name as config, t.entry_at,
LAG(t.entry_at) OVER (PARTITION BY t.run_id ORDER BY t.entry_at) as prev_entry
FROM sim_trades t
JOIN sim_runs r ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour'
)
SELECT
config,
COUNT(*) as entry_gaps,
ROUND(AVG((entry_at - prev_entry)/1000.0)::numeric, 1) as avg_gap_sec,
ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY (entry_at - prev_entry)/1000.0)::numeric, 1) as median_gap_sec,
COUNT(*) FILTER (WHERE entry_at - prev_entry < 1000) as gaps_under_1s,
COUNT(*) FILTER (WHERE entry_at - prev_entry < 5000) as gaps_under_5s,
COUNT(*) FILTER (WHERE entry_at - prev_entry < 60000) as gaps_under_1min
FROM entries
WHERE prev_entry IS NOT NULL
GROUP BY config
ORDER BY median_gap_sec;
解释:中位数间隔 < 60 秒建议过度交易。健康配置的间隔应为分钟级(典型 20-30 分钟)。间隔 < 1 秒是主要红旗 - 表示指标不断触发。
2.3 持仓重叠
-- 平均有多少并发持仓?
WITH trade_events AS (
SELECT t.run_id, sc.name as config, t.entry_at as ts, 1 as delta
FROM sim_trades t
JOIN sim_runs r ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour'
UNION ALL
SELECT t.run_id, sc.name as config, t.exit_at as ts, -1 as delta
FROM sim_trades t
JOIN sim_runs r ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour' AND t.exit_at IS NOT NULL
),
running_pos AS (
SELECT run_id, config, ts,
SUM(delta) OVER (PARTITION BY run_id ORDER BY ts, delta DESC) as open_positions
FROM trade_events
)
SELECT
config,
MAX(open_positions) as max_concurrent,
ROUND(AVG(open_positions)::numeric, 2) as avg_concurrent,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY open_positions) as median_concurrent
FROM running_pos
GROUP BY config
ORDER BY avg_concurrent DESC;
2.4 持仓持续时间分布
SELECT
sc.name as config,
CASE
WHEN t.holding_duration_ms < 30000 THEN '<30s'
WHEN t.holding_duration_ms < 60000 THEN '30-60s'
WHEN t.holding_duration_ms < 120000 THEN '1-2min'
WHEN t.holding_duration_ms < 180000 THEN '2-3min'
WHEN t.holding_duration_ms < 240000 THEN '3-4min'
WHEN t.holding_duration_ms < 300000 THEN '4-5min'
ELSE '5min+'
END as hold_bucket,
COUNT(*) as trades,
ROUND(AVG(t.net_pnl_cents)::numeric, 1) as avg_pnl,
ROUND(COUNT(*)::numeric / SUM(COUNT(*)) OVER (PARTITION BY sc.name) * 100, 1) as pct
FROM sim_trades t
JOIN sim_runs r ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour' AND t.holding_duration_ms IS NOT NULL
GROUP BY sc.name, hold_bucket
ORDER BY sc.name, MIN(t.holding_duration_ms);
2.5 出场原因分析
SELECT
sc.name as config,
t.exit_reason,
COUNT(*) as trades,
ROUND(AVG(t.net_pnl_cents)::numeric, 1) as avg_pnl,
ROUND(COUNT(*)::numeric / SUM(COUNT(*)) OVER (PARTITION BY sc.name) * 100, 1) as pct
FROM sim_trades t
JOIN sim_runs r ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour'
GROUP BY sc.name, t.exit_reason
ORDER BY sc.name, trades DESC;
2.6 YES 与 NO 边分析(重要 - 检查不对称性)
SELECT
sc.name as config,
t.side,
COUNT(*) as trades,
ROUND(AVG(t.net_pnl_cents)::numeric, 1) as avg_pnl,
ROUND(AVG(CASE WHEN t.net_pnl_cents > 0 THEN 1 ELSE 0 END)::numeric * 100, 1) as win_pct,
ROUND(AVG(t.entry_price)::numeric, 1) as avg_entry_price,
ROUND(AVG(t.exit_price)::numeric, 1) as avg_exit_price,
ROUND(AVG(t.quantity)::numeric, 1) as avg_qty
FROM sim_trades t
JOIN sim_runs r ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour'
GROUP BY sc.name, t.side
ORDER BY sc.name, t.side;
解释:YES 和 NO 之间的显著不对称性表明价格转换或边选择逻辑可能存在错误。除非策略明确偏好一边,否则两边应有相似特征。
2.7 仓位大小验证
-- 检查填充是否匹配预期大小并与订单簿对齐
SELECT
sc.name as config,
t.quantity as fill_qty,
COUNT(*) as occurrences,
ROUND(AVG(t.entry_price)::numeric, 1) as avg_entry_price,
ROUND(AVG(t.net_pnl_cents)::numeric, 1) as avg_pnl
FROM sim_trades t
JOIN sim_runs r ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour'
GROUP BY sc.name, t.quantity
ORDER BY sc.name, t.quantity;
检查:数量是否匹配配置的 maxTradeSize?是否发生部分填充?与 maxPositionPerMarket 比较。
2.8 交易序列性能
WITH numbered_trades AS (
SELECT t.*, sc.name as config,
ROW_NUMBER() OVER (PARTITION BY t.run_id ORDER BY t.entry_at) as trade_num
FROM sim_trades t
JOIN sim_runs r ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour'
)
SELECT
config,
CASE
WHEN trade_num = 1 THEN '1st'
WHEN trade_num BETWEEN 2 AND 5 THEN '2-5th'
WHEN trade_num BETWEEN 6 AND 10 THEN '6-10th'
WHEN trade_num BETWEEN 11 AND 20 THEN '11-20th'
ELSE '21st+'
END as position,
COUNT(*) as trades,
ROUND(AVG(net_pnl_cents)::numeric, 1) as avg_pnl,
ROUND(AVG(CASE WHEN net_pnl_cents > 0 THEN 1 ELSE 0 END)::numeric * 100, 1) as win_pct
FROM numbered_trades
GROUP BY config, position
ORDER BY config, MIN(trade_num);
2.9 市场段分析
SELECT
ms.definition_name,
COUNT(DISTINCT r.id) as runs,
COUNT(t.id) as trades,
ROUND(AVG(r.total_pnl_cents)::numeric, 1) as avg_run_pnl,
ROUND(AVG((ms.metrics->>'swing_count')::int)::numeric, 1) as avg_swings,
ROUND(AVG((ms.metrics->>'reversion_rate')::float)::numeric, 2) as avg_reversion_rate
FROM sim_runs r
JOIN market_segments ms ON ms.source_meta->>'legacy_test_case_id' = r.test_case_id::text
LEFT JOIN sim_trades t ON t.run_id = r.id
JOIN strategy_configs sc ON r.config_id = sc.id
WHERE r.created_at > NOW() - INTERVAL '1 hour'
GROUP BY ms.definition_name
ORDER BY runs DESC;
2.10 运行健康检查
SELECT status, COUNT(*) as count
FROM sim_runs
WHERE created_at > NOW() - INTERVAL '1 hour'
GROUP BY status;
红旗:批次完成后任何 “running” 状态 = 崩溃的模拟。
阶段 3:日志分析(始终运行)
检查最近的模拟日志以查找异常:
# 查找最新的模拟日志
ls -lt logs/sim/ | head -5
# 从带有内容的最新日志中采样条目
LOG=$(ls -ltS logs/sim/*.log | head -1 | awk '{print $NF}')
echo "分析:$LOG ($(du -h "$LOG" | cut -f1))"
# 按类别检查日志量(性能红旗:>1M 指标日志)
echo "按类别的日志量:"
rg '"category"' "$LOG" | jq -r '.category' 2>/dev/null | sort | uniq -c | sort -rn
# 从时间戳检查运行持续时间
echo "运行持续时间:"
FIRST_TS=$(head -1 "$LOG" | jq -r '.time' 2>/dev/null)
LAST_TS=$(tail -1 "$LOG" | jq -r '.time' 2>/dev/null)
if [ "$FIRST_TS" != "null" ] && [ "$LAST_TS" != "null" ]; then
echo "持续时间:$(( (LAST_TS - FIRST_TS) / 1000 )) 秒"
fi
# 计算出场原因
echo "出场原因分布:"
rg '"reason"' "$LOG" | jq -r '.reason' 2>/dev/null | sort | uniq -c | sort -rn | head -20
# 检查错误
echo "日志中的错误:"
rg -i '"level":50' "$LOG" | head -10
# 检查警告
echo "日志中的警告:"
rg -i '"level":40' "$LOG" | head -10
查找:
- 异常的出场原因(非 profit_target、time_limit、collapse、end_of_data)
- 错误级别(50)条目
- 暗示错误的模式(重复相同条目、缺失字段)
- 性能:日志文件 > 500MB = 过度日志记录
- 性能:>1M 指标类别日志 = 调试日志记录瓶颈
阶段 3.5:性能分析
检查模拟吞吐量:
-- 最近批次的每秒运行数
SELECT
DATE_TRUNC('minute', created_at) as minute,
COUNT(*) as runs,
ROUND(COUNT(*)::numeric / 60, 1) as runs_per_sec
FROM sim_runs
WHERE created_at > NOW() - INTERVAL '1 hour'
GROUP BY minute
ORDER BY minute DESC
LIMIT 10;
预期吞吐量:使用 tick 缓存时 20-40 次运行/秒。如果 < 10 次运行/秒,调查:
- 是否使用 tick 缓存?(检查批处理脚本输出中的 “Pre-loaded”)
- 是否启用指标调试日志记录?(检查日志大小)
- 工作器是否受 I/O 限制?(检查工作器数量与 CPU 核心数)
阶段 4:分析摘要
双重焦点:突出准确性问题和利润见解。
首先:准确性检查(优先级 #1)
标记预期与实际行为之间的任何差异:
- 配置行为是否匹配参数?
- 是否有无法解释的模式或异常?
- 指标是否合乎逻辑?
准确性问题优先。 1% 的准确性错误可能使利润分析无效。
然后:利润见解(优先级 #2)
准确性验证后,分析:
- 哪些配置最有利可图?
- 市场条件中出现了哪些模式?
- 是否有优化机会?
验证问题回答
对于每个发现,问:“这符合我们的预期吗?如果不,为什么?”
| 观察 | 预期行为 | 实际 | 匹配? | 如果不,调查 |
|---|---|---|---|---|
| 入场间隔 | 基于指标参数 | ? | ? | 指标计算?蜡烛聚合? |
| 出场原因 | 平衡混合 | ? | ? | 出场逻辑?阈值错误? |
| YES 与 NO 性能 | 相似(市场对称) | ? | ? | 价格转换?边选择? |
| 仓位大小 | 匹配配置 maxTradeSize | ? | ? | 填充逻辑?深度解析? |
| 持仓持续时间 | 分布在各桶中 | ? | ? | 出场条件触发? |
| 交易序列 | 后期交易 ≈ 早期 | ? | ? | 状态累积错误? |
| 布林带/其他指标 | 应有时触发 | ? | ? | 指标实现? |
关键验证检查
-
指标触发频率
- 入场间隔是否与指标周期一致?
- EMA(20) 与 60 秒蜡烛 = 需要 20 分钟数据。我们看到入场比那更快是否有意义?
-
价格/边一致性
- YES 和 NO 应该是镜像。主要不对称性 = 可能的价格处理错误。
- 检查:我们是否为每边正确转换 bid/ask?
-
配置参数实际应用
- stopLossCents 是否实际在那个价格触发?
- profitTargetCents 是否实际触发?
- 是否检查 maxSpreadToEnter?
-
数据流完整性
- ticks 是否按顺序到达策略?
- 深度数据是否被正确解析?
- 填充是否被准确记录?
阶段 5:发现与下一步
基于分析,将发现组织成:
准确性关注(首先突出)
- 可能错误 - 暗示代码错误的差异
- “X 应该是 Y 但我们看到 Z” → 调查代码路径
- 不明确行为 - 我们尚不理解的事情
- “我们预期 X,得到 Y,不确定为什么” → 需要更深入调查
- 确认工作正常 - 匹配预期的事情
- “这按预期行为” → 增加置信度
利润见解(其次突出)
- 顶级表现者 - 哪些配置显示最佳结果?
- 市场模式 - 哪些条件有利于哪些策略?
- 优化机会 - 参数调整建议
下一步
- 准确性关注的调查优先级
- 验证利润假设的实验
- 要测试的配置变体
记住:准确性第一。突出差异,然后提供利润优化见解。两者都重要,但小的准确性错误可能使所有利润分析无效。