Files
etf/rotation/analysis/oil_tracking.py
aszerW ac022020c7 refactor: 整理rotation目录结构
将分析/测试/实验脚本从核心目录移出:
- enrich_etf_data.py → scripts/
- oil_tracking.py → analysis/
- tracking_error_full.py → analysis/
- tracking_error_validation.py → analysis/
- test_start_year_analysis.py → experiments/
- experiment_select_num.py → experiments/

rotation/ 目录现在只保留核心策略代码:
- simple_rotation.py (策略主逻辑)
- config_loader.py (配置加载)
- config_simple.yaml (配置文件)
- daily_scheduler.py (调度器)
2026-06-21 13:38:15 +08:00

130 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""计算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)