资讯动态

用uiautomator2+OpenCV实现同花顺量化交易自动下单

发布时间:2026/9/1 7:36:37 来源:尧图企业网站定制
简介压缩包内提供的 THSTrader 量化交易工具基于 Python 开发面向希望用代码驱动同花顺手机版模拟炒股、降低手动操作成本的交易者与自动化脚本开发者。工具借助 uiautomator2 完成 Android 端点击、滑动等交互并辅以图色方法识别界面元素从而自动执行买卖流程后续可自行扩展接入实盘接口。资源共 9 个文件以 THSTrader.py 主程序、requirements.txt 依赖清单、README.md 使用说明为主另含模拟器分辨率图、实盘示意图片、许可证及 gitignore整体压缩包仅 1.33MB结构精简便于快速上手。目前已有 203 人学习适合具备一定 Python 基础、希望入门移动端量化交易自动化的开发者可借此掌握 UI 自动化与交易策略结合的基本思路并参考文件结构完成模拟环境到实盘迁移的二次开发。 量化交易这条路上最尴尬的往往不是策略写不出来而是策略都验证好了却找不到一个顺手的执行通道。同花顺手机版是很多人看盘和模拟炒股的首选但官方并没有给个人开发者开放交易API策略条件满足了也只能手动切进App点买卖盯盘盯到怀疑人生。今年我干脆动手做了个小工具用uiautomator2直接接管安卓手机上的同花顺配合OpenCV模板匹配、像素颜色识别这类“图色方法”兜底把看行情、选股、下单、确认弹窗这一整条链路自动化跑通了。它在同花顺的模拟炒股里运行得很稳定而且架构上把“策略判断”和“执行通道”拆开了后续要扩展实盘也不用推倒重来。1. 方案选型先想清楚再动手1.1 为什么绕开接口爬虫选了UI自动化当时我把可行路径都盘了一遍。第一条是官方API结果同花顺没有面向个人开放的交易接口直接出局。第二条是抓模拟炒股网页端的HTTP接口但同花顺登录流程里签名、动态加密、cookie校验叠了不少开发维护成本很高而且网页端和手机端功能并不完全对等。第三条就是安卓端UI自动化不碰任何非公开接口单纯替人手完成“看屏、点击、判断”的动作实现路径最直接也最快能验证策略。从“验证策略”这个目标出发UI自动化慢一点是可以接受的。一次点击怎么也得几百毫秒跟机构级别的毫秒级抢单完全不是一回事但模拟盘本来就是验证逻辑对不对不是拼速度。真到了实盘阶段我在第6章会说清楚为什么更推荐换券商官方API而不是继续用UI自动化去点真实资金。这套选择的本质是在受限条件下用能拿到的工具把流程跑通而不是追求教科书式的完美方案。1.2 uiautomator2相比Appium的优势选型时我给Appium投过票毕竟它名气大、生态全。但真用起来发现它太重启动server要占资源配置项又多对于只想对着一个固定App写脚本的场景来说属于大炮打蚊子。另一个思路是绕开框架直接写无障碍服务但权限设置麻烦事件注入的稳定性和文档友好度都不如现成框架。uiautomator2的优势是轻、快、免root一条pip命令装好再执行python -m uiautomator2 init就能给手机装上辅助服务之后脚本直接通过USB或局域网控制手机。它还自带watcher可以全局监听界面弹窗并自动点击“关闭”“我知道了”这类按钮这对交易类App尤其重要因为弹窗无处不在。这两点叠加选它就成了很自然的事。1.3 图色方法为什么必须补位没用过真机的人可能不理解uiautomator2不是能拿到控件树吗为什么还要做图像识别因为同花顺里一部分界面元素是自绘制的比如K线图区域、分时图上的数字标签、红涨绿跌的状态指示在dump_hierarchy导出的XML里根本看不到。还有一类是纯图片按钮控件属性缺失严重普通选择器定位不到。这时候“图色方法”就上场了用OpenCV模板匹配找目标图标的位置用像素颜色判断涨跌状态用OCR在极端情况下读数字。我把这套方案定为“控件优先、图色兜底”控件拿不到就降级到图像两条通道互补覆盖了绝大多数界面场景。这个双通道设计是整个工具的地基后面所有功能都建在这套机制上。2. 核心技术细节把原理讲清楚2.1 uiautomator2的基础操作uiautomator2连接手机后最常用的操作基本就是截屏、取控件树、点击、输入、滑动这几种import uiautomator2 as u2 # 连接设备USB直连或局域网连接都可以 d u2.connect_usb(设备序列号) # 也可以 u2.connect(192.168.1.100) # 导出当前界面的控件树用来分析可用元素 xml d.dump_hierarchy() # 点击带指定文本的元素 d(text买入).click() # 坐标点击用于图色匹配后给出落点 d.click(540, 1200) # 文本输入 d(text搜索).set_text(000001) # 滑动翻页 d.swipe(500, 1500, 500, 600, duration0.3) # 全局弹窗监控 d.watcher(关闭弹窗).when(text我知道了).click() d.watcher.start()选择器匹配有个关键原则能用resourceId就用resourceId其次是text最后才用坐标。因为坐标依赖分辨率而resourceId和text在不同版本里相对稳定。如果页面元素还没加载完就去点击大概率会失败所以每次关键点击前都要加等待比如d(text买入).wait(timeout5)或者直接time.sleep(1~2秒)。等待这件事看着简单实际是自动化稳定性的大头。2.2 OpenCV模板匹配的原理与实操模板匹配的核心思路是先截一张当前屏幕图再用一张预先准备好的小图模板在大图里滑动比对计算相似度得分最高且超过阈值的位置就是目标坐标。代码实现如下import cv2 import numpy as np def take_screenshot_cv(d): # uiautomator2截屏返回PIL图像转成OpenCV的BGR格式 img d.screenshot() return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) def find_template(screen, template, threshold0.75): # 在screen中查找template返回中心坐标或None res cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(res) if max_val threshold: h, w template.shape[:2] cx max_loc[0] w // 2 cy max_loc[1] h // 2 return cx, cy, max_val return None用TM_CCOEFF_NORMED这种归一化相关系数能抗一定程度的亮度变化。模板图最好是目标在界面上静止状态的截图避免带遮罩、阴影的变体。实操中最常遇到的坑是分辨率不一致同一张模板在1080P手机上能匹配换个2K屏手机就找不到了这时可以做一个多尺度匹配把模板缩放0.8到1.2倍后逐个试能解决一部分问题但更稳妥的做法是同一机型维护同一套模板。2.3 像素颜色判断涨跌判断股票涨跌常规做法是读控件文本里的涨跌幅数字但某些行情界面里数字是自定义视图绘制的拿不到节点。这时候可以利用中国市场红涨绿跌的习惯对指定区域做颜色统计def judge_up_down(screen, x, y, w, h): region screen[y:yh, x:xw] red_count 0 green_count 0 for row in region: for b, g, r in row: if r g 30 and r b 30: red_count 1 elif g r 20 and g b 20: green_count 1 if red_count green_count: return up if green_count red_count: return down return unknown逐像素循环在Python里偏慢实际用的时候可以把区域缩小或者直接用cv2.inRange做颜色过滤速度更快。这个识别方法不追求精确数字只用于状态判断比如“某股票当前是红还是绿”这类非黑即白的场景非常轻量。我把这类判断放在策略循环的高频路径里几乎不消耗性能。2.4 数字识别的两条路线当必须读出具体价格和涨跌幅且控件树拿不到文本时有两条路线。价格不复杂时直接用模板匹配数字0到9把价格区域裁剪下来与数字模板逐一比对识别简单数字串完全够用。需要识别更复杂的文字内容比如弹窗提示、通知类文字就得上OCR我用PaddleOCR的轻量模型速度慢一些但准确率靠谱适合低频辅助场景。路线选择依据是调用频率高频轮询价格时尽量靠控件树取数或数字模板匹配低频处理异常弹窗、读通知时才用OCR。把重量级识别放在低频分支里整条自动化链路的执行效率会高很多这也避免让手机CPU长期跑在高温状态。3. 环境搭建与设备准备照着做就行3.1 Python环境与依赖建议用Python 3.8以上版本装依赖就三行pip install uiautomator2 pip install opencv-python pillow # 如果要用OCR识别再装 pip install paddleocrpip下载慢就换国内镜像源。装完uiautomator2后再执行一次初始化给手机安装辅助服务python -m uiautomator2 init初始化时手机会提示安装ATX小助手按引导操作即可。这个过程只需要一次换手机后要重新对那台设备做init。顺便说一句建议把Python环境用虚拟环境管理避免后面装其他库时把系统环境搞乱。3.2 真机还是模拟器我建议先用模拟器把脚本逻辑跑通再换真机做最终验证。模拟器用过MuMu和雷电好处是可以在电脑上随时重置系统状态分辨率固定方便测试环境复现。坏处是部分App对模拟器有检测同花顺一般能用但偶尔会要求刷新登录态。真机的好处是环境真实状态布局和实际使用一致模板匹配出来的坐标更可信。模拟器分辨率建议固定为1080x2340这类主流值避免不同尺寸导致模板失效。真机务必开启开发者模式和USB调试连接后用adb devices确认设备能被识别。这两条基础设施不牢后面全是空中楼阁。3.3 连接的几种方式USB直连是最稳定的方式适合脚本调试阶段设备唯一标识通过adb devices查看。局域网连接用u2.connect(手机IP)手机和电脑连同一个WiFi即可这样调试完成后可以脱离USB线但需要保证App保持在前台、WiFi稳定、屏幕不休眠。屏幕不休眠这个点经常被忽略我一般会在系统设置里改成“永不”休眠或者脚本里定时用d.wakeup()和d.swipe()保持屏幕活跃否则跑到半夜屏幕一锁整个脚本就断线了。4. 核心功能实现从登录到下单全链路4.1 自动化登录流程登录流程很统一打开App等待登录页出现输入手机号输入验证码或密码点击登录。手机号和密码可以set_text自动填但短信验证码我不建议全自动识别第一是短信通道的稳定性不可控第二是自动读取短信在权限上容易出问题。我的做法是脚本把手机号填好验证码由人手输入脚本继续跑如果验证码未在时限内输入脚本报错停止避免空转。登录保护的关键是避免频繁重新登录。脚本尽量保持长连接不要主动退出登录更不要反复清App缓存。同花顺这类App对登录异常很敏感频繁重登轻则要求滑块验证重则限制登录一段时间。4.2 从自选股列表提取行情打开自选股页面后dump_hierarchy拿到XML再用XPath或正则把股票代码、名称、现价、涨跌幅从节点文本中捞出来。同花顺自选列表的每一行结构比较固定通常是一组TextView。实操时我写了一个简易解析器遍历所有文本节点按“代码-名称-价格-涨跌幅”顺序做行切分存成结构化字典列表。这样策略层拿到的就是干净的行情数据不用关心界面长什么样。字段提取这块的核心心得是不要试图一次性写一个万能的解析器因为App改版之后控件结构会变。正确的做法是先dump一份XML肉眼确认当前版本的节点结构再针对性写解析保留一份解析器配置方便App改版后快速修正。4.3 搜索并打开个股个股搜索是交易流程的前置动作def search_stock(d, code): # 点击顶部搜索入口 d(text搜索).click() time.sleep(1) # 输入股票代码 edit d(classNameandroid.widget.EditText) edit.set_text(code) time.sleep(0.5) # 点击搜索结果里的“进入”或第一项搜索结果 d(text进入).click(timeout3) time.sleep(1)有些版本没有“进入”按钮而是直接点击搜索结果第一行这需要用dump_hierarchy先确认当前App的实际控件结构。我的建议是做一个统一的“打开个股”函数把版本差异封装在里面策略层完全不用关心是哪个版本。4.4 买入与卖出流程买入是整条链路里风险最高的操作必须处理好确认弹窗。完整流程如下def buy(d, code, price, shares): search_stock(d, code) # 优先控件定位失败则用模板匹配 if not click_btn_by_text(d, 买入): click_btn_by_template(d, buy_btn.png) time.sleep(1) # 价格和数量输入框一般有两个EditText edits d(classNameandroid.widget.EditText) edits[0].set_text(str(price)) edits[1].set_text(str(shares)) time.sleep(0.5) # 点击下单/买入弹窗出现后再次确认 click_btn_by_text(d, 买入) time.sleep(1) click_btn_by_text(d, 确认) # 校验成交回执 if d(textContains委托).exists(timeout5): return True return False重点在于每一步都需要有等待和校验。尤其是第二次“买入”确认和“确认”按钮如果脚本没有等待就直接点下一步很容易因为页面未加载完而漏点。卖出流程与买入镜像只是按钮文本不同。我在这个函数里还加了截图留痕每次下单成功都会保存一张当时的屏幕截图方便事后复盘。4.5 一个能跑起来的双均线循环示例策略本身不是重点重点是写清楚“判断信号、执行下单、状态同步”三个循环要素。这里给一个双均线示范import time short_window 5 long_window 20 prices [] buy_flag False def get_current_price(d): # 读取当前价优先级控件文本 数字模板 OCR return read_price(d) def run_strategy(d, code): global buy_flag, prices prices.append(get_current_price(d)) if len(prices) long_window: short_ma sum(prices[-short_window:]) / short_window long_ma sum(prices[-long_window:]) / long_window if not buy_flag and short_ma long_ma: buy(d, code, prices[-1], 100) buy_flag True elif buy_flag and short_ma long_ma: sell(d, code, prices[-1], 100) buy_flag False while True: run_strategy(d, 000001) time.sleep(30)循环里最重要的不是均线计算而是每次进入循环先去持仓页核对一次真实持仓让buy_flag和实际仓位保持一致。脚本重启、App闪退、网络断开都会造成状态偏移不校验就去下单就会出大问题。这种“状态同步”思维在整个自动化项目里比任何单一技术点都重要。5. 常见问题与排查技巧坑都替你踩过了5.1 高频问题速查我整理了一版高频问题表格基本覆盖日常运行会遇到的大部分坑问题现象可能原因解决思路u2.connect一直连不上adb服务异常或设备驱动问题adb kill-server adb start-server重新连接dump_hierarchy拿不到任何节点当前界面是纯自绘或权限不足截图后用图色方案兜底同样脚本换个手机就点错分辨率不同或控件id变化用resourceId或文本定位并维护对应分辨率模板模板匹配到错误位置相似图标干扰或阈值过低提高阈值到0.8以上裁剪更精准的模板弹窗反复出现导致流程中断新弹窗没被watcher覆盖加watcher规则或统一异常处理模拟器刷新率导致截图花屏模拟器渲染问题调低模拟器帧率或换渲染模式5.2 风控与频率限制高频请求是最容易踩雷的。实测下来两次操作之间最好间隔2秒以上行情轮询间隔不要低于15秒否则会触发“操作频繁”类的风控提示。一旦被提示脚本应当立即暂停而不是继续点击暂停至少一小时比较稳妥。我的原则是操作越慢越安全模拟盘也没必要追求毫秒级。脚本里可以加一个自适应退避机制连续多次操作失败时自动降低频率。5.3 状态同步与失败重试三个原则我给自己定了三条铁律。第一下单前必须确认界面在当前页面避免把买入点成卖出第二连续失败三次以上立即停止并告警不要无限重试第三脚本每次启动都重新同步真实持仓不沿用上一次保存的仓位快照。这三条帮我避免了绝大多数“脚本失控”问题尤其是第三条经历过一次重启后脚本不知道自己持仓状态、一顿乱操作之后就再也忘不掉了。5.4 模板失效时的自我修复模板匹配最怕App改版后按钮样式变化。对此我的办法是脚本里保留一个“模板校准模式”人工介入时自动截下当前按钮的新图并保存为新模板文件不需要改代码。这个校准模式实现起来很简单就是在程序里加一个命令行参数进入校准流程后截屏、框选目标区域、保存为模板名称整个过程耗时不到一分钟。对个人长期维护来说成本很低收益很高。6. 从模拟盘到实盘我的个人看法这一节最能解释为什么我把整套架构做成“策略与执行分离”。如果确实想接实盘最正统的方式是去券商官方量化平台申请API而不是让UI自动化去点真实资金。UI自动化的延迟、丢单、异常弹窗处理在真实资金面前明显不够可靠还可能被判定为异常交易行为得不偿失。我自己衡量下来模拟盘验证策略逻辑已经足够实盘必须换更可靠的技术路线。所以我定义了一个下单接口层模拟盘实现走uiautomator2将来换正规券商API时只需替换接口实现策略层完全不用动。架构上预留这一步让这套工具的寿命长了很多。测试新策略时还能在模拟盘环境里把策略逻辑、风控判断先反复验证避免直接上真实接口去试错。实话说做完这个工具后我最大的收获不是“自动下单”本身而是被迫把策略逻辑和执行细节彻底分开。写在策略文件里的永远只是判断真正决定成败的是执行端那些琐碎状态弹窗有没有跳出来、网络有没有断、页面有没有卡住、仓位和标志位是不是一致。uiautomator2和图色方法都只是工具能跑通是第一步能让它在无人值守时连续一周稳定运行才是真正的难关。我个人的最后一条建议是先让它在模拟盘里跑够一个完整的交易周期把所有突发情况都修完再考虑任何实盘级别的延伸千万别一上来就动真金白银。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价