ETF资金流监测系统实战:用Python构建场内场外套利信号识别

ETF资金流监测系统实战:用Python构建场内场外套利信号识别

ETF是A股一个特殊的存在——它既是一篮子股票,又能在二级市场像股票一样交易。我做量化这几年,专门搭建了一套ETF资金流监测系统,用Python把场内场外的资金流数据打通,识别套利信号。这篇文章把核心实现分享出来。

ETF数据主要分四块。第一块是ETF基金列表,路径base/jjlbList(基金列表),返回所有ETF基金的代码、名称、类型、跟踪指数。第二块是ETF基金行情,路径time/jjHQ/{基金代码},返回ETF的实时行情。第三块是ETF基金规模变动,路径time/jjgm/{基金代码},返回ETF的份额变动历史。第四块是行业资金流向,路径all/zjlx/zjhhy(行业资金流向),返回各行业的资金流向数据。我把这四块数据全部沉到本地。

先说ETF基金列表怎么读。

import json
import os
import pandas as pd

data_dir = "D:/stock_data"

def read_etf_list():
    file_path = os.path.join(data_dir, "base", "jjlbList")
    if not os.path.exists(file_path):
        return None
    with open(file_path, "r", encoding="utf-8") as f:
        data = json.load(f)
    df = pd.DataFrame(data)
    df.columns = ["dm", "mc", "jjlb", "gmbm", "gmrb", "gmsj", "gmjz", "gmje"]
    return df

def read_etf_realtime(dm):
    file_path = os.path.join(data_dir, "time", "jjHQ", dm)
    if not os.path.exists(file_path):
        return None
    with open(file_path, "r", encoding="utf-8") as f:
        data = json.load(f)
    df = pd.DataFrame([data])
    df.columns = ["dm", "mc", "now", "zf", "jk", "zs", "zsz", "cjsl", "cjje", "zt", "dt", "wb", "wp"]
    return df

字段简写,dm基金代码mc基金名称jjlb基金类型gmbm规模编码gmrb规模日期gmje规模金额。实时行情字段now当前价zf涨跌幅jk今开zs昨收zsz总市值cjsl成交量cjje成交金额zt涨停dt跌停wb委买wp委卖。

第一步是分析ETF场内场外溢价。ETF同时在场内(二级市场)和场外(一级市场)交易,两个价格之间的差额就是套利空间。

def analyze_etf_premium(dm, lookback_days=30):
    realtime = read_etf_realtime(dm)
    if realtime is None:
        return None
    
    etf_list = read_etf_list()
    if etf_list is None:
        return None
    
    target_etf = etf_list[etf_list["dm"] == dm]
    if len(target_etf) == 0:
        return None
    
    etf_iopv = target_etf.iloc[0]["gmjz"]
    etf_price = realtime["now"].iloc[0]
    premium = (etf_price - etf_iopv) / etf_iopv * 100
    
    print("ETF{}场内场外溢价分析:".format(dm))
    print("场内价格:{:.4f}".format(etf_price))
    print("场外净值:{:.4f}".format(etf_iopv))
    print("溢价率:{:.2f}%".format(premium))
    
    signal = None
    if premium > 2.0:
        signal = "场内高溢价,可考虑卖出套利"
    elif premium < -1.0:
        signal = "场内折价,可考虑买入套利"
    else:
        signal = "溢价正常,无套利空间"
    
    return {"premium": premium, "signal": signal}

第二步是分析ETF份额变动。ETF份额变动反映了资金对这只ETF的态度,是大资金进场还是离场最直观的指标。

def read_etf_share_change(dm):
    file_path = os.path.join(data_dir, "time", "jjgm", dm)
    if not os.path.exists(file_path):
        return None
    with open(file_path, "r", encoding="utf-8") as f:
        data = json.load(f)
    df = pd.DataFrame(data)
    df.columns = ["rq", "fbdw", "fqjz", "fcye", "zfdw", "zqjz", "zcye", "jzzzl"]
    return df

def analyze_etf_share_change(dm, days=30):
    df = read_etf_share_change(dm)
    if df is None or len(df) == 0:
        return None
    
    df["fbdw"] = pd.to_numeric(df["fbdw"], errors="coerce")
    df["zfdw"] = pd.to_numeric(df["zfdw"], errors="coerce")
    df["rq"] = pd.to_datetime(df["rq"], errors="coerce")
    
    recent = df.head(days)
    total_inflow = recent["fbdw"].sum() / 1e8
    total_outflow = recent["zfdw"].sum() / 1e8
    net = total_inflow - total_outflow
    
    print("ETF{}近{}日份额变动:".format(dm, days))
    print("总申购:{:.2f}亿份".format(total_inflow))
    print("总赎回:{:.2f}亿份".format(total_outflow))
    print("净申购:{:.2f}亿份".format(net))
    
    signal = None
    if net > 1.0:
        signal = "大额净申购,资金流入"
    elif net < -1.0:
        signal = "大额净赎回,资金流出"
    else:
        signal = "份额变动正常"
    
    return {"net": net, "signal": signal}

第三步是行业ETF联动分析。行业ETF和行业指数高度联动,可以用行业资金流向数据来预判行业ETF的走势。

def read_industry_flow():
    file_path = os.path.join(data_dir, "all", "zjlx", "zjhhy")
    if not os.path.exists(file_path):
        return None
    with open(file_path, "r", encoding="utf-8") as f:
        data = json.load(f)
    df = pd.DataFrame(data)
    df.columns = ["rq", "hy", "zjl", "zlJlr", "zlJlb", "shJlb", "ddy", "ddx", "ddz", "ddf", "f5MinZlJe"]
    return df

def find_industry_etf_signals(date_str):
    df = read_industry_flow()
    if df is None:
        return None
    
    df["rq"] = pd.to_datetime(df["rq"], errors="coerce")
    target = df[df["rq"].dt.strftime("%Y-%m-%d") == date_str]
    if len(target) == 0:
        return None
    
    target["zlJlr"] = pd.to_numeric(target["zlJlr"], errors="coerce")
    
    strong = target[target["zlJlr"] > 5]
    weak = target[target["zlJlr"] < -5]
    
    print("日期:{} 行业资金流向".format(date_str))
    print("强势行业:")
    for idx, row in strong.iterrows():
        print("  {}:净流入{:.2f}亿".format(row["hy"], row["zlJlr"]))
    print("弱势行业:")
    for idx, row in weak.iterrows():
        print("  {}:净流出{:.2f}亿".format(row["hy"], abs(row["zlJlr"])))
    
    return {"strong": strong, "weak": weak}

第四步是构建完整的ETF套利信号系统。我把上面几个模块组合起来。

def etf_arbitrage_scan(date_str):
    etf_list = read_etf_list()
    if etf_list is None:
        return None
    
    etf_list = etf_list[etf_list["jjlb"].str.contains("ETF", na=False)]
    
    signals = []
    for dm in etf_list["dm"].tolist():
        premium_result = analyze_etf_premium(dm)
        share_result = analyze_etf_share_change(dm, days=10)
        
        if premium_result is None or share_result is None:
            continue
        
        premium = premium_result["premium"]
        net_share = share_result["net"]
        
        if premium > 2.0 and net_share > 0.5:
            signals.append({
                "dm": dm,
                "signal": "高溢价+净申购:可能有大资金申购套利,关注卖出机会",
                "premium": premium,
                "net_share": net_share
            })
        elif premium < -1.0 and net_share < -0.5:
            signals.append({
                "dm": dm,
                "signal": "折价+净赎回:可能有大资金赎回套利,关注买入机会",
                "premium": premium,
                "net_share": net_share
            })
    
    return pd.DataFrame(signals)

这套ETF系统跑了一年,最关键的收获是:ETF的资金流向比价格更值得关注。当某只ETF出现大额净申购,但价格没怎么涨的时候,往往是聪明钱在悄悄建仓。反过来,当某只ETF大额净赎回但价格还坚挺的时候,往往是陷阱。

接口说明:

  • ETF基金列表:base/jjlbList
  • ETF实时行情:time/jjHQ/{dm}
  • ETF份额变动:time/jjgm/{dm}
  • 行业资金流向:all/zjlx/zjhhy

资料参考:ig50

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值