Files
etf/rotation/oil_tracking.py
aszerW adb83d8cd7 feat: 实现贪心分配模式(greedy)
- 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 夏普
2026-06-21 12:40:40 +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)