Root README now points at the auto-trading shell and research pipeline; AGENTS.md gains a keep-docs-in-sync rule; trading/README.md maps every method from the two research docs to trading/risk and documents manage_position; research/README.md documents the backtest hook and --backtest CLI. Co-Authored-By: Claude Code <noreply@anthropic.com>
trading/ —— 自动交易执行壳(你的代码)
⚠️ 这是你的执行壳骨架,不是策略,也不构成投资建议。它包装
mtdata-cli作为子进程,不 import 任何 mtdata 内部模块,因此不会被上游同步覆盖。
本目录实现自动交易所需的四个核心机制:
┌──────────────────────────────────────────────────────────┐
│ signal_loop 信号循环:每个周期取行情 → 问策略 → 执行 │
│ state 状态机:IDLE→SIGNALED→PLACED→CLOSED │
│ circuit_breaker 熔断器:连续失败 N 次后停止交易 │
│ dry-run 分层 preview → dry_run → demo → live │
└──────────────────────────────────────────────────────────┘
快速上手
# 默认 dry-run,安全。单跑一个周期看看会发生什么:
py -3.14 -m trading --once -v
# 持续轮询(默认不会发任何真实订单):
py -3.14 -m trading
# 强制预览层,验证当前策略/参数组合:
py -3.14 -m trading --once --tier preview
第一个周期会:查询账户 → 查持仓 → 拉 K 线 → 问策略 → 记录到
journal.jsonl。默认策略(strategies/example_strategy.py)被禁用,
不会产生任何信号。
安全层级(dry-run 分层)
TRADING_TIER 控制能触达的账户层级,只升不降:
| 层级 | 行为 | 何时用 |
|---|---|---|
preview |
连 CLI 都不发送真实请求语义,仅记录"将要发生什么" | 开发 |
dry_run(默认) |
所有交易命令带 --dry-run true |
演练 / CI |
demo |
允许真实发送,但仅当登录的是模拟账户(自动校验) | 模拟盘 |
live |
允许真实发送,且要求每次调用显式 send_live=True |
实盘 |
即使到 live,执行壳默认仍然 send_live=False(只预览);真实下单必须
同时满足:层级够 + 显式开启。这是最后的防呆。
环境变量
| 变量 | 默认 | 说明 |
|---|---|---|
TRADING_TIER |
dry_run |
preview / dry_run / demo / live |
TRADING_SYMBOL |
EURUSD |
交易品种 |
TRADING_TIMEFRAME |
H1 |
K 线周期 |
TRADING_POLL_SECONDS |
60 |
轮询间隔(秒) |
TRADING_MAX_LOTS |
0.01 |
回退手数:风险计算不可用时使用(也是历史上单一的上限) |
TRADING_RISK_PCT |
0.5 |
每笔风险占权益百分比;策略不显式给 volume 时据此计算手数 |
TRADING_MAX_OPEN_POSITIONS |
1 |
单品种最大持仓数 |
TRADING_MAGIC |
0 |
订单 magic,用于与策略分组 |
TRADING_COMMENT |
mtdata-shell |
订单注释 |
TRADING_STRATEGY |
strategies.example_strategy |
策略模块路径 |
TRADING_ENABLE_EXAMPLE_SIGNALS |
0 |
设为 1 才允许示例策略发信号 |
TRADING_INDICATORS |
rsi,ema |
拉 K 线时附带的指标 |
TRADING_GUARD_DAILY_LOSS_PCT |
(关) | 当日亏损占日初余额百分比阈值 |
TRADING_GUARD_DRAWDOWN_PCT |
(关) | 相对当日权益峰值回撤阈值 |
TRADING_GUARD_MAX_POSITIONS |
(关) | 全局最大持仓数上限 |
TRADING_GUARD_MAX_DAILY_TRADES |
(关) | 每日最多开仓笔数 |
TRADING_GUARD_COOLDOWN_SECONDS |
(关) | 平仓后禁止新开的冷却秒数 |
TRADING_GUARD_SESSION |
(关) | 允许交易时段,如 09:30-16:00 |
TRADING_GUARD_MAX_SPREAD_PCT |
(关) | 点差超过价格百分比时拒绝入场 |
MTDATA_CLI |
mtdata-cli |
CLI 路径(或可执行文件全路径) |
TRADING_STATE_FILE |
state.json |
状态机持久化文件 |
TRADING_JOURNAL_FILE |
journal.jsonl |
追加式审计日志 |
文件地图
| 文件 | 职责 |
|---|---|
main.py |
入口:--once / 轮询 / --tier 覆盖 |
config.py |
环境变量 → ExecutionConfig(frozen dataclass) |
signal_loop.py |
主循环 + 策略加载 + 持仓管理 pass + 执行编排 |
state.py |
交易周期状态机,崩溃后可恢复 |
circuit_breaker.py |
连续失败熔断(CLOSED→OPEN→HALF_OPEN) |
executor.py |
mtdata-cli 子进程包装 + dry-run 分层 + 护栏解析 |
journal.py |
追加式 JSONL 审计日志 |
types.py |
TradeSignal / SignalContext 共享类型 |
risk/broker.py |
品种规格 SymbolSpec:pip/point、手数取整、止损距离规范 |
risk/sizing.py |
风险百分比手数计算 + 回退入口 suggest_volume |
risk/exits.py |
出场规则库:保本、经典/ATR/吊灯/均线移动止损、时间止损、分批平仓 |
risk/guards.py |
账户级入场闸门(日亏损/回撤/持仓数/冷却/时段/点差),默认全关 |
risk/indicators.py |
均线/RSI/ATR 等纯 numpy 指标(策略内部共用) |
strategies/ |
策略模块(signal(ctx) 接口) |
写自己的策略
任何模块只要有 signal(ctx) -> TradeSignal | None 即可,然后:
TRADING_STRATEGY=my_package.my_strategy py -3.14 -m trading --once -v
研究侧的模型(见 research/)最终也是通过这个接口进入执行壳:训练好的
模型在 signal() 里对最新特征做推理,返回 TradeSignal。
ctx(SignalContext,见 types.py)提供一次决策需要的全部输入:
symbol / timeframe / candles(OHLCV 列表,ctx.closes() 取收盘价)/
quote / account / positions(当前持仓)/ as_of。ctx.symbol_spec 是
symbols_describe 的 best-effort 载荷,策略可用
trading.risk.broker.SymbolSpec.from_payload 解析成规格(pip / 手数步长 /
最小止损距离等),再配合 risk/sizing.py 的 suggest_volume 自算带止损的手数。
TradeSignal.volume 留空时,循环按风险百分比定手数(见上表
TRADING_RISK_PCT);显式给 volume 则始终优先(权威顺序:
显式 volume > 风险百分比 > TRADING_MAX_LOTS 回退)。
持仓管理(可选)
有持仓时,循环每周期先调用策略的 manage_position(ctx, position) 再做
开仓决策;离线回测器在同一根 K 线上也调用它 —— 出场决策两侧共用同一份。
返回 None 或 PositionAction("hold") = 不动(持仓继续由服务器端挂单
SL/TP 管理)。要主动管理时返回(PositionAction 见 types.py):
PositionAction("modify", stop_loss=..., take_profit=...)→ 移损 / 改止盈;trading/risk/exits.py的update_breakeven、update_classic_trailing、evaluate_exits等会直接产出这些参数;PositionAction("close", reason=...)→ 全部平仓;PositionAction("partial_close", close_volume=..., reason=...)→ 平掉close_volume手,剩余继续持有。
实盘由 Executor.modify / close(dry-run 分层)执行;回测按同一语义模拟
(research/backtest.py 的 apply_position_action)。策略不实现该方法 =
不做任何动态出场,行为与旧版完全一致。管理动作失败只会记日志、熔断记数,
不会中断循环。
风控库:两份梳理文档的抽象落点
把根目录的《止损方法梳理与伪代码》《仓位管理方法梳理》抽象进了
trading/risk/(不保证 100% 保留,重点是留下可复用、可组合的积木):
| 文档里的方法 | 落在哪里 | 说明 |
|---|---|---|
| pip/point 规则(3/5 位小数) | risk/broker.py SymbolSpec.pip |
所有距离换算的单一来源 |
| 手数归一化(向下取整、低于最小手数归零、封顶) | risk/broker.py normalize_volume |
一次性收口佣金/点差外的手数规则 |
| 止损距离规范(stop level) | risk/broker.py min_stop_distance |
防"止损贴太近"被拒单 |
| 风险百分比 → 手数 | risk/sizing.py size_lot |
公式来自文档;不可算时明说原因 |
| R 冻结在开仓价(|开仓−初始止损|) | risk/exits.py initial_risk / r_multiple |
后续移损不改变 R |
| 保本止损 | risk/exits.py update_breakeven |
支持正向偏移,幂等 |
| 经典/ATR/吊灯/均线移动止损 | risk/exits.py update_classic_trailing / update_atr_trailing / update_chandelier / update_ma_trailing |
都遵守"只许收紧"的单调不变量 |
| 时间止损 | risk/exits.py time_stop_exceeded |
支持时长与K线数两种口径 |
| 分批平仓 | risk/exits.py plan_partial_close |
剩余仓位必须 ≥ 最小手数 |
| 组合优先级(硬出场 > 分批 > 移损) | risk/exits.py evaluate_exits |
一个出口函数折叠所有规则 |
| 日亏损/回撤/持仓数/冷却/时段/点差闸门 | risk/guards.py |
环境变量武装,默认全关 |
| 指标(均线/RSI/ATR) | risk/indicators.py |
策略侧 strategies/indicators.py 已改为转出这些名字 |
暂缓、未纳入(策略空间,留给你的研究决定何时接):网格 / DCA / 马丁格尔 / 回撤恢复区 / 对冲 / 新闻停损。它们需要独立的策略语义,不适合 在风控库层面预设行为。
实盘前必读
切换 TRADING_TIER=live 之前,请完整核对
docs/LIVE_TRADING_CHECKLIST.md
(护栏、幂等、演练、首笔小额、运维清单),并阅读
docs/TRADING_SAFETY.md。