Qbot 雪球组合模拟交易详解:XueQiuTrader 如何把组合调仓封装成标准券商交易 API
Qbot 雪球组合模拟交易详解XueQiuTrader 如何把组合调仓封装成标准券商交易 API【免费下载链接】Qbot[updating ...] AI 自动量化交易机器人(完全本地部署) AI-powered Quantitative Investment Research Platform. online docs: https://ufund-me.github.io/Qbot ✨ :news: qbot-mini: https://github.com/Charmve/iQuant项目地址: https://gitcode.com/GitHub_Trending/qbot/Qbot在量化交易落地环节除了连接同花顺、华泰等券商客户端实盘下单还需要一种安全、零风险的纸面交易通道来验证策略。Qbot 仓库内置的 easytrader 引擎提供了xq雪球交易端它把雪球组合的比例调仓接口封装成与其他券商完全一致的buy/sell/get_balance/get_position标准接口让策略代码在模拟环境和实盘之间可以零改动切换。读完本篇你将掌握雪球模拟交易的初始化与参数配置、净值到资金的换算机制、按 1 手拆单的委托换算规则以及adjust_weight比例调仓的完整实现细节与错误处理边界。为什么雪球组合适合作为模拟交易通道雪球组合的本质是按比例调仓而非按股数下单这与券商实盘接口按价格股数委托存在天然差异。easytrader 的XueQiuTrader正是解决这一差异的适配层其核心设计目标见 xueqiu.md接口基本与其他券商接口调用参数、返回结构一致策略端无需感知底层是真实券商还是雪球组合由于雪球调仓在开盘时间相当于直接市价成交委托单不支持挂高挂低初始资金按组合净值1:1000000换算即组合净值 1 元对应账户 100 万元虚拟资金委托单的委托价格、委托数量换算回来都按1 手100 股拆分因为雪球本身只记录持仓比例持仓中持股市值是准确的但持仓价格和持仓数量是按 1 手折算的近似值不合理的操作资金不足、卖出未持有股票、操作停牌股等会直接抛出TradeError具体错误信息定义在 exceptions.py其中TradeError继承自IOError。这种比例 → 金额 → 1 手委托的换算链路是整个模拟交易机制的核心。快速开始创建雪球交易端入口是 api.py 中的工厂函数use()传入xq或中文雪球即可创建XueQiuTrader实例import easytrader # 默认初始资金一百万净值 1:1000000 换算 user easytrader.use(xq) # 也可以指定初始资金 user easytrader.use(xq, initial_assets2000000)创建之后通过prepare()完成登录。雪球登录不使用用户名密码而是cookies 组合代码三要素仓库根部的 xueqiu.json 给出了账号配置模板{ cookies: 雪球 cookies登陆后获取, portfolio_code: 组合代码(例:ZH818559), portfolio_market: 交易市场(例:us 或者 cn 或者 hk) }# 文件模式登录 user easytrader.use(xq) user.prepare(xueqiu.json) # 或者参数模式登录portfolio_market 缺省为 cn user easytrader.use(xq) user.prepare(cookiesxxx, portfolio_codeZH818559, portfolio_marketcn)从源码看两条路径的收敛点在 webtrader.py 的prepare()传config_file时走read_config()读 JSON否则调用子类的_prepare_account()校验参数。而 xqtrader.py 中重写的autologin()不做真正的登录请求只是把 cookies 解析后写入requests.Session因此 cookies 必须在创建组合账户时保持有效_prepare_account()对缺失参数会分别抛出明确的TypeError缺portfolio_code或缺cookies。initial_assets净值到资金的换算机制initial_assets是雪球端唯一的资金参数其解析逻辑在XueQiuTrader.__init__中# 资金换算倍数 self.multiple ( kwargs[initial_assets] if initial_assets in kwargs else 1000000 ) if not isinstance(self.multiple, numbers.Number): raise TypeError(initial assets must be number(int, float)) if self.multiple 1e3: raise ValueError( 雪球初始资产不能小于1000元当前预设值 {}.format(self.multiple) )三个要点默认值为 1000000即净值 1 对应 100 万虚拟资金与文档净值 1:1000000 换算一致必须是数字类型否则抛TypeError下限 1000 元过低会使 1 手100 股的拆单粒度失真故直接抛ValueError。换算方向只有一个虚拟净值 → 资金实现在_virtual_to_balance()中就是净值 * multiple。反过来下单时把金额换算回比例见下文_trade()的weight volume / asset_balance * 100。查询类接口balance / position / entrustXueQiuTrader继承WebTrader基类重写了全部查询方法数据源是组合页面的SNB.cubeInfo全局变量_get_portfolio_info()用正则(?SNB.cubeInfo ).*(?;\n)从 HTML 中提取 JSON和调仓历史接口。对应的接口 URL 全部集中在 config/xueqiu.json配置键用途portfolio_url组合详情页前缀拼接组合代码后抓取 cubeInfosearch_stock_url按代码搜索股票信息search.jsonrebalance_url提交调仓cubes/rebalancing/create.jsonhistory_url调仓历史cubes/rebalancing/history.jsonreferer调仓请求的 Referer 模板get_balance资金状况从 cubeInfo 中取net_value净值和view_rebalancing仓位结构返回结构与券商接口对齐的列表asset_balance self._virtual_to_balance(float(portfolio_info[net_value])) # 总资产 cash asset_balance * float(position[cash]) / 100 # 现金按仓位中的现金比例折算 market asset_balance - cash # 市值返回字段包括asset_balance、current_balance可用现金、enable_balance、market_value、money_type: 人民币等其中current_balance与enable_balance相同——模拟账户不存在冻结资金。get_position持仓1 手拆分规则文档中提到持仓价格、数量按 1 手拆市值是对的源码印证了这一近似逻辑for pos in xq_positions: volume pos[weight] * balance[asset_balance] / 100 # 该持仓市值 权重% × 总资产 position_list.append({ cost_price: volume / 100, # 用1 手的价格近似持仓价格 current_amount: 100, # 固定 100 股 enable_amount: 100, market_value: volume, # 市值是准确的 stock_code: pos[stock_symbol], stock_name: pos[stock_name], ... })即把每只持仓视作1 手股票其单价取市值/100因此单只持仓的current_amount恒为 100。这在消费端只需要市值/市值占比时完全够用但如果策略依赖真实股数如 T1 可卖数量判断需要注意该近似。get_entrust委托单最近 20 次调仓雪球没有当日委托概念get_entrust()用调仓历史模拟取history_url返回的最近 20 次调仓将状态映射为券商术语——pending→ 已报canceled/failed→ 废单其余 → 已成买卖方向由target_weight与prev_weight的大小关系判定加仓为买入否则为卖出。每条委托的business_amount/entrust_amount固定为 1001 手价格取调仓时的price。cancel_entrust伪撤单对pending状态的调仓cancel_entrust(entrust_no)通过发起一笔反向调仓实现伪撤单计算目标权重与当前权重的差额市值调用_trade()反向操作若发现该记录已被移除权重均为 0直接抛出TradeError(移除的股票操作无法撤销,建议重新买入)找不到对应委托则抛TradeError(撤销对象已失效)。这是典型的用业务规则模拟不支持的原生操作使用时必须留意错误信息。adjust_weight雪球特有的比例调仓20160909 新增的adjust_weight是雪球端独有函数直接面向比例调仓语义而非价格股数user.adjust_weight(stock_code600519, weight15.5) # 把贵州茅台调仓到 15.5% user.adjust_weight(stock_code600519, weight0) # 清仓该股票两个参数stock_code指定调仓股票代码weight指定调整后的目标持仓比例0–100 之间的浮点数保留两位小数。实现流程xqtrader.py 第 314 行起股票校验调search_stock_url搜索股票搜不到抛TradeError(没有查询要操作的股票信息)flag ! 1未上市 0 / 停牌 2 / 涨跌停 3 / 退市 4抛TradeError(未上市、停牌、涨跌停、退市的股票无法操作。)修改持仓列表若该股票已在组合中更新其weight并标记proactive: True若是新股票且weight ! 0按搜索接口返回的完整字段code、stock_id、ind_name、url等追加进持仓列表计算现金比例cash round(100 - sum(所有持仓权重), 2)即现金补足剩余仓位提交调仓POST 到rebalance_url参数为cash、holdingsJSON 序列化的持仓列表、cube_symbol组合代码、segment、comment。返回值的三态值得注意网络异常时记日志并返回None服务端返回error_description时返回[{error_no: ..., error_info: ...}]成功时返回None并在日志中输出调仓成功。这与buy/sell成功返回委托列表的约定不同调用方需要自行判空/判错。buy / sell金额到权重的换算链路XueQiuTrader的buy(security, price, amount, volume)/sell(...)签名与券商端保持一致内部统一走_trade()换算链路是资金校验volume缺省时按price * amount计算交易金额买入时若volume current_balance可用现金抛TradeError(没有足够的现金进行操作)状态与金额校验股票flag ! 1抛错volume 0抛TradeError(操作金额不能为零)金额 → 权重weight round(volume / asset_balance * 100, 2)把本次交易金额折算为占总资产的比例更新持仓买入时目标权重 新权重 原权重卖出时若weight 原权重抛TradeError(操作数量大于实际可卖出数量)未持有股票卖出抛TradeError(没有持有要卖出的股票)现金比例重算按买入/卖出后现金余额占总资产的比例计算cash字段提交并组装虚拟委托POST 成功后返回一条雪球虚拟委托记录entrust_no取服务端返回的调仓identrust_status为-无真实撮合状态买卖方向字段固定回填参数值。这条链路解释了文档中委托价格和委托数量换算回来都是按 1 手拆的这一限制雪球只关心权重变化价格与股数只是提交前的换算中间量回填到委托列表时统一按 1 手表示。心跳保活与登录态维护XueQiuTrader继承了WebTrader的后台心跳线程机制autologin()成功后启动send_heartbeat守护线程循环调用check_login()默认每 30 秒一次通过heartbeat()雪球端即拉取一次balance维持 cookies 有效捕获到RequestException时会重置日志级别、记录账户出现错误尝试重新登陆并再次执行autologin()——对雪球而言就是重新写入一次 cookies。退出时调用exit()将heart_active置为False停止心跳。这意味着长时间挂着的模拟交易进程会自动续命登录态但前提是 cookies 本身未过期。在 Qbot 中的两种使用形态结合 usage.md雪球端在 easytrader 生态中有两类典型用法1. 本地模拟交易策略直连雪球组合import easytrader user easytrader.use(xq, initial_assets1000000) user.prepare(xueqiu.json) print(user.balance) print(user.position) user.buy(000001, price10, amount100) # 参数与券商端一致 user.adjust_weight(000001, weight10) # 雪球特有比例调仓2. 远端交易服务量化平台信号驱动雪球组合remoteclient.use()支持xq作为客户端类型让运行在量化平台JoinQuant / RiceQuant 等上的策略信号经交易服务端转发到雪球组合执行from easytrader import remoteclient user remoteclient.use(xq, host服务器ip, port1430) user.buy(...) user.sell(...)同时 usage 文档还描述了follower反向跟踪模式用easytrader.use(xq)创建 trader 后配合easytrader.follower(...)跟踪聚宽/米筐的模拟交易将成交信号落到雪球组合上——雪球在此充当了跟单执行端。使用限制与排错清单限制/现象根因源码位置委托单无法挂高挂低雪球调仓开盘即市价成交_trade()只提交权重无价格委托语义持仓价格/数量不真实get_position()固定按 1 手100 股拆单仅market_value准确initial_assets报错非数字抛TypeError小于 1000 抛ValueErrorTradeError: 没有足够的现金进行操作买入金额超过current_balanceTradeError: 操作数量大于实际可卖出数量卖出折算权重超过原持仓权重TradeError: 未上市、停牌、涨跌停、退市的股票无法操作搜索接口flag ! 1adjust_weight返回None成功或网络异常查日志区分调仓返回[{error_no, error_info}]服务端拒绝读error_info排查NotLoginError心跳检测登录态失效且 cookies 重注入仍失败相关源码索引文件说明docs/other/xueqiu.md雪球组合模拟交易说明文档本文核心easytrader/xqtrader.pyXueQiuTrader完整实现查询、买卖、adjust_weight、伪撤单easytrader/api.pyuse()/follower()工厂函数xq入口与initial_assets参数easytrader/webtrader.pyWebTrader基类prepare、autologin、心跳保活easytrader/config/xueqiu.json雪球接口 URL 配置xueqiu.json账号配置模板cookies / portfolio_code / portfolio_marketeasytrader/exceptions.pyTradeError/NotLoginError定义docs/usage.md本地与远端remoteclient两种使用形态示例需要提醒的是雪球端依赖 cookies 登录态与组合页面/接口的 HTML 结构如SNB.cubeInfo变量当雪球前端改版时_get_portfolio_info()的正则提取可能失效维护时应以 xqtrader.py 中的解析逻辑为基准做适配。【免费下载链接】Qbot[updating ...] AI 自动量化交易机器人(完全本地部署) AI-powered Quantitative Investment Research Platform. online docs: https://ufund-me.github.io/Qbot ✨ :news: qbot-mini: https://github.com/Charmve/iQuant项目地址: https://gitcode.com/GitHub_Trending/qbot/Qbot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考