2026-10-22 21:07:42 +08:00
|
|
|
|
# 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` | 轮询间隔(秒) |
|
2026-10-25 20:21:46 +08:00
|
|
|
|
| `TRADING_MAX_LOTS` | `0.01` | 回退手数:风险计算不可用时使用(也是历史上单一的上限) |
|
|
|
|
|
|
| `TRADING_RISK_PCT` | `0.5` | 每笔风险占权益百分比;策略不显式给 volume 时据此计算手数 |
|
2026-10-22 21:07:42 +08:00
|
|
|
|
| `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 线时附带的指标 |
|
2026-10-25 20:21:46 +08:00
|
|
|
|
| `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` | (关) | 点差超过价格百分比时拒绝入场 |
|
2026-10-22 21:07:42 +08:00
|
|
|
|
| `MTDATA_CLI` | `mtdata-cli` | CLI 路径(或可执行文件全路径) |
|
|
|
|
|
|
| `TRADING_STATE_FILE` | `state.json` | 状态机持久化文件 |
|
|
|
|
|
|
| `TRADING_JOURNAL_FILE` | `journal.jsonl` | 追加式审计日志 |
|
|
|
|
|
|
|
|
|
|
|
|
## 文件地图
|
|
|
|
|
|
|
|
|
|
|
|
| 文件 | 职责 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| `main.py` | 入口:`--once` / 轮询 / `--tier` 覆盖 |
|
|
|
|
|
|
| `config.py` | 环境变量 → `ExecutionConfig`(frozen dataclass) |
|
2026-10-25 20:21:46 +08:00
|
|
|
|
| `signal_loop.py` | 主循环 + 策略加载 + **持仓管理 pass** + 执行编排 |
|
2026-10-22 21:07:42 +08:00
|
|
|
|
| `state.py` | 交易周期状态机,崩溃后可恢复 |
|
|
|
|
|
|
| `circuit_breaker.py` | 连续失败熔断(CLOSED→OPEN→HALF_OPEN) |
|
|
|
|
|
|
| `executor.py` | `mtdata-cli` 子进程包装 + dry-run 分层 + 护栏解析 |
|
|
|
|
|
|
| `journal.py` | 追加式 JSONL 审计日志 |
|
|
|
|
|
|
| `types.py` | `TradeSignal` / `SignalContext` 共享类型 |
|
2026-10-25 20:21:46 +08:00
|
|
|
|
| `risk/broker.py` | 品种规格 `SymbolSpec`:pip/point、手数取整、止损距离规范 |
|
|
|
|
|
|
| `risk/sizing.py` | 风险百分比手数计算 + 回退入口 `suggest_volume` |
|
|
|
|
|
|
| `risk/exits.py` | 出场规则库:保本、经典/ATR/吊灯/均线移动止损、时间止损、分批平仓 |
|
|
|
|
|
|
| `risk/guards.py` | 账户级入场闸门(日亏损/回撤/持仓数/冷却/时段/点差),默认全关 |
|
|
|
|
|
|
| `risk/indicators.py` | 均线/RSI/ATR 等纯 numpy 指标(策略内部共用) |
|
2026-10-22 21:07:42 +08:00
|
|
|
|
| `strategies/` | 策略模块(`signal(ctx)` 接口) |
|
|
|
|
|
|
|
|
|
|
|
|
## 写自己的策略
|
|
|
|
|
|
|
|
|
|
|
|
任何模块只要有 `signal(ctx) -> TradeSignal | None` 即可,然后:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
TRADING_STRATEGY=my_package.my_strategy py -3.14 -m trading --once -v
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
研究侧的模型(见 `research/`)最终也是通过这个接口进入执行壳:训练好的
|
|
|
|
|
|
模型在 `signal()` 里对最新特征做推理,返回 `TradeSignal`。
|
|
|
|
|
|
|
2026-10-25 20:21:46 +08:00
|
|
|
|
`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 /
|
|
|
|
|
|
马丁格尔 / 回撤恢复区 / 对冲 / 新闻停损。它们需要独立的策略语义,不适合
|
|
|
|
|
|
在风控库层面预设行为。
|
|
|
|
|
|
|
2026-10-22 21:07:42 +08:00
|
|
|
|
## 实盘前必读
|
|
|
|
|
|
|
|
|
|
|
|
切换 `TRADING_TIER=live` 之前,请完整核对
|
|
|
|
|
|
[docs/LIVE_TRADING_CHECKLIST.md](../docs/LIVE_TRADING_CHECKLIST.md)
|
|
|
|
|
|
(护栏、幂等、演练、首笔小额、运维清单),并阅读
|
|
|
|
|
|
[docs/TRADING_SAFETY.md](../docs/TRADING_SAFETY.md)。
|