1. 先搞清楚问题:手动平仓后为什么还报孤立持仓
如果你正在用 Claude Code 写量化脚本,尤其是那种带套利对、带状态持久化的策略,你大概率会遇到一个很别扭的现象:交易所里明明已经手动全平了,脚本重启后日志还是刷出「检测到孤立持仓」。这不是交易所的问题,也不是你手速不够快,而是脚本的状态文件在作怪。
孤立持仓(orphan position)本身是个合理概念:套利对一般要两条腿同时开、同时平,如果程序崩溃、网络抖动、一边成交一边失败,就会剩下一条单腿仓位,这条腿不受套利逻辑保护,必须单独处理。问题出在「持久化」这一步——很多脚本把孤立持仓也写进了bot_state.json,下次启动时无脑state.get('orphan_positions', {})恢复回来,完全不问交易所现在到底还有没有这个仓位。于是你手动平仓的事实被本地缓存覆盖了,脚本活在自己的记忆里。
这篇就围绕ds_unified.py这个具体文件,讲清楚三件事:怎么用 Claude Code 的@文件能力把 3000 多行的脚本喂给模型做审查、孤立持仓的持久化该怎么改才不坑自己、以及重启后怎么验证状态真的对齐了。适合已经在写量化脚本、被状态管理折腾过的朋友,也适合刚接触 Claude Code 想看看@文件到底能干嘛的人。核心检索词就三个:Claude Code 的 @文件 引用、ds_unified.py 状态持久化、孤立持仓识别与恢复。
我先把结论摆前面:孤立持仓是临时状态,不该持久化;启动时必须以交易所实际持仓为准做一次校验;@文件引用要带行号范围,别整文件硬塞。下面按步骤拆。
2. 前置准备:TaoToken 接入与 Claude Code 环境
在动手改ds_unified.py之前,得先把 Claude Code 跑起来,并且让它能稳定读到你的项目文件。这里我用的是 TaoToken 的 Coding Plan 通道,原因是长上下文审查 3000 行脚本时,按量计费容易心跳,包月套餐更适合这种反复@文件来回问的场景。官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api ,两个地址别混。
第一步,拿 Key。进控制台创建 API Key,页面在 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,创建后立刻复制,只显示一次。这个 Key 后面要同时喂给 Claude Code 和你的量化脚本环境变量,别写死在代码里。
第二步,配置 Claude Code 的接入。Claude Code 走的是 Anthropic 兼容协议,需要设置ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN两个环境变量。Base URL 指向 TaoToken 的 API 地址,注意不要带 UTM 参数,那是给网页用的。配置片段如下,直接贴进你的 shell 配置文件(~/.zshrc或~/.bashrc):
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="sk-你的TaoToken密钥"保存后source ~/.zshrc生效。如果你用的是 Claude Code 的 settings 文件方式,也可以在项目根目录建.claude/settings.json,内容长这样:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的TaoToken密钥" } }这里有个坑要提前说:ANTHROPIC_BASE_URL末尾不要加/v1,Claude Code 自己会拼路径,加了会变成/v1/v1/messages直接 404。我第一次配的时候就栽在这,日志里报local proxy failed,排查了半天以为是网络问题。
第三步,确认模型 ID。Coding Plan 通道常用的模型 ID 是claude-sonnet-4-5这类,具体以你套餐页面显示的为准。三件套凑齐:Base URL、Key、Model ID,缺一个都跑不通。验证方式很简单,在项目目录下起一个会话:
claude进去后随便问一句「当前目录有哪些 py 文件」,能正常回你就说明通道通了。如果报 401,先检查 Key 有没有多余空格;如果报reading choices之类的解析错误,多半是 Base URL 拼错或者模型 ID 不存在。
环境通了之后,把ds_unified.py放到项目根目录,A_log/bot_state.json也确认存在(如果之前跑过)。接下来才是正题:用@文件让 Claude Code 读脚本。
3. 可复制配置:@文件 引用写法与持久化改造片段
3.1 @文件 的正确引用姿势
Claude Code 的@语法支持直接引用文件,输入@后会弹出文件补全。但 3045 行的脚本整文件塞进去,token 消耗大不说,模型注意力也容易散。我的做法是分两次引用:先整文件扫一遍结构,再针对具体方法带行号精读。
整文件引用就一行:
@ds_unified.py 请先梳理这个文件里所有涉及状态持久化的方法,列出方法名和行号范围,不要改代码。模型会返回类似_save_state在 434 行、_load_state在 460 行、get_unified_positions在 541 行附近这样的清单。拿到行号后,第二次引用就精准了:
@ds_unified.py:434-482 这段 _save_state 和 _load_state 里,orphan_positions 的存取逻辑有什么问题?手动平仓后重启为什么还会恢复出孤立持仓?带行号范围的引用能显著减少无关上下文。实测下来,同样的问题,整文件问要 3 万 token 上下,带范围问只要 4 千左右,回答质量反而更高,因为模型不用在 3000 行里自己找重点。
如果你要一次性审查多个相关方法,可以连续@多个范围:
@ds_unified.py:434-482 @ds_unified.py:541-560 对比这两段,状态恢复和交易所同步的先后顺序对不对?3.2 持久化改造:孤立持仓不落盘
问题根源在_save_state里把orphan_positions也写进了 JSON。改造思路是:孤立持仓是运行时临时状态,每次启动从交易所重新检测,不写盘。修改后的_save_state片段:
def _save_state(self): """保存策略状态到JSON文件(机构级持久化) 注意:孤立持仓(orphan_positions)是临时状态,不持久化。 每次启动时从交易所重新检测,避免保存过期数据。 """ try: state = { 'positions': self.positions, # 'orphan_positions': self.orphan_positions, # 不持久化 'trade_history': self.trade_history, 'trades_today': self.trades_today, 'total_pnl': self.total_pnl, 'last_update': datetime.now().isoformat(), 'last_update_timestamp': time.time() } with open(self.state_file, 'w', encoding='utf-8') as f: json.dump(state, f, indent=2, ensure_ascii=False) logging.debug(f"状态已保存: {len(self.positions)}个持仓, {self.trades_today}笔交易") except Exception as e: logging.error(f"保存状态失败: {e}")对应的_load_state也要改,恢复后立刻拿交易所实际持仓做一次校验,把已经不存在的孤立持仓清掉:
def _load_state(self): """从JSON文件恢复策略状态,并校验孤立持仓有效性""" if not os.path.exists(self.state_file): return with open(self.state_file, 'r', encoding='utf-8') as f: state = json.load(f) self.positions = state.get('positions', {}) self.trade_history = state.get('trade_history', []) self.trades_today = state.get('trades_today', 0) self.total_pnl = state.get('total_pnl', 0.0) # 孤立持仓不从文件恢复,启动时重新检测 self.orphan_positions = {} logging.info(f"状态已恢复: {len(self.positions)}个持仓")然后在启动流程里,同步完交易所持仓后调用一次孤立持仓检测,逻辑是「交易所有的仓位减去策略记录的仓位」,差集就是孤立持仓。这样手动平仓后重启,交易所返回空列表,差集为空,自然不会误报。
3.3 自动平仓开关
光检测不够,还得给个处理入口。加一个handle_orphan_positions(auto_close=False)方法,默认只告警,传True时自动平掉。这样既保留了人工确认的安全边界,又不用每次都手动去交易所点。配置上建议在config.json里加一个orphan_auto_close布尔项,默认false,跑稳定了再打开。
4. 验证请求与成功结果
改完代码,验证分三步走,每步都要看到明确的日志输出才算过。
第一步,清掉旧状态文件,模拟干净启动:
rm -f A_log/bot_state.json python ds_unified.py预期日志里应该出现「状态文件不存在,跳过恢复」或者「状态已恢复: 0个持仓」,并且不再有「恢复孤立持仓」的字样。
第二步,制造一个孤立持仓场景来验证检测逻辑。最省事的办法是手动在交易所开一条单腿仓位(比如只买不卖),然后重启脚本。预期日志:
检测到 1 个孤立持仓: BTC/USDT:USDT 处理建议: 设置 orphan_auto_close=true 自动平仓,或调用 close_all_orphans()第三步,验证持久化不落盘。在孤立持仓存在的情况下,让脚本正常跑一轮保存,然后cat A_log/bot_state.json,确认 JSON 里没有orphan_positions这个键:
cat A_log/bot_state.json | python -m json.tool | grep orphan没有任何输出就对了。再手动平掉那条单腿仓位,重启脚本,日志应该显示「孤立持仓检测: 0 个」,说明启动校验生效,过期数据没有复活。
如果你想让 Claude Code 帮你确认改动是否完整,可以这样问:
@ds_unified.py:434-500 我删掉了 orphan_positions 的持久化,并在 _load_state 里重置为空。请检查还有没有其他地方读写这个字段,列出所有引用位置。模型会把handle_orphan_positions、检测函数里的引用都列出来,你逐个确认是运行时赋值而非从文件读取即可。
5. 常见报错排查
5.1 401 Unauthorized
最常见。先看 Key 有没有复制全,TaoToken 的 Key 是sk-开头的一长串,中间不能有换行。再看ANTHROPIC_AUTH_TOKEN和ANTHROPIC_API_KEY是不是同时设了,两个都设时 Claude Code 可能取错。清理掉多余的:
unset ANTHROPIC_API_KEY echo $ANTHROPIC_AUTH_TOKEN确认输出是你的 Key 再重试。
5.2 local proxy failed
这个报错通常不是代理问题,而是 Base URL 拼错。检查ANTHROPIC_BASE_URL是不是https://taotoken.net/api,末尾有没有多余的/或/v1。另外确认你的 shell 里没有残留的HTTP_PROXY指向一个不存在的本地端口,有的话unset HTTP_PROXY HTTPS_PROXY再试。
5.3 reading choices 解析失败
模型返回结构不符合预期,多半是 Model ID 写错了。Coding Plan 通道要用套餐里标注的模型 ID,别自己拼。改完 settings 后重启 Claude Code 会话,旧会话不会热加载环境变量。
5.4 OAuth 相关报错
Claude Code 某些版本会尝试走 OAuth 登录流程,如果你用的是 API Key 方式,确保没有残留的登录态。删掉~/.claude/下的凭据缓存文件再启动,让它走环境变量认证。
5.5 孤立持仓反复出现
如果改完代码重启还是报,先确认你改的是正在运行的那份ds_unified.py,别是备份文件。再确认state_file路径指向的确实是A_log/bot_state.json,有些脚本会按日期分文件存。最后用@ds_unified.py让模型全局搜一遍orphan_positions,看有没有遗漏的读取点。
排查时把报错原文贴给 Claude Code,配合@文件定位,比自己在 3000 行里翻快得多:
@ds_unified.py 启动时报 "检测到孤立持仓" 但我已经在交易所全平了,请定位所有可能恢复 orphan_positions 的代码路径。6. 把状态管理跑通之后
量化脚本的状态管理,本质就一句话:本地文件是缓存,交易所才是真相。孤立持仓这种临时状态尤其不能落盘,一旦落盘就会和手动操作打架。@文件引用配合行号范围,是我目前审查这类长脚本最顺手的方式,比全文粘贴省 token,也比 grep 找得全。
如果你也想把这套流程固定下来,建议把 Claude Code 的接入配置和量化脚本放同一个项目里,用 Coding Plan 通道跑长期审查,地址在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite ,临时验证模型连通性用得上。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,环境变量和模型 ID 对不上时先翻这里。
最后留一个我踩过的坑:改完_save_state别忘了同步改_load_state,只改一半的话,保存时不写、加载时还get,虽然拿到空字典不会报错,但你会误以为改造生效了,实际检测逻辑根本没跑。两个方法一起改,再用第 4 节的grep orphan验证一遍,才算真正闭环。