"""计算3只原油ETF跟踪WTI原油价格的准确率(使用Flask API数据源)""" import os, sys sys.path.insert(0, '/app') from dotenv import load_dotenv load_dotenv('/app/.env') import pandas as pd import numpy as np import requests import time # 绕过系统代理避免SSL EOF _session = requests.Session() _session.trust_env = False BASE_URL = os.getenv('FLASK_API_URL', 'https://k3s.tokenpluse.xyz') def fetch_ohlcv(code, start='2020-01-01', end='2026-06-20'): """通过Flask API获取OHLCV数据""" url = f"{BASE_URL}/api/v1/ohlcv" params = {'code': code, 'start': start, 'end': end} for attempt in range(3): try: resp = _session.get(url, params=params, timeout=120) if resp.status_code == 200: data = resp.json() if 'error' in data or not data.get('data'): print(f" {code}: 无数据 - {data.get('error', 'empty')}") return None df = pd.DataFrame(data['data']) df['date'] = pd.to_datetime(df['date']) df = df.set_index('date').sort_index() df['ret'] = df['close'].pct_change() cnt = data.get('count', len(df)) dr = data.get('date_range', {}) print(f" {code}: {cnt} 条 ({dr.get('start','?')} ~ {dr.get('end','?')})") return df else: print(f" {code}: HTTP {resp.status_code}") return None except Exception as e: if attempt < 2: time.sleep(2) continue print(f" {code}: 失败 - {e}") return None return None # 1. 获取WTI原油价格 print("=" * 90) print("获取WTI原油价格 (CL=F)") print("=" * 90) cl_df = fetch_ohlcv('CL=F') if cl_df is None or len(cl_df) < 10: print("WTI数据获取失败,退出") sys.exit(1) # 2. 获取3只原油ETF价格 etf_codes = { '160723.SZ': '嘉实原油(WTI*100%)', '161129.SZ': '易方达原油(标普高盛原油*100%)', '501018.SH': '南方原油(WTI*60%+BRENT*40%)', } print("\n" + "=" * 90) print("获取原油ETF价格") print("=" * 90) etf_dfs = {} for code, name in etf_codes.items(): df = fetch_ohlcv(code) if df is not None: etf_dfs[code] = name # 3. 计算跟踪准确率 print("\n" + "=" * 90) print("跟踪准确率(基于ETF收盘价收益率 vs WTI收盘价日收益率)") print("=" * 90) for code, name in etf_dfs.items(): etf = etf_dfs[code][['ret']].copy() etf.columns = ['etf_ret'] etf.index = etf.index.normalize() cl = cl_df[['ret']].copy() cl.columns = ['cl_ret'] cl.index = cl.index.normalize() m = pd.merge(etf.reset_index(), cl.reset_index(), on='date', how='inner', suffixes=('', '_cl')) if 'ret' in m.columns and 'ret_cl' in m.columns: m = m.rename(columns={'ret': 'etf_ret', 'ret_cl': 'cl_ret'}) m = m[['date', 'etf_ret', 'cl_ret']].dropna() if len(m) < 10: print(f"\n{code} {name}: 数据不足 ({len(m)} 天)") continue corr = m['etf_ret'].corr(m['cl_ret']) r2 = corr ** 2 diff = m['etf_ret'] - m['cl_ret'] te_annual = diff.std() * np.sqrt(252) cum_etf = (1 + m['etf_ret']).prod() - 1 cum_cl = (1 + m['cl_ret']).prod() - 1 bias = diff.mean() # 分段: 2024至今 recent = m[m['date'] >= '2024-01-01'] if len(recent) > 20: r2_recent = recent['etf_ret'].corr(recent['cl_ret']) ** 2 else: r2_recent = np.nan print(f"\n{code} {name}") print(f" 重叠交易日: {len(m)} 天") print(f" 全区间 R²: {r2:.4f} ({r2*100:.1f}%)") if not np.isnan(r2_recent): print(f" 2024至今 R²: {r2_recent:.4f} ({r2_recent*100:.1f}%)") else: print(f" 2024至今 R²: 数据不足") print(f" 年化跟踪误差: {te_annual*100:.2f}%") print(f" 日均偏差: {bias*100:.4f}%") print(f" ETF累计收益: {cum_etf*100:.1f}%") print(f" WTI累计收益: {cum_cl*100:.1f}%") print(f" 累计收益差: {(cum_etf-cum_cl)*100:.1f}%") print("\n" + "=" * 90) print("注: 原油ETF为QDII-LOF,净值披露有T+1~T+2延迟,") print(" 且需通过期货合约展期,与WTI现货价格存在结构性偏差。") print("=" * 90)