BotDeepSeek/AGENTS.md

14 KiB
Raw Blame History

AGENTS.md — AI 助手项目上下文

本文件供 AI 编码助手(如 OpenCode在新会话中快速恢复项目上下文。 修改本项目后请同步更新本文件。

项目概述

Interactive Brokers 股票自动交易机器人Python + ib_insync多策略并行。 实盘运行:账户 U4845070IB Gateway 端口 4001Gateway Live。 另有账户内独立持仓 HF x300 —— 永远不属于 bot 管理,禁止触碰。

交易标的1分钟K线每策略每标的最多1笔AMZN、AAPL、NVDA、NOK2026-08-03 起加 NVDA、NOK此前为11只科技股的组合已停用。 每标的持仓价值上限 max_symbol_value_usd=$2300三策略合计tracker.symbol_value() 计算)。

文件结构与职责

文件 职责
config.py dataclass 配置;自动加载 .envpython-dotenvstate_file 为基于 __file__ 的绝对路径
connection.py IB 连接单例 ib_connconnect/reconnect/ensure_connected
bars.py BarManager:每个合约一条 keepUpToDate=True 实时K线订阅多策略共享to_completed_df 丢弃未收盘baris_market_active 通过最新bar时效判断开闭市
state.py PositionTracker:持仓归属(哪个策略拥有哪笔仓位),持久化到 bot_state.json;启动/重连时 reconcile() 对账;每笔买卖追加写入 trades.jsonl 流水账本(首次运行以当前持仓为 seed
daily_report.py / report.sh 当日成交明细+盈亏报告FIFO数据源 trades.jsonl);用法 ./report.sh [YYYY-MM-DD]
orders.py execute_market_order:下单+等待成交30s超时撤单+防重复单(has_open_order,按 orderRef 匹配)
strategies/ ma_cross(SMA20/50+ADX)、short_term(EMA5/10+VWAP+max_hold)、mean_reversion(RSI/布林/急跌抄底)、forex(默认关闭)
main.py 主循环60sdisconnectedEvent 只注册一次且有并发/关机防护;重连后 bar_manager.reset() + 重新 on_start

关键设计决策(不要违反)

  1. 持仓归属IB 只报账户级持仓,归属靠 bot_state.json。策略只卖自己的持仓。账户中未被追踪的持仓标记为 UNMANAGED永不出卖。
  2. 交易所硬止损(主)+ 软止损(兜底):买入成交后立即挂 GTC STP 卖单(stop_loss_pctoutsideRth=Truebot 停机/断线期间仍由交易所执行。base.py 止损单机制:每周期 _sync_stop_orders()成交→record_sell终态订单按 _trade_uid(permId/orderId) 只消费一次只认领未完成订单owned 但无单→补挂);策略卖出前 _cancel_stop() 并处理撤单竞态中的成交;软止损仅在 _has_active_stop()==False 时兜底。外汇仍用软止损(市场连续,且策略默认关闭)。
  3. 卖出不阻塞:死叉类退出若盈利不足 min_profit_pct1.5%每轮重检(不是等下次交叉事件),达标即卖。
  4. 按金额定股数:每笔 trade_value_usd$200007-27 从 $1000 提高)按信号价折算(base._order_quantityfloor、最少1股佣金占比约 0.05%+0.1%往返。
  5. 全局持仓上限config.max_positions12 批,≈$24k 敞口07-28 从 15 调低)。策略买入前检查 tracker.total_positions(),达上限则跳过买入(日志 SKIPPED已有持仓正常管理退出不强制平仓。
  6. 每日亏损熔断config.max_daily_loss$150PositionTracker 按 record_sell 实时累计当日已实现盈亏(持久化 day_pnl.json,跨重启保留、本地午夜重置),触限后当日禁止开新仓(base._can_open_position 统一闸门,返回原因)。
  7. 止损/失败冷却:同一策略同一标的被止损(硬止损成交或软止损卖出)后 stop_cooldown_minutes30分钟内禁止重买下单被拒/超时也进冷却(防每周期重试刷屏)。
  8. 全局卖出冷却(跨策略防 flip-flop:任何策略卖出某标的后,所有策略在 sell_cooldown_minutes30分钟内禁止买入该标的超过冷却但仍在 sell_improvement_window_minutes60分钟重买价必须 ≥ sell_improvement_pct0.5%)低于最近卖价。登记点统一在 tracker.record_sell / _record_external_sell(覆盖策略卖出、硬止损成交、撤单竞态成交、断线回填),持久化到 recent_sells.json跨重启保留惰性清理。2026-08-04 修复当日 3 起同价 flipNOK 卖→买跨策略、NVDA 卖→买(同策略 4 分钟原价买回、AAPL 卖→买(跨策略)。
  9. MeanRev 趋势过滤:仅在 close > SMA(trend_ma_period=50) 时允许抄底,避免下跌趋势中接飞刀。
  10. 信号只用已收盘K线:最后一根 forming bar 必须丢弃(时间戳比较法)。
  11. 休市禁交易最新bar年龄 > bar_seconds*5 视为休市跳过全部信号。收盘后约5分钟内 bar 仍"新鲜",此窗口的市价单会隔夜排队——收市停机需提前(参考 15:59 EOD 停止的做法)。
  12. K线流量:禁止每轮全量拉历史数据(旧版 5 小时 845MB用 keepUpToDate 订阅(约 3.5MB/5小时formatDate=2epoch时区无歧义

⚠️ 已修复的 bug勿重新引入

  • ADXdown_move = -low.diff()Wilder 定义),不是 low.diff().abs()。abs 版本会把上涨中的低点抬升误记为空头 DM导致趋势方向完全颠倒。
  • RSIrs = avg_gain / avg_loss 直接除,让 inf 自然传播纯涨→RSI 100。不要 .replace(0, nan),否则纯涨时 RSI 变 NaN。
  • state_file 必须绝对路径cron/nohup 启动时 CWD 是 $HOME,相对路径会把状态文件写错位置。
  • 从 shell 工具启动 bot 必须 setsidsetsid nohup .venv/bin/python main.py >> trading_bot.log 2>&1 < /dev/null &。否则终端会话结束/工具超时杀进程组时 bot 会被带走。
  • BarManager.subscribe 竞态:先存 in-flight Task 再 await重连任务的 on_start 与主循环 on_bar 会并发订阅同一合约,旧代码 await 后才存导致重复订阅+订阅泄漏)。主循环断线期间直接跳过周期。
  • 断线窗口的止损成交必须回填账本STP 在 bot 断线时成交bot 收不到事件;reconcile() 清理/裁剪仓位时必须调用 _record_external_sell 补记 trades.jsonl(价格取 reqCompletedOrders 实际成交价,取不到则按止损价 -2% 估记并标 est:true。否则报告出现幽灵持仓、漏记亏损07-30 修复,含一次性历史修复)。
  • 重连事件重复注册、关机后误触发重连:已在 main.py 修复,注意保持 _running_reconnect_task 防护逻辑。
  • reqCompletedOrdersAsync 必须传 apiOnly 参数ib_insync 0.9.86 起签名必填。bot 回填断线 STP 成交用 apiOnly=True(只取 API 订单,天然排除 TWS 手工单)。不传会抛 TypeError断线回填静默失效07-31 修复)。
  • reqCompletedOrdersAsync 会把已完成订单注入 wrapper.trades 造成"幻影订单"ib_insync 0.9.86 的 wrapper.completedOrderwrapper.py:402以非终态 OrderStatusfilled=0、status 取 orderState.status把全部已完成订单塞进 ib.trades()/ib.openTrades()——同 ref 的僵尸单(如 08-03 遗留 NOK STP在重启后以 PreSubmitted/done=False 现形 → has_open_order 永远返回 True → 该 key 的止损永不补挂持仓裸奔done 幻影还会触发每轮 "ended with status=Filled - re-placing now" 噪音。防御08-04 修复):main._completed_stp_fills 收集完成交价后按 permId 把幻影从 ib.wrapper.trades 弹出;orders.has_open_order 另加 remaining > 0 硬条件(幻影 remaining 恒为 0真实在挂单 >0
  • 外部成交价 0.0 必须按"未知"处理reqCompletedOrders 对跨会话(上次 bot 会话)的已完成订单可能返回 avgFillPrice=0.0main.py_completed_stp_fills 要跳过 fill_price <= 0 的订单;state.py_record_external_sellestimated = not priceNone 或 0.0 都视为取不到)→ 按止损价 -2% 估记并标 est:true。否则会把 0.0 当真价写账本亏损虚算07-31 修复:迁移目录时踩中,造成 day_pnl 虚增 -1668.74)。
  • 僵尸 STPPreSubmitted 永不变更)+ has_open_order 造成"假卡死":被风控拒掉/IB 端失同步的 STP 会以 PreSubmitted 卡在 ib.openTrades()isDone()=Falsehas_open_order 永远返回 True → _place_stop 静默 skip_raise_stop_if_needed 只在 target>current 时撤单重挂,价格不符的僵尸单永不处理 → bot 看起来"卡死"(无日志,持仓无有效 STP。症状reqAllOpenOrdersAsync(跨 clientId可见这些单cancelOrder 报 Error 10147 取不掉。恢复:reqGlobalCancel() 清掉全部僵尸单后重启。防御08-03 修复):_raise_stop_if_needed 改为 auxPrice 与目标价不匹配(容差 <0.01)就撤单重挂,不只处理上移。另:has_open_order 只看 openTrades()(本 clientId 自己的单),reqOpenOrdersAsync 返回本 clientId 的订单,诊断全账户订单要用 reqAllOpenOrdersAsync
  • Cancelled STP 必须当轮重挂_sync_stop_orders 消费终态订单后若 owned 且未成交,同一周期_place_stop,不要拖到下轮(旧代码 continue 后依赖下一轮 by_ref 找不到已 consumed 的单才重挂,重启后首轮只挂 1 条就停。08-03 修复。
  • 正式目录是 ~/Desktop/DeepSeek/BotDeepSeekbot 自此从该目录启动(restart_bot.sh 自定位)。~/Desktop/kimi/ib-trading-bot-kimi 仅作备份保留,勿再从那里启动;两目录代码保持一致。

环境特殊性

  • .venv 无 pip/ensurepip;装包用 python3 -m pip install --target=.venv/lib/python3.12/site-packages <pkg>
  • pandas 3.0.3:避免使用已移除的 API如 groupby.apply 的 include_groupsVWAP 用 groupby+cumsum 向量化实现
  • 系统无 at,有一次性任务用 cron + 执行后自删crontab -l | grep -v <pattern> | crontab -),参考 close_legacy_positions.sh / stop_bot_eod.sh
  • 系统时区 EDT = 美股时间cron 按本地时间
  • IB Gateway 自带 JRE 17~/.local/share/i4j_jres/~/jdkTemurin 21是备用start_gateway.sh 引用它
  • Gateway APIclientId=1 给 bot临时只读检查用 clientId=77查完即断开

安全红线

  • 这是实盘账户:任何测试不得真实下单。验证用离线测试(纯函数:指标/状态/K线工具参考做法构造合成数据测 _calc_rsi/_calc_adx/_calc_vwapPositionTrackerto_completed_df
  • 不要为了"测试"运行 main.py——它会下真实订单
  • 修改策略逻辑后:先 py_compile,再跑离线测试,最后才考虑重启 bot

日常操作

./start_gateway.sh          # 启动 IB GatewayGUI需登录状态
./restart_bot.sh            # 重启 bot杀旧进程 + nohup 启动)
tail -f trading_bot.log     # 运行日志
cat bot_state.json          # 当前策略持仓归属与成本

一次性定时任务示例cronclose_legacy_positions.sh(平仓+自动启动botstop_bot_eod.sh15:59 收市前停机)。

当前状态2026-08-03 盘中)

  • 已恢复正常交易sell_only=False),标的 AMZN/AAPL/NVDA/NOK每标的持仓价值上限 $2300
  • 当前持仓08-03 盘中ShortTerm AMZN x7 @285.86、NVDA x9 @208.17、NOK x212 @9.3997MAStock AAPL x6 @304.08。四笔均有 GTC STP 在交易所clientId=1
  • 交易参数:每笔 ~$2000 折股、min_profit 1.5%、入场 2 根K线确认、止损 -2.5% STP2026-08-03 从 -2% 放宽,依据 256 笔成交统计61 笔亏损平仓中 82% 已控制在 -3% 内2% 执行良好;放宽仅为减少 whipsaw每笔风险 +$10 仍在 $150 日熔断内)、每日熔断 $150、全局持仓 12 批
  • 移动止损方案A08-03 实现):盈利 ≥ min_profit(1.5%) 后STP 从"入场价-2.5%"上移到"持仓期间最高收盘价-2.0%",且永不低于固定止损。use_trailing_stop/trailing_stop_pct 在 config 三股票策略里
  • 持仓不设上限08-05 撤销 5 天强制卖出)ShortTermConfig.max_hold_days=00=不限short_term.py 仅在 >0 时触发 max-hold 卖出。持仓由 EMA 死叉+min_profit、硬止损、移动止损自然退出可长期持有趋势单。
  • 全局卖出冷却08-04 实现方案Csell_cooldown_minutes=30(任何卖出后所有策略禁买)+ sell_improvement_pct=0.5%(冷却后120分钟内重买须比最近卖价低0.5%08-06 由60→120+ 登记在 recent_sells.json
  • 硬止损成交当日禁买08-06_mark_hard_stop_cooldown 使硬止损成交后该标的冷却到次日零时_next_day_start),防隔夜跳空止损后当天同价回补;软止损/订单失败冷却仍 30 分钟。
  • NOK 减半仓位08-06config.symbol_trade_value_usd={"NOK": 1000.0}base _trade_value_usd 供各策略 _order_quantity/_can_open_position 使用,其余标的一律默认 2000。
  • MAStock 入场过滤收紧08-06ADX 门槛 25→30adx_min),且新增 require_slow_ma_slope=True——仅当慢 MA 自身在确认窗口内上升才允许买入防下跌趋势中金叉接刀08-05 NOK 案例)。
  • 过夜敞口观察中2026-07-31 起)07-31 早盘 AAPL x5 隔夜跳空 -8.9% 止损成交STP @265.52→实际 304.145,亏损 $148≈止损预期的4倍。决策暂不改为 EOD 平仓,先积累 AMZN/AAPL 新 regime 样本2-4周统计跳空频率/实际过夜损耗,再决定是否加 EOD 强制平仓。