# A 股 ETF 自适应网格策略 策略目录为 `py-client/strategy/etf`,入口名称 `etf`,独立配置为 `py-client/etc/etf.yaml`。每只 ETF 总持仓上限 10 手(1000 份),默认每次买入 1 手。 这是常见均值回归与波动率网格方法的工程组合,尚未完成历史收益回测。 ## 方法分析 | 方法 | 优点 | 局限 | 本策略选择 | | --- | --- | --- | --- | | 固定价差网格 | 简单直观 | 不适应不同价格与波动率 | ATR 动态格距,设置百分比下限 | | 均线回归 | 提供相对高低位置 | 单边下跌中均线会滞后 | MA60 作中轴,设置仓位上限 | | BOLL 低吸 | 用价格分布寻找相对低位 | 触及下轨不代表跌势结束 | 下轨仅启动观察,反弹后再买 | | 回撤止盈 | 上涨时跟随峰值 | 不能保证最高价退出 | 复用现有 GridTrailingTracker | ATR 反映波动幅度,不判断方向;BOLL 为均线加减标准差倍数。定义参考 [Fidelity ATR](https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/atr) 及 [Fidelity BOLL](https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/bollinger-bands)。 默认参数是可调整的起点,不代表已经优化或保证收益。 ## 指标与网格 历史日线直接读取 `http://go.apinb.com/a/get_daily?code=<证券代码>`,使用响应 `details` 中的 `ts_code / trade_date / open / high / low / close`。校验 `code=0`、 证券归属、日期和 OHLC 后,按日期排序,排除当天及未来数据,再取最近 120 根。 每个标的每天计算一次并缓存,至少需要 60 根;ATR 周期为 60 时至少需要 61 根。 接口样本未声明复权口径,策略使用接口原始价格,不自行假定或执行前复权。 - `M = 最近60根收盘价平均值`,固定 MA60。 - `TR = max(最高-最低, abs(最高-前收盘), abs(最低-前收盘))`。 - ATR 默认 14 日:前 14 个 TR 平均作初值,后续按 `ATR = (前ATR × 13 + 当日TR) / 14` 进行 Wilder 平滑。 - BOLL 默认 20 日、2 倍总体标准差:`中轨=MA20`,`上下轨=MA20 ± 2σ`。 - 格距 `G = max(ATR × atr_multiplier, M × min_grid_pct / 100, 0.001)`, 向上取整至 0.001 元。均线作中轴,ATR 决定每格宽度。 重复日期、非正数或非有限价格、价格关系异常、日线不足均不交易。 最后日线超过 15 个自然日也不交易;该检查只排除明显过期,不能替代交易所日历。 接口数据应更新至上一交易日。获取失败的标的每 5 分钟重试。 ## 买入规则 1. 首仓在 `现价 <= min(BOLL下轨, M-G)` 时开始观察,不直接买入。 2. 复用 `DipWatch` 防接飞刀:下跌刷新低点,从低点反弹默认 0.61% 后触发; 观察默认 600 秒过期重置。反弹可站回下轨上方,但不能超过 MA60。 3. 加仓还须满足 `现价 <= 上次实际买入成交均价-G`,防止同价位连续补满。 初次接管已有仓位时,券商成本作为初始加仓基准。 4. 每次买入 `buy_hands × 100` 份;若买后超过 `max_hands × 100`,整笔跳过, 不临时缩量。配置强制 `1 <= buy_hands <= max_hands <= 10`。 5. 买入预算为券商可用资金减账户现金安全线,再减所有本地待确认买单。 限价金额加预估佣金占用预算,多标的串行扣减。 示例:MA60=4.00、G=0.05、下轨=3.90,价格进入 3.90 以下才观察;低点 3.88 反弹至 3.904 时超过 0.61%,可以提交固定手数。如果实际均价为 3.904, 下一笔最高买价为 3.854,同时仍须满足低位触发和反弹确认。 ## 止盈规则 1. `现价 >= max(BOLL上轨, M+G, 成本+G)`,且满足 `min_profit_pct`、 预估价差收益大于买卖两侧佣金,才启动高位跟踪。 2. 启动时冻结格距。盈利格编号为 `floor((现价-成本)/冻结格距)`,复用 `GridTrailingTracker` 记录最高格;进入更高格更新峰值,跌回较低格时止盈。 这是跌破峰值格边界,不是从最高价回撤完整一个 ATR。 3. 启动后,即使回落到 BOLL 上轨或启动价以下,也继续判断回撤;卖出时仍须满足 最低利润和费用门槛。止盈启动后暂停补仓,不同时发买卖单。 4. **固定手数用于买入;止盈卖出当前全部可用整手份额**,数量为 `min(持仓, 券商可卖数量)` 向下取整到 100 份。零股暂不处理。 5. T+1 当天不可卖时继续记录峰值,翌日按券商 `can_use_volume` 判断。 清仓、实际成交改变仓位、外部数量或成本变化后重建基准;部分卖出后剩余仓位 重新等待高位启动,不把旧峰值带入新仓。 交易单位、0.001 元报价精度及股票 ETF 的 T+1 参考 [上交所 ETF 常见问题](https://www.sse.com.cn/assortment/fund/etf/question/)。 本策略没有自动止损,单边下跌可能满仓后长期持有;10 手上限只限制数量。 佣金参数按券商实际情况调整。费用门槛是估算,不逐笔归集历史买入最低佣金; 若券商持仓成本已含费用,该估算会偏保守。 ## 委托、持仓与持久化 - 管理 `codes` 白名单内的已有持仓;`excluded_codes` 优先排除。配置外证券不买卖。 - 以 `ETF-BUY-*` / `ETF-SELL-*` 为本地编号,标签 `etf`,当前价按 0.001 元 精度提交限价。复用 OrderBook,只自动撤销超时 120 秒的 ETF 前缀委托。 - 同标的全账户买卖在途、未知委托状态或在途份额都会阻止新单。 行情缺少 `timetag`、不是当天或超过默认 90 秒,也不交易。 - 下单前原子保存意图,HTTP 成功、超时或失败均不会自动解除锁。 必须收到终态(53/54/56/57),并且券商持仓与累计成交量相符,才能继续。 部分成交按实际数量核对,买入必须取得实际成交均价才推进下一格。 - 状态位于 `{qmt_data_dir}/etf/{账户SHA256}/state.json`,保存实际买入基准、 止盈峰值、冻结格距和未确认委托;重启恢复,损坏不静默覆盖。 - 同一 ETF 不适合同时由人工或其他策略频繁交易。在途期间外部改变持仓,会暂停核对。 若跨日后柜台不再返回未确认订单,则持续暂停该标的,需要核对历史订单与持仓后 人工处理状态,不按超时自动重发。有未确认委托的标的不能直接从配置移除。 ## 启用 1. 修改 `etc/etf.yaml` 的标的及参数。示例仅示范格式,请选择实际交易的 A 股股票 ETF; 代码形态检查不验证基金投资范围。 2. 主机对应的账户 YAML 设置: ```yaml strategy: etf etf_config_path: etf.yaml enable_auto_ipo: false ``` 路径相对账户配置目录,也支持绝对路径;不填默认 `etc/etf.yaml`。 `account_id`、`min_cash_ratio` 继续生效。公共校验仍要求 `buy_value`、 `grid_step_pct` 为正,但 ETF 不用它们计算数量或格距。 关闭 IPO 是此处示例选择;ETF 决策不依赖 IPO 或远端股票信号。 3. 无需修改 QMT 服务端或 `sdk/`。历史接口适配完全位于 `strategy/etf/data.py`; 实时行情、持仓和交易继续使用已有 QMT SDK。 4. 确认外部接口提供配置 ETF 的足量、最新日线。HTTP 错误、业务失败、空列表、 证券代码不一致或数据异常都会跳过该标的,不改用其他证券数据。 2026-09-17 联通验证中,股票样本 `600584.SH` 成功返回 200 条,示例 ETF `510300.SH` 返回 404;需要数据服务覆盖实际配置的 ETF 后才能正常运行。 5. 按现有方式运行 `python main.py`。每 30 秒执行,午休暂停,15:00 退出。 本次新增不会自动切换已有实盘账户,也没有进行实盘委托。 ## 离线验证 在 `py-client` 中运行: ```powershell python -m unittest discover -s tests -p test_etf.py -v python -m unittest discover -s tests -v ``` 覆盖指标、未收盘日线排除、反弹确认、固定手数、限仓、资金共享、部分成交、拒单、 快照延迟、T+1、重启防重、峰值恢复、状态损坏、外部日线接口与异常响应。 离线行为验证不等同于历史收益回测或实盘联调。