- config_loader.py: 添加 etf_pool 字段和 GREEDY 枚举 - config_simple.yaml: 每个资产添加 etf_pool 列表 - simple_rotation.py: - 添加 _compute_greedy_weights 方法 - _calculate_daily_return 支持 greedy 模式 - 向后兼容原有 rank/equal 模式 贪心算法:按 ETF 池容量分配仓位,装不下的顺延给下一名 - 有色金属(1 ETF): 吸收25%,顺延75% - 原油(3 ETF): 吸收75% - 黄金(4 ETF): 吸收100% 回测对比 (select_num=3): - rank: 326.60% 累计收益, 1.24 夏普 - greedy: 421.35% 累计收益, 1.03 夏普
130 lines
4.2 KiB
Python
130 lines
4.2 KiB
Python
"""计算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)
|