# trading/ —— 自动交易执行壳(你的代码) > ⚠️ 这是**你的执行壳骨架**,不是策略,也不构成投资建议。它包装 `mtdata-cli` > 作为子进程,**不 import 任何 mtdata 内部模块**,因此不会被上游同步覆盖。 本目录实现自动交易所需的四个核心机制: ``` ┌──────────────────────────────────────────────────────────┐ │ signal_loop 信号循环:每个周期取行情 → 问策略 → 执行 │ │ state 状态机:IDLE→SIGNALED→PLACED→CLOSED │ │ circuit_breaker 熔断器:连续失败 N 次后停止交易 │ │ dry-run 分层 preview → dry_run → demo → live │ └──────────────────────────────────────────────────────────┘ ``` ## 快速上手 ```bash # 默认 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` 即可,然后: ```bash 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/LIVE_TRADING_CHECKLIST.md) (护栏、幂等、演练、首笔小额、运维清单),并阅读 [docs/TRADING_SAFETY.md](../docs/TRADING_SAFETY.md)。