做量化的人应该都有过这种体验:策略在聚宽上回测曲线漂亮得很,年化、回撤、胜率怎么看怎么顺眼,可真到了准备实盘那一关,整个人就卡住了——券商那边要你开通QMT,一问资金门槛,心凉半截;再一看终端环境配置,又是一堆破事。我当初就为这事纠结了很久,后来干脆换了一条路:不碰QMT,照样用聚宽跑策略,小资金自动下单。这套方案我跑了快一年,稳定性和省心程度都超出预期,今天把它完整拆开讲。
先说清楚这套方案适合谁:主要面向已经在聚宽上写好策略、资金量在几十万以内、不想去凑券商资产门槛的散户朋友。你不一定需要多高深的编程能力,但至少能用Python写简单脚本。整条链路的核心思路就一句话:聚宽只负责算信号,本地跑一个执行器负责下单,二者之间用最普通的HTTP请求或者消息推送对接,绕开券商对QMT的种种限制。
1. 先想清楚:为什么非要从QMT这条路绕开
1.1 QMT不是不好,是门槛实在不友好
QMT的全称是迅投极速策略交易系统,功能确实强,支持Python策略、实盘交易、Level-2行情这些硬需求,很多机构和个人大户都在用。但对小资金用户来说,它最大的问题就是开通条件。
我接触过几家券商的客户经理,口径大同小异:有的要求账户资产连续20个交易日不低于50万,有的要求30万外加一定的交易经验,有的虽然资金门槛能谈,但会要求你不定期保持一定的交易频率。你算一笔账就明白,假如你账户里只有5万块,连门槛的零头都不够,这条路基本走不通。
还有些券商的QMT通道要单独申请、单独审核,审核周期短则两三天长则一两周。等你把流程跑完,策略也许早就不想做了。这种挫败感,经历过的人都懂——明明技术方案已经闭环了,最后卡在开户审核和资金门槛上,太不值。
1.2 QMT本身也有让人头疼的毛病
就算资金量达标,QMT也未必省心。它不是一个开箱即用的东西,对运行环境有要求:需要Windows系统、需要安装特定版本的Python环境、需要处理一堆依赖库的兼容问题。网上搜QMT相关内容,十个帖子里面有八个是在问“环境依赖怎么装”“客户端client is null怎么办”“终端连不上”。
这里面的坑我帮你们踩过一部分:QMT带了Python但版本往往偏老,装第三方库容易遇到编译问题;客户端要登录、要更新数据,有些券商的版本还时不时抽风,登录态失效、连接断掉,你根本不敢把自动交易的生死完全交给这样一套环境。
这些现实问题叠加起来,让我下决心:小资金就没必要硬往QMT上挤,换一条轻量路径可能更舒服。
2. 整体信号链路:聚宽只做大脑,本地做手脚
2.1 三层架构:信号生成、传输、执行
不依赖QMT的自动化交易,本质上就是一个更直白的三层结构:
- 第一层:聚宽平台。你的策略在这里运行,负责计算每日目标持仓、输出交易信号。它不需要直接下单,所以不涉及任何券商接口。
- 第二层:信号传输通道。聚宽把信号推送到本地,最常见的方式是HTTP回调、消息推送机器人或者邮件轮询。
- 第三层:本地执行器。一台常年开机的电脑或者云服务器,定时接收信号,和当前真实持仓对比,计算出要买什么、卖什么、各多少股,然后调起下单接口。
整个架构里,最复杂的是第三层,但它的复杂度和QMT那套相比已经小太多了:你要面对的题目不是庞大厚重的交易终端,而是你自己完全可控的几百行Python代码。
2.2 聚宽侧信号推送的具体做法
聚宽这边,最简单的做法是利用它的模拟交易功能或者定时运行的研究任务,把策略的目标持仓通过一个HTTP请求推到本地。
比如你的策略持仓里有三只股票,每天收盘后模拟盘会刷新持仓。这时候在聚宽的代码里加一段推送逻辑,把目标持仓、日期、信号ID这些信息打包成JSON,发到本地服务端口。
# 聚宽模拟盘/定时任务中示意代码,具体接口以聚宽官方文档为准 import requests import json def after_trading_end(context): targets = get_target_position(context) signal = { "signal_id": str(context.current_dt.date()), "date": str(context.current_dt.date()), "targets": targets } try: requests.post( "http://你的本地地址:8000/signal", json=signal, timeout=10 ) except Exception as e: log.warn("信号推送失败: %s", e)推送通道具体用什么,我建议按稳定性和开发成本排序:
| 推送方式 | 稳定性 | 实现成本 | 说明 |
|---|---|---|---|
| HTTP回调到本地/云服务器 | 高 | 低 | 需要本地暴露端口,或使用云服务器中转 |
| 企业微信/钉钉群机器人 | 高 | 极低 | 免费、有重试机制,本地轮询机器人消息即可 |
| 邮件轮询 | 中 | 极低 | 有延迟,但最简单,适合盘后低频策略 |
| 聚宽本身的邮件/微信通知 | 中 | 最低 | 只适合人工提醒,不适合自动触发下单 |
我个人实际跑下来最喜欢的是HTTP回调,因为它能让本地执行器立刻感知新信号,还方便调试。如果你不想暴露家里IP,也可以在一台便宜的云服务器上部署一个只有几十行的消息中转服务,聚宽推给云服务器,本地再从云服务器拉取,中间不会暴露内网端口,更安全。
3. 本地执行器的核心代码:从目标信号到真实订单
3.1 主循环与状态处理流程
本地执行器是整个方案里唯一真正碰钱的程序,所以它的设计核心不是“能下单”,而是“不瞎下单、不乱重复下单”。我的主循环写得很朴素,但每一步都在防问题:
import time import requests import sqlite3 def main_loop(): while True: signal = fetch_latest_signal() if signal and not is_duplicate(signal["signal_id"]): targets = signal["targets"] positions = get_current_positions() orders = build_orders(targets, positions) for order in orders: submit_order(order) mark_processed(signal["signal_id"]) time.sleep(60)这段代码看着简单,实际背后有几个值得注意的设计点。
第一点是signal_id。这个字段必须能唯一标识一条信号,我用的是日期加策略编号的组合。收到信号后先查数据库,如果这个信号ID已经处理过,直接忽略——这是防止重复下单最重要的一道防线。
第二点是build_orders。它把“目标持仓”变成“具体交易动作”。比如你的目标持仓是“某某股票占比20%”,就需要结合当前真实持仓和账户可用资金,算出来到底要买多少股、卖多少股。这个函数不能出错,否则要么资金不够,要么买成超仓。
第三点是所有委托记录都落库。我用的是SQLite,轻量还不用装服务。每次收到信号、每次下单、每次委托失败,都写一条日志,出问题的时候回查非常方便。
3.2 下单通道怎么选
不通过QMT,下单通道主要有这么几个选择:
- 券商自带的普通交易客户端配套自动化库,比如通过pywinauto、pyautogui模拟界面操作,稳健性一般但胜在门槛低。
- 开源项目如easytrader,通过对同花顺、券商客户端做底层操作实现自动交易,社区用的人多。
- 部分券商提供的简版API,直接HTTP请求下单,但需要你逐一确认自己开户的券商是否有这类通道。
你可能会问我用哪种。我的建议是:先用模拟交易或自己写一个模拟盘接口验证整个执行器逻辑,再考虑接真实券商通道。真实通道不管选哪种,都存在一个共性风险——券商客户端升级可能导致脚本失效,所以放量实盘前一定要留出观察期。
3.3 仓位和股数计算逻辑
计算下单数量是整个执行器里最容易出错的地方,因为股票交易有每手100股的约束,而目标持仓比例往往是百分比,不是一个整数。每次都要算清楚:
def calc_order_quantity(share, target_value, price, cash_available): desired_quantity = int(target_value / price / 100) * 100 current_quantity = share if share else 0 diff = desired_quantity - current_quantity if diff > 0: need_cash = diff * price if need_cash > cash_available: # 资金不足时,只买能买得起的整手数 diff = int(cash_available / price / 100) * 100 return diff这里有几个细节不能漏:A股买入必须整手,卖出时可以卖出零股但最好也整手处理;计算买入量的价格要用现价估,实际成交价可能更高,所以最好留出2%到3%的资金缓冲,否则容易下单后可用资金不足被券商拒单。我刚开始跑的时候吃过这个亏:按市价刚好够买100股,结果下一瞬间价格跳了一分钱,委托就失败了。
4. 聚宽与本地的一致性处理:成败都在细节
4.1 历史数据与复权方式必须对齐
如果聚宽模拟盘负责生成信号,本地只做执行,那么信号本身的可信度就锚定在聚宽的数据上。聚宽默认用的前复权价格、真实价格,和你本地下单所看到的行情价格可能不完全一致。这并不是平台的问题,而是复权因子、数据供应商的差异造成的。
这个问题在日频策略上影响相对有限,但如果你做的是突破类、价格敏感类策略,就要额外小心。我的建议是:让聚宽计算信号时优先使用“不复权”价格,或者模型里避开对绝对价位的依赖,否则你很容易发现策略在模拟盘信号正常,到了实盘却因为价位对不上而错过买入点。
4.2 执行时点的选择
很多聚宽策略是收盘后或者第二天开盘前才刷新信号。如果你等信号推送完再开始下单,往往已经过了最佳时间窗口。这里有个经典矛盾:信号越早拿到,执行越接近预期,但前提是你的策略不依赖盘中实时数据。
我目前的方案分两种策略处理:
- 日频策略:当天收盘后,用当天的日线数据和收盘指标计算次日目标持仓。盘后信号推送完毕,本地在第二天集合竞价阶段或开盘前几分钟按计划下单。
- 分钟级策略:不建议完全走这套轻量链路,因为延迟太大。如果你确实做分钟级,至少要保证本地服务器位于离券商通道近的机房,并且信号推送链路使用云服务器中转,否则一个轮询周期就是几十秒,策略早就失效了。
4.3 模拟盘成交与实盘成交的偏差容错
聚宽模拟盘成交假设比较理想,比如按收盘价、开盘价成交,不考虑滑点。但真实实盘里,流动性不足的股票、大盘开盘瞬间的波动,都会让成交价和信号计算价差出不少。小资金尤其明显:你总共买个几千块,滑点可能吃掉小半个点。
我的做法是在执行器里设置一个成交价容忍度:如果预计成交价和信号价格偏离超过1.5%,暂时不追单,记录到告警日志,等下一次信号刷新再处理。宁可错过一次交易,也不要在情绪化价格下成交。这个参数我后来调宽到2%,因为A股振幅本身不小,卡太死会导致很多委托白白废掉。
5. 小资金实盘最常踩的五个坑
5.1 最小交易单位与资金碎片的矛盾
这是小资金实盘最现实的问题。比如账户里总资产5万,一个信号告诉你“买20%仓位”,算出来是一万块,按理能买几百股某只价格20多的股票,但当你持仓里有十只股票时,每一只都可能因为整手限制剩下几十股的零头,这些零头既卖不掉又不值钱,逐渐拖累资金利用率。
解决办法有两个:一是坚持“目标持仓数量不要贪多”,小资金控制在3到5只以内;二是在执行器里加资金碎片整理逻辑,定期把持仓市值不足一手的股票一次性清掉,把资金集中到核心标的上。
5.2 停牌和涨跌停的委托失败处理
策略信号是静态的,但市场是动态的。目标持仓里有股票停牌,或者正好一字涨停买不进、一字跌停卖不出,这种情况非常常见。执行器如果不知道停牌状态,就会反复提交委托下单,然后反复被拒。
我现在维护了一份“可交易状态表”,每日开盘前用行情接口拉取所有持仓和候选股票的停牌状态,停牌日在执行器里直接跳过;对于涨跌停,设定“只尝试失败一次即放弃”的规则,不做无意义重试。这样虽然可能错过某些回归的机会,但至少程序不会陷入死循环里浪费资源。
5.3 重复执行、重试风暴与幂等防护
自动化程序最怕一个场景:行情接口临时超时,信号拉取请求被打回,然后本地程序误以为没收到信号,下一次循环又重新下单一遍。这种重复下单在半自动场景下还不致命,但在全自动模式下就可能造成超额买入、严重超仓。
幂等设计我前面提过一次,这里再强调一层:所有涉及钱的接口调用都必须有唯一请求号,并且执行器要能够通过查询状态决定自己是否已经处理过该信号。我在SQLite里建了一张signal_record表,记录每个signal_id对应的订单编号,哪怕程序重启、网络抖动,都不会重复执行。
5.4 环境故障:死机、断网、掉线
你可能觉得程序写对了就万事大吉,但实际上跑自动化交易最不停歇的敌人是环境问题。我有一次出差,云服务器上的执行器因为内存不足崩了,第二天早上才发现它已经停了好几个小时,那天的信号完全没执行。
后来我做了三层保障:第一,用systemd或者supervisor管理执行器进程,崩了自动拉起;第二,加一个看门狗脚本,每隔20分钟检查一次最新信号的处理时间,如果超时没有新信号或没有新委托记录,就给自己推送一条告警;第三,凡是涉及跨天运行的任务,每天凌晨定时重启一次进程,清掉无效连接。这些保障看起来笨,但真的能救命。
5.5 费用与滑点对小资金的实际影响
做回测时很多人不会太在意手续费和滑点,但小资金实盘会被这些成本咬得很疼。以万2.5佣金加卖出万10印花税来算,一个来回大约千分之1.5到2的成本,加上买卖各几毛钱的滑点,你的策略年化如果只有10%,扣完成本可能只剩六七个点,再遇上几次失败委托、追价单,收益会更薄。
我的建议是:实盘前先在回测里强行加上双边千分之2的成本模型,看策略还能不能活。如果一个策略在这么苛刻的成本假设下依然能赚钱,才值得拿出来跑实盘。
6. 我的分阶段落地节奏与维护清单
6.1 第一阶段:聚宽模拟盘加本地影子执行
我推荐你第一周不要碰真钱。先把聚宽模拟盘信号推送写好,本地执行器全部接一个“模拟交易接口”,也就是只在SQLite里记录“假如我下单了,会买什么、买多少、成本多少”,不实际发送任何委托。这一阶段主要验证信号链路通不通、执行器算得对不对、日志全不全。
你可以人为制造一些极端情况来测试:比如把信号故意设成重复推送两次,观察执行器是否只处理一次;比如故意断网一次,重启后再看数据库里的状态是否正确。所有反馈正常,再进入第二阶段。
6.2 第二阶段:最小资金试跑与滑点统计
第二阶段我放了一万块钱进去,只执行一到两只股票的策略。这时候你要特别关注三组数据:理想成交价与实际成交价的差、实际执行耗时、以及执行器在开盘高峰期的稳定性。这一阶段不再写新功能,只看它的表现。持续运行两三周,把每天的滑点记录下来,如果平均滑点超过预期,就要回头检查下单时点或者下单通道的延迟。
当初我跑出来的数据给自己提了个醒:开盘前几分钟委托会被券商排得很久,原本预期开盘价附近成交,实际滑点常常到了1%以上。后来我调整成开盘15分钟后才开始下单,虽然信号时效性略降,但执行质量明显提升。
6.3 第三阶段:放量后的日常维护清单
第二阶段跑顺之后,你就可以根据自己实际情况逐步放大资金量。不过放量不代表放任不管,我给自己列了个简单的维护清单:
- 每天收盘后看一眼信号推送是否成功、委托记录是否完整。
- 每周做一次SQLite备份,日志定期清理,防止磁盘占满。
- 每月对比一次聚宽模拟盘净值与真实账户净值的偏差,如果偏差持续放大,说明执行环节出了问题。
- 每次券商客户端升级后,先跑一天模拟交易,再恢复实盘。
这套方案跑下来,我的感受是它不一定适合所有人,但对“资金量小、策略逻辑不复杂、想要自动化却不想被QMT绑死”的人而言,它提供了另外一条完全可行的路。你不需要为了开通一个工具去凑资金门槛,也不用忍受复杂客户端的各种环境问题,只要把聚宽和本地执行器之间这条链路做稳了,小资金一样可以做到自动化交易。
最后再分享一个小技巧:本地执行器除了处理信号之外,我还会把它接到一个简单的Web页面上,每天显示“当前目标持仓、实际持仓、最近一轮交易记录”。哪怕你在外面用手机看一眼,心里也踏实得多。自动化交易最重要的不是每天紧张盯盘,而是当它出问题的时候,你能第一时间知道。