十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python实现LOF基金溢价套利自动化监控与微信预警

Python实现LOF基金溢价套利自动化监控与微信预警 搞基金套利的人最怕的其实不是行情不涨而是机会摆在眼前你根本没看见。LOF基金溢价套利这个事窗口期往往就几分钟等人肉刷新页面看到溢价价格早就被资金抹平了。我用Python做了一套自动化监控脚本开盘期间自动轮询几十只LOF的场内价格和盘中估算净值实时计算溢价率一旦超过阈值就通过微信推消息给我。全天不用盯盘手机接收预警看到消息再去券商App确认下单比我以前手动刷盘要靠谱太多。这篇文章不卖源码也不讲那种“一键躺赚”的夸张故事我只把整套方案的原理、数据结构、关键代码逻辑以及实际运行中踩过的坑讲清楚。无论你是已经会Python想要做量化监控的人还是第一次听说LOF这个词但对自动化监控感兴趣都可以把这篇文章当作一份可直接复现的参考手册。1. 溢价套利的本质为什么盯盘盯不住机会1.1 LOF基金为什么会出现溢价LOF基金中文全称是上市型开放式基金。它有两个交易渠道一个是通过基金公司或代销平台按净值申购和赎回另一个是在交易所内像股票一样按实时价格买卖。这两个渠道并存理论上价格应该无限接近净值但实际因为场内买盘的强弱、市场情绪、资金进出速度不一样二级市场的交易价格经常和基金净值出现偏差。当场内交易价格高于基金净值的时候就叫溢价。反过来场内价格低于净值就叫折价。对套利者来说溢价和折价都是机会。溢价出现时可以在场外按净值申购LOF份额到账后转入场内卖出赚取价格和净值的差价折价出现时则在场内买入份额再转场外赎回。思路不复杂难点在于价格波动太快净值又是实时估算的靠人工盯住几十个品种几乎不可能。1.2 人工盯盘的痛点和自动化的价值我最早尝试过用行情软件加自选股列表盯LOF结果很受挫。LOF的溢价不是一直挂在那里等你的它往往在开盘后的半小时内突然冒出来持续不到十分钟就被套利资金抹平。而且一只基金出现溢价常常会带动同板块的其他LOF一起动人工根本来不及逐个切换查看。自动化监控解决的就是这个“覆盖范围”和“响应速度”的问题。脚本可以每60秒把整个自选池全部扫一遍溢价率超过设定阈值的品种直接推送微信。我在服务器上跑这套脚本运行时间甚至超过了我的睡眠时间。它不管你是吃饭、开会还是睡觉只要触发条件就发消息这才是机器盯盘的意义。1.3 完整监控链路看起来是什么样整套监控系统的数据流并不复杂大致是四个环节数据采集、指标计算、逻辑判断、消息推送。数据采集从公开接口获取LOF的盘中估算净值和场内实时价格指标计算负责把两者转成溢价率逻辑判断决定是否触发预警消息推送把结果通过微信发到手机上。这四步单独拆开来看都不难难点在于把它们稳定地串联起来长期运行。数据接口偶尔失效、网络偶尔抖动、行情偶尔跳变这些都是实际运行中一定会遇到的问题。所以下面我会把每一步涉及的技术选型和关键判断讲透并附上能跑的Python参考实现。2. 动手前的关键判断数据源、指标口径、预警通道2.1 三种数据要分清收盘净值、盘中估值、场内价格很多刚接触LOF套利的人会被各种概念绕晕我建议先把三种数据彻底分清楚。第一种是收盘净值也就是基金公司每天晚上公布的单位净值它是前一日的结算结果精确但滞后。第二种是盘中估值是第三方平台根据基金最新披露的持仓组合、用实时股价估算出来的净值它在交易时间内持续变化但只是估算值和当天晚上的真实净值会有偏差。第三种是场内实时交易价格就是LOF在交易所里买卖的成交价。溢价套利要盯的是“盘中估值”和“场内价格”不是收盘净值。用收盘净值算数据已经过期没有任何参考价值。这个口径问题一定要搞清楚否则后面整个计算逻辑都会偏。2.2 溢价率公式和阈值怎么定溢价率的计算公式不复杂溢价率(%) (场内价格 - 盘中估算净值) / 盘中估算净值 × 100如果算出来是正数代表溢价负数代表折价。这个值越大套利空间越大但真正能落袋的收益还要扣除申购费、赎回费、场内卖出佣金等成本。基于常见费率我建议把预警阈值分成三档第一档是1.5%属于“开始关注”级别通常覆盖不了交易成本但可以记录观察第二档是2.5%属于“可以考虑”级别对于部分低费率渠道已经具备操作价值第三档是3.5%属于“重要预警”级别这类机会相对稀少往往伴随明显的市场情绪推动值得立即处理。需要注意的是估算净值和真实净值之间存在误差这个误差可能在0.5%甚至更高。所以我更倾向于把阈值稍微定高一些宁可少收到几条消息也不要频繁收到假信号。我最终在自己的脚本里长期运行的阈值就是2.5%并在3.5%设置了更醒目的推送。2.3 微信推送选Server酱还是PushPlus微信预警通道我测试过Server酱、PushPlus和企业微信群机器人三种方式各有取舍。Server酱的使用门槛最低只需要在网站上绑定微信拿到一个SendKey然后调用HTTP接口就能把消息推到微信。个人使用完全免费消息里有富文本内容可以展示表格。PushPlus也类似同样是HTTP接口页面稍微复杂一点。企业微信群机器人的好处是不依赖第三方平台通过Webhook直接推到企业微信群里适合团队共同盯盘但对网络环境有要求而且邀请机器人进个人微信群目前不太方便。我最后选择了Server酱作为主力推送通道。原因很简单免费、稳定、接口简洁一条GET或POST请求就能触发推送非常适合脚本调用。如果你已经有企业微信也可以使用群机器人方式毕竟少一道第三方依赖链路更可控。2.4 调度方案定时任务优于死循环写Python监控脚本常见的做法有两个一是用while True加time.sleep让脚本自己循环二是用调度库在指定时间触发任务。刚开始写监控脚本时我也喜欢用死循环觉得这样代码简单但实际跑了几天就发现问题死循环里只要一个请求卡住后面所有数据都会跟着延迟而且脚本崩了之后不会自动恢复。后来我改用schedule库做固定间隔触发加上APScheduler做更精细的交易时段控制。每个监控周期之间相互独立就算某一只基金的数据请求超时也不会拖垮整个调度。用APScheduler的CronTrigger可以精确控制在交易时段内运行比如周一到周五的9:30到11:30、13:00到15:00非交易时段直接跳过省得收盘后还收到无效信息。3. Python实现从零搭一个能跑的监控脚本3.1 准备工作Python环境与依赖我用的是Python 3.9以上版本主要依赖库包括requests、pandas、schedule和apscheduler。requests负责请求数据和推送消息pandas用来做数据处理和记录schedule负责分钟级触发apscheduler做交易日定时控制。安装依赖很简单直接用pip批量安装即可pip install requests pandas schedule apscheduler如果是在本地Windows上跑安装完以后可以直接在终端运行脚本如果在服务器上跑建议配合systemd或者Docker做进程守护避免SSH断开后脚本被终止。我在稳定运行阶段选择了服务器加systemd的部署方式比本地电脑挂机要可靠得多。3.2 第一步抓取基金盘中估值盘中估值数据我用的是天天基金公开的估值接口。接口地址格式是固定的把基金代码拼进去即可http://fundgz.1234567.com.cn/js/{基金代码}.js接口返回的是JSONP格式例如jsonpgz({fundcode:161005,name:富国天惠成长混合(LOF)A,jzrq:2025-04-15,dwjz:2.9051,gsz:2.8974,gszzl:-0.26,gztime:2025-04-15 15:00})字段含义分别是jzrq是净值日期dwjz是前一日单位净值gsz是当前估算净值gztime是估值时间。在Python里用正则把JSON部分提取出来转换成字典就行。import requests import re import json def fetch_estimate(code: str) - dict: url fhttp://fundgz.1234567.com.cn/js/{code}.js resp requests.get(url, timeout5) resp.encoding utf-8 match re.search(rjsonpgz\((.*?)\), resp.text) if not match: return {} data json.loads(match.group(1)) return { estimate_nav: float(data[gsz]), estimate_time: data[gztime], nav_date: data[jzrq], name: data[name], }要注意这个接口在非交易时间也会返回数据但估算净值是最后一天收盘时的状态盘中判断时一定要先看gztime是否属于当前交易日避免拿过期数据算溢价。3.3 第二步获取场内实时价格场内价格我走的是东方财富的行情接口。LOF基金在交易所的代码有规律上海市场的LOF一般以50、51、52开头深圳市场的LOF一般以16开头。在东方财富接口里secid参数区分市场上海市场用1作为前缀深圳市场用0作为前缀。def get_market_price(code: str) - float: market 1 if code.startswith((50, 51, 52)) else 0 secid f{market}.{code} fields f43 url https://push2.eastmoney.com/api/qt/stock/get params {secid: secid, fields: fields} resp requests.get(url, paramsparams, timeout5) j resp.json() if j.get(data) is None: return 0.0 price j[data][f43] / 100 return pricef43字段在接口里返回的是“价格乘以100”的整数所以需要除以100转换成正常的价格单位。不同接口的字段含义可能随版本变动刚接入时最好先手工打印一次完整响应确认字段位置和数量级再写逻辑。3.4 第三步计算溢价并加“冷静期”有了估算净值和场内价格溢价率就很好算了。但我建议把溢价率计算封装成一个独立函数同时记录时间和基金名称方便后面的消息推送和日志归档。def calc_premium(price: float, estimate_nav: float) - float: if price 0 or estimate_nav 0: return 0.0 return (price - estimate_nav) / estimate_nav * 100直接发预警还有一个问题某个LOF可能在几分钟内连续触发多次如果每次都推送手机通知会变成灾难。我的做法是给每只基金设置一个“冷静期”比如15分钟内只推一次之后再触发才重新提醒。last_alert {} COOLDOWN_SECONDS 15 * 60 def should_alert(code: str) - bool: now time.time() last last_alert.get(code, 0) if now - last COOLDOWN_SECONDS: return False last_alert[code] now return True冷静期时间不宜太长否则机会都走完了才提醒毫无意义也不宜太短否则连续震荡会刷爆手机。我测试下来10到20分钟是比较合理的区间。3.5 第四步微信预警推送我使用Server酱作为推送通道。首先需要在Server酱官网绑定微信并获取SendKey然后调用接口发送消息。标题部分用基金代码和预警类型内容部分把溢价率、场内价格、估算净值、触发时间全部带上方便手机上直接判断。def send_wechat(title: str, content: str) - bool: sendkey 你的SENDKEY url fhttps://sctapi.ftqq.com/{sendkey}.send data {title: title, desp: content} resp requests.post(url, datadata, timeout10) result resp.json() return result.get(code) 0第一次测试时建议先发一条测试消息确认微信能收到再接入监控循环。推送失败时脚本要记录日志不能静默吞掉异常。3.6 第五步整合成定时监控任务主监控函数需要按顺序执行遍历监控列表、取估值、取价格、算溢价、判断阈值、推送预警。每个品种的请求异常都要单独捕获不能让一只基金的数据问题拖垮整个监控进程。import schedule import time WATCH_CODES [161005, 501018, 163415, 160119, 501078] ALERT_LEVEL1 2.5 ALERT_LEVEL2 3.5 def monitor_job(): for code in WATCH_CODES: try: estimate fetch_estimate(code) if not estimate: continue price get_market_price(code) premium calc_premium(price, estimate[estimate_nav]) if premium ALERT_LEVEL2 and should_alert(code): content ( f价格: {price:.3f}\n f估算净值: {estimate[estimate_nav]:.3f}\n f溢价率: {premium:.2f}%\n f估值时间: {estimate[estimate_time]} ) send_wechat(f【重要】{code} {estimate[name]} 溢价 {premium:.2f}%, content) elif premium ALERT_LEVEL1 and should_alert(code): content ( f价格: {price:.3f}\n f估算净值: {estimate[estimate_nav]:.3f}\n f溢价率: {premium:.2f}% ) send_wechat(f【关注】{code} {estimate[name]} 溢价 {premium:.2f}%, content) except Exception as exc: print(f{code} 处理异常: {exc}) continue schedule.every(1).minutes.do(monitor_job) while True: schedule.run_pending() time.sleep(1)这个脚本是最小可运行版本大约六十行代码已经能把溢价机会实时推送到手机上。接下来要做的就是把它丢到服务器上长期运行。4. 跑在生产环境稳定性、日志和那些坑4.1 数据源不稳定怎么办第三方接口不保证全年无休我实际跑下来的确遇到过几次接口超时、返回空数据、甚至是返回昨天旧数据的情况。应对策略有三层。第一层是超时控制。requests请求全部加上timeout参数默认5秒宁可放弃这次采样也不能让脚本卡死。第二层是异常重试。对于偶发性的网络抖动连续重试两次往往就能拿到数据但单只基金重试次数不要超过三次否则会拖慢整个监控周期。第三层是数据新鲜度校验。拿到估值数据后一定要检查gztime是不是当前时间如果发现返回的是非交易时段数据直接跳过不要写进日志里当有效记录。另外不要把接口返回的字段当成永久稳定的。接口升级、字段调整都是有可能的定期手工抽查一次数据确认解析逻辑仍然正确这是运维监控脚本的基本习惯。4.2 别让重复预警把手机塞满很多人把监控脚本跑起来之后遇到的第一个问题不是没信号而是信号太多。盘中某一刻估值跳一下溢价率突然超过阈值但几分钟后价格回到正常区间脚本又会推送一次“恢复”或者“再次触发”的消息。如果不做冷却处理一个交易日下来手机能收到几十条无效通知。我的做法是双保险冷静期加上状态标记。冷静期保证同一只基金最少间隔15分钟才能再次推送状态标记记录每只基金上一轮的溢价状态只有状态从“未触发”变成“触发”时才推送。这样一天下来真正收到的消息数量非常可控每一条背后都对应一个值得看的机会。还有一个小细节推送内容要包含完整的计算字段而不仅仅是溢价率。因为在手机上看到消息时往往已经过了几十秒行情又变了如果只有溢价率而没有价格、净值的对比就没法快速判断现在的真实空间。4.3 日志要留复盘要勤监控脚本不是部署完就不管的我也走过一段“只顾跑、不看结果”的弯路。头一个月脚本天天推消息我也跟着跑了几次操作但盈亏结果并不理想后来一查日志才发现问题不在监控而在我没有把推送和后续成交数据关联起来复盘。建议脚本至少记录两类日志第一类是采样日志每轮监控完成后把每个品种的价格、估值、溢价率、时间追加到CSV文件或数据库第二类是预警日志记录每次推送触发的具体原因和阈值档位。采样的数据积累一段时间后可以用来统计不同基金、不同时段的溢价频率和幅度找到真正值得监控的品种。我现在的做法是每天收盘以后自动生成一个当日汇总文件包含所有监控品种的溢价次数、最大溢价、持续时间。这个文件既是复盘依据也是调整阈值的参考。没有数据积累就去调整预警参数那基本是拍脑袋。4.4 安全边界估值不等于真实净值留足成本余量最后还是要泼一盆冷水。盘中估算净值是基于基金季报持仓推算出来的基金经理实际持仓和季报公布的组合之间往往有一段时间差所以估算误差是所有LOF套利监控都无法回避的问题。盘中看到3%的溢价扣除估算误差和交易成本后实际能落袋的利润可能只剩下一个零头。这也是我不建议把预警阈值放到1.5%以下的原因信号太多操作价值却不高。成本测算也要提前做好。场外申购LOF有申购费场内卖出有佣金赎回拿钱还要等待到账时间资金占用本身也有成本。把这些全部算完以后如果溢价空间仍然可观才值得出手。监控脚本的价值是帮你发现“可能存在机会”的品种而不是代替你完成交易决策。我个人在实际操作里的体会是这套Python自动化监控脚本最大的价值不是让你抓住每一个套利机会而是让你从“被动盯盘”变成“被动收消息”。把监控跑稳了之后我每天花在看盘上的时间大大减少反而更有精力去研究真正重要的东西。如果你也想搭一套类似的系统建议先从小范围开始选三五只自己熟悉的LOF跑两周纯日志模式不急着接微信推送等摸清了数据波动规律再打开预警。稳定压倒一切消息越少说明你的公式越可靠真正来消息的时候才值得你放下手里的事情去看一眼。
返回列表