refactor(archive): move unused modules to archive/
Archive legacy framework and utility modules that are no longer referenced by the active core (datasource/ and rotation/): - framework/ -> archive/framework/ - framework_v2/ -> archive/framework_v2/ - strategies/ -> archive/strategies/ - config/ -> archive/config/ - visualization/ -> archive/visualization/ - scripts/ -> archive/scripts/ - tests/ -> archive/tests/ - run_rotation.py, run_us_rotation.py -> archive/single_files/ - compare_*.py, test_api_dates.py -> archive/single_files/
This commit is contained in:
95
archive/tests/test_flask_api_calendar.py
Normal file
95
archive/tests/test_flask_api_calendar.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
测试 FlaskAPIDataSource 的交易日历获取功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到路径
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from datasource.flask_api_source import get_flask_api_source
|
||||
|
||||
|
||||
def test_trading_calendar():
|
||||
"""测试交易日历获取"""
|
||||
print("=" * 60)
|
||||
print("测试 FlaskAPIDataSource 交易日历获取")
|
||||
print("=" * 60)
|
||||
|
||||
source = get_flask_api_source()
|
||||
|
||||
# 测试 1: 获取服务信息
|
||||
print("\n[测试 1] 获取服务信息")
|
||||
info = source.get_service_info()
|
||||
if 'error' not in info:
|
||||
print(f"✓ 服务名称: {info.get('name', 'N/A')}")
|
||||
print(f"✓ API 版本: {info.get('version', 'N/A')}")
|
||||
else:
|
||||
print(f"✗ 服务信息获取失败: {info['error']}")
|
||||
|
||||
# 测试 2: 获取日历信息
|
||||
print("\n[测试 2] 获取日历信息")
|
||||
cal_info = source.get_calendar_info()
|
||||
if 'error' not in cal_info:
|
||||
print(f"✓ pandas_market_calendars 已安装: {cal_info.get('pandas_market_calendars_installed')}")
|
||||
print(f"✓ 支持的市场: {list(cal_info.get('supported_markets', {}).keys())}")
|
||||
else:
|
||||
print(f"✗ 日历信息获取失败: {cal_info['error']}")
|
||||
|
||||
# 测试 3: A 股交易日历
|
||||
print("\n[测试 3] A 股交易日历 (2024-01)")
|
||||
dates_a = source.get_trading_calendar('A', '2024-01-01', '2024-01-31')
|
||||
if dates_a is not None:
|
||||
print(f"✓ 返回 {len(dates_a)} 个交易日")
|
||||
print(f" 首个交易日: {dates_a[0].strftime('%Y-%m-%d')}")
|
||||
print(f" 最后交易日: {dates_a[-1].strftime('%Y-%m-%d')}")
|
||||
else:
|
||||
print("✗ A 股交易日历获取失败")
|
||||
|
||||
# 测试 4: 美股交易日历
|
||||
print("\n[测试 4] 美股交易日历 (2024-01)")
|
||||
dates_us = source.get_trading_calendar('US', '2024-01-01', '2024-01-15')
|
||||
if dates_us is not None:
|
||||
print(f"✓ 返回 {len(dates_us)} 个交易日")
|
||||
print(f" 首个交易日: {dates_us[0].strftime('%Y-%m-%d')}")
|
||||
print(f" 最后交易日: {dates_us[-1].strftime('%Y-%m-%d')}")
|
||||
# 验证马丁·路德·金日(1月15日)是否被排除
|
||||
mlk_day = '2024-01-15'
|
||||
if mlk_day not in [d.strftime('%Y-%m-%d') for d in dates_us]:
|
||||
print(f" ✓ 正确识别马丁·路德·金日休市")
|
||||
else:
|
||||
print("✗ 美股交易日历获取失败")
|
||||
|
||||
# 测试 5: 港股交易日历
|
||||
print("\n[测试 5] 港股交易日历 (2024-01)")
|
||||
dates_hk = source.get_trading_calendar('HK', '2024-01-01', '2024-01-15')
|
||||
if dates_hk is not None:
|
||||
print(f"✓ 返回 {len(dates_hk)} 个交易日")
|
||||
print(f" 首个交易日: {dates_hk[0].strftime('%Y-%m-%d')}")
|
||||
print(f" 最后交易日: {dates_hk[-1].strftime('%Y-%m-%d')}")
|
||||
else:
|
||||
print("✗ 港股交易日历获取失败")
|
||||
|
||||
# 测试 6: 验证 OHLCV 功能仍然正常
|
||||
print("\n[测试 6] 验证 OHLCV 数据获取")
|
||||
df = source.fetch('518880.SH', '2024-01-01', '2024-01-10')
|
||||
if df is not None and len(df) > 0:
|
||||
print(f"✓ 获取 {len(df)} 条 OHLCV 数据")
|
||||
else:
|
||||
print("✗ OHLCV 数据获取失败")
|
||||
|
||||
# 总结
|
||||
print("\n" + "=" * 60)
|
||||
success_count = sum([
|
||||
dates_a is not None,
|
||||
dates_us is not None,
|
||||
dates_hk is not None,
|
||||
df is not None
|
||||
])
|
||||
print(f"测试完成: {success_count}/4 通过")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_trading_calendar()
|
||||
164
archive/tests/test_trading_calendar.py
Normal file
164
archive/tests/test_trading_calendar.py
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试交易日历 API
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import requests
|
||||
|
||||
# Flask 服务地址
|
||||
FLASK_API_URL = "http://localhost:80"
|
||||
|
||||
def test_calendar_api():
|
||||
"""测试交易日历 API"""
|
||||
print("\n" + "="*80)
|
||||
print("📅 交易日历 API 测试")
|
||||
print("="*80)
|
||||
|
||||
# 测试 1: A 股
|
||||
print("\n[1] 测试 A 股交易日历...")
|
||||
url = f"{FLASK_API_URL}/api/v1/trading-calendar"
|
||||
params = {"market": "A", "start": "2024-01-01", "end": "2024-01-31"}
|
||||
|
||||
try:
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" ✅ 成功: {data['count']} 个交易日")
|
||||
print(f" 市场: {data['market']}")
|
||||
print(f" 交易所: {data['exchange']}")
|
||||
print(f" 日期范围: {data['start']} ~ {data['end']}")
|
||||
print(f" 前5个交易日: {data['trading_dates'][:5]}")
|
||||
else:
|
||||
print(f" ❌ 失败: {response.status_code}")
|
||||
print(f" 响应: {response.json()}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {e}")
|
||||
|
||||
# 测试 2: 美股
|
||||
print("\n[2] 测试美股交易日历...")
|
||||
params = {"market": "US", "start": "2024-01-01", "end": "2024-01-31"}
|
||||
|
||||
try:
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" ✅ 成功: {data['count']} 个交易日")
|
||||
print(f" 市场: {data['market']}")
|
||||
print(f" 交易所: {data['exchange']}")
|
||||
print(f" 前5个交易日: {data['trading_dates'][:5]}")
|
||||
else:
|
||||
print(f" ❌ 失败: {response.status_code}")
|
||||
print(f" 响应: {response.json()}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {e}")
|
||||
|
||||
# 测试 3: 港股
|
||||
print("\n[3] 测试港股交易日历...")
|
||||
params = {"market": "HK", "start": "2024-01-01", "end": "2024-01-31"}
|
||||
|
||||
try:
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" ✅ 成功: {data['count']} 个交易日")
|
||||
print(f" 市场: {data['market']}")
|
||||
print(f" 交易所: {data['exchange']}")
|
||||
print(f" 前5个交易日: {data['trading_dates'][:5]}")
|
||||
else:
|
||||
print(f" ❌ 失败: {response.status_code}")
|
||||
print(f" 响应: {response.json()}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {e}")
|
||||
|
||||
# 测试 4: 日历信息
|
||||
print("\n[4] 测试日历信息...")
|
||||
url_info = f"{FLASK_API_URL}/api/v1/calendar/info"
|
||||
|
||||
try:
|
||||
response = requests.get(url_info, timeout=10)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" ✅ 成功")
|
||||
print(f" 支持的市场:")
|
||||
for market, info in data.get('supported_markets', {}).items():
|
||||
print(f" {market}: {info['name']} ({info['method']})")
|
||||
print(f" pandas_market_calendars: {'✅ 已安装' if data.get('pandas_market_calendars_installed') else '❌ 未安装'}")
|
||||
else:
|
||||
print(f" ❌ 失败: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 异常: {e}")
|
||||
|
||||
def test_local_fetcher():
|
||||
"""测试本地 UniversalDataFetcher"""
|
||||
print("\n" + "="*80)
|
||||
print("🧪 本地 UniversalDataFetcher 测试")
|
||||
print("="*80)
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
try:
|
||||
from datasource.universal_fetcher import UniversalDataFetcher
|
||||
|
||||
fetcher = UniversalDataFetcher()
|
||||
|
||||
# 测试 A 股
|
||||
print("\n[1] A 股交易日历 (2024年)...")
|
||||
cal_a = fetcher.get_trading_calendar('A', '2024-01-01', '2024-12-31')
|
||||
print(f" ✅ {len(cal_a)} 个交易日")
|
||||
print(f" 前5天: {list(cal_a[:5])}")
|
||||
|
||||
# 测试美股
|
||||
print("\n[2] 美股交易日历 (2024年)...")
|
||||
cal_us = fetcher.get_trading_calendar('US', '2024-01-01', '2024-12-31')
|
||||
print(f" ✅ {len(cal_us)} 个交易日")
|
||||
print(f" 前5天: {list(cal_us[:5])}")
|
||||
|
||||
# 测试港股
|
||||
print("\n[3] 港股交易日历 (2024年)...")
|
||||
cal_hk = fetcher.get_trading_calendar('HK', '2024-01-01', '2024-12-31')
|
||||
print(f" ✅ {len(cal_hk)} 个交易日")
|
||||
print(f" 前5天: {list(cal_hk[:5])}")
|
||||
|
||||
# 日历信息
|
||||
print("\n[4] 日历支持信息...")
|
||||
info = fetcher.get_calendar_info()
|
||||
print(f" ✅ 支持 {len(info['supported_markets'])} 个市场")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def main():
|
||||
print("\n" + "="*80)
|
||||
print("📅 交易日历功能测试")
|
||||
print("="*80)
|
||||
|
||||
# 测试 1: 本地 fetcher
|
||||
test_local_fetcher()
|
||||
|
||||
# 测试 2: Flask API(如果服务在运行)
|
||||
print("\n" + "="*80)
|
||||
print("🌐 测试 Flask API 端点")
|
||||
print("="*80)
|
||||
print(f"\nAPI 地址: {FLASK_API_URL}")
|
||||
print("注意: 需要 Flask 服务正在运行")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{FLASK_API_URL}/health", timeout=3)
|
||||
if response.status_code == 200:
|
||||
print("✅ Flask 服务可访问")
|
||||
test_calendar_api()
|
||||
else:
|
||||
print(f"⚠️ Flask 服务返回 {response.status_code},跳过 API 测试")
|
||||
except:
|
||||
print("⚠️ Flask 服务未运行,跳过 API 测试")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("✅ 测试完成")
|
||||
print("="*80)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
143
archive/tests/verify_fix_result.py
Normal file
143
archive/tests/verify_fix_result.py
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
验证修复后的回测结果是否与文档一致
|
||||
|
||||
文档预期结果 (Mode A - 指数信号+指数收益):
|
||||
CAGR: 11.80%, 最大回撤: -29.49%, 夏普: 0.818, Calmar: 0.400
|
||||
|
||||
文档预期结果 (Mode B - 指数信号+ETF收益):
|
||||
CAGR: 28.07%, 最大回撤: -13.34%, 夏普: 1.685, Calmar: 2.104
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from strategies.rotation.strategy import RotationStrategy
|
||||
|
||||
|
||||
def calculate_metrics(nav: pd.Series) -> dict:
|
||||
"""计算绩效指标"""
|
||||
start_date = nav.index[0]
|
||||
end_date = nav.index[-1]
|
||||
days = (end_date - start_date).days
|
||||
years = days / 365
|
||||
|
||||
total_return = nav.iloc[-1] - 1
|
||||
cagr = (nav.iloc[-1] / nav.iloc[0]) ** (1/years) - 1
|
||||
|
||||
daily_ret = nav.pct_change().dropna()
|
||||
sharpe = daily_ret.mean() / daily_ret.std() * np.sqrt(252) if daily_ret.std() > 0 else 0
|
||||
|
||||
peak = nav.cummax()
|
||||
drawdown = (nav - peak) / peak
|
||||
max_dd = drawdown.min()
|
||||
|
||||
calmar = cagr / abs(max_dd) if max_dd != 0 else 0
|
||||
win_rate = (daily_ret > 0).sum() / len(daily_ret)
|
||||
|
||||
return {
|
||||
'start_date': start_date.strftime('%Y-%m-%d'),
|
||||
'end_date': end_date.strftime('%Y-%m-%d'),
|
||||
'years': years,
|
||||
'days': len(nav),
|
||||
'total_return': total_return,
|
||||
'cagr': cagr,
|
||||
'max_dd': max_dd,
|
||||
'sharpe': sharpe,
|
||||
'calmar': calmar,
|
||||
'win_rate': win_rate
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
# 加载配置
|
||||
config_path = project_root / 'strategies/rotation/config.yaml'
|
||||
with open(config_path, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
# 设置回测区间(文档中的测试区间)
|
||||
config['start_date'] = '2020-01-02'
|
||||
config['end_date'] = '2026-05-19'
|
||||
|
||||
print('='*70)
|
||||
print('修复后回测结果验证')
|
||||
print('='*70)
|
||||
print(f'回测区间: {config["start_date"]} ~ {config["end_date"]}')
|
||||
|
||||
# 初始化策略
|
||||
strategy = RotationStrategy(config)
|
||||
|
||||
# 获取数据并执行回测
|
||||
print('\n获取数据...')
|
||||
data = strategy.get_data(use_flask_api=False)
|
||||
|
||||
print('\n执行回测...')
|
||||
result = strategy.run_backtest(data=data)
|
||||
|
||||
if result.get('result') is None:
|
||||
print('❌ 回测未生成结果')
|
||||
return
|
||||
|
||||
# 计算指标
|
||||
nav = result['result']['策略净值']
|
||||
metrics = calculate_metrics(nav)
|
||||
|
||||
# 输出结果
|
||||
print('\n' + '='*70)
|
||||
print('修复后回测结果')
|
||||
print('='*70)
|
||||
print(f"回测区间: {metrics['start_date']} ~ {metrics['end_date']}")
|
||||
print(f"回测年数: {metrics['years']:.2f} 年")
|
||||
print(f"交易天数: {metrics['days']} 天")
|
||||
print('-'*70)
|
||||
print(f"CAGR: {metrics['cagr']:.2%}")
|
||||
print(f"最大回撤: {metrics['max_dd']:.2%}")
|
||||
print(f"夏普比率: {metrics['sharpe']:.3f}")
|
||||
print(f"Calmar比率: {metrics['calmar']:.3f}")
|
||||
print(f"日胜率: {metrics['win_rate']:.2%}")
|
||||
print(f"累计收益: {metrics['total_return']:.2%}")
|
||||
print(f"调仓次数: {len(result.get('rebalance_events', []))} 次")
|
||||
print('='*70)
|
||||
|
||||
# 文档预期结果对比
|
||||
print('\n' + '='*70)
|
||||
print('文档预期结果对比')
|
||||
print('='*70)
|
||||
print("\nMode A (指数信号 → 指数收益):")
|
||||
print(" 预期: CAGR 11.80%, MaxDD -29.49%, Sharpe 0.818, Calmar 0.400")
|
||||
|
||||
print("\nMode B (指数信号 → ETF收益):")
|
||||
print(" 预期: CAGR 28.07%, MaxDD -13.34%, Sharpe 1.685, Calmar 2.104")
|
||||
|
||||
# 判断当前模式
|
||||
print('\n' + '-'*70)
|
||||
cagr_diff_a = abs(metrics['cagr'] - 0.1180)
|
||||
cagr_diff_b = abs(metrics['cagr'] - 0.2807)
|
||||
|
||||
if cagr_diff_a < 0.03:
|
||||
print(f"✓ 当前结果接近 Mode A (CAGR差异: {cagr_diff_a:.2%})")
|
||||
print(" 说明: 当前回测使用指数收盘价计算收益")
|
||||
elif cagr_diff_b < 0.03:
|
||||
print(f"✓ 当前结果接近 Mode B (CAGR差异: {cagr_diff_b:.2%})")
|
||||
print(" 说明: 当前回测使用ETF价格计算收益")
|
||||
else:
|
||||
print(f"⚠ 当前结果与文档预期有差异")
|
||||
print(f" Mode A CAGR差异: {cagr_diff_a:.2%}")
|
||||
print(f" Mode B CAGR差异: {cagr_diff_b:.2%}")
|
||||
|
||||
print('='*70)
|
||||
|
||||
return metrics
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
208
archive/tests/verify_mode_b.py
Normal file
208
archive/tests/verify_mode_b.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
验证 Mode B: 指数信号 → ETF收益
|
||||
|
||||
文档预期结果:
|
||||
CAGR: 28.07%, 最大回撤: -13.34%, 夏普: 1.685, Calmar: 2.104
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from strategies.rotation.strategy import RotationStrategy
|
||||
|
||||
|
||||
def calculate_metrics(nav: pd.Series) -> dict:
|
||||
"""计算绩效指标"""
|
||||
start_date = nav.index[0]
|
||||
end_date = nav.index[-1]
|
||||
days = (end_date - start_date).days
|
||||
years = days / 365
|
||||
|
||||
total_return = nav.iloc[-1] - 1
|
||||
cagr = (nav.iloc[-1] / nav.iloc[0]) ** (1/years) - 1
|
||||
|
||||
daily_ret = nav.pct_change().dropna()
|
||||
sharpe = daily_ret.mean() / daily_ret.std() * np.sqrt(252) if daily_ret.std() > 0 else 0
|
||||
|
||||
peak = nav.cummax()
|
||||
drawdown = (nav - peak) / peak
|
||||
max_dd = drawdown.min()
|
||||
|
||||
calmar = cagr / abs(max_dd) if max_dd != 0 else 0
|
||||
win_rate = (daily_ret > 0).sum() / len(daily_ret)
|
||||
|
||||
return {
|
||||
'start_date': start_date.strftime('%Y-%m-%d'),
|
||||
'end_date': end_date.strftime('%Y-%m-%d'),
|
||||
'years': years,
|
||||
'days': len(nav),
|
||||
'total_return': total_return,
|
||||
'cagr': cagr,
|
||||
'max_dd': max_dd,
|
||||
'sharpe': sharpe,
|
||||
'calmar': calmar,
|
||||
'win_rate': win_rate
|
||||
}
|
||||
|
||||
|
||||
def run_mode_b_backtest(data: dict, signals: pd.DataFrame, valid_codes: list,
|
||||
etf_code_map: dict, a_share_dates: pd.DatetimeIndex,
|
||||
trade_cost: float, select_num: int) -> dict:
|
||||
"""
|
||||
Mode B: 使用ETF价格计算收益
|
||||
|
||||
Args:
|
||||
data: 包含 etf_data 的数据字典
|
||||
signals: 指数生成的信号
|
||||
valid_codes: 指数代码列表
|
||||
etf_code_map: {指数代码: ETF代码} 映射
|
||||
a_share_dates: A股交易日历
|
||||
trade_cost: 交易成本
|
||||
select_num: 选股数量
|
||||
"""
|
||||
from framework.execution import BacktestExecutor
|
||||
|
||||
etf_data = data.get('etf_data')
|
||||
if etf_data is None:
|
||||
print("❌ ETF数据不可用")
|
||||
return {'result': None}
|
||||
|
||||
# 将信号对齐到 A 股日历
|
||||
if a_share_dates is not signals.index:
|
||||
signals = signals.reindex(a_share_dates, method='ffill').dropna(subset=[signals.columns[0]])
|
||||
|
||||
# 使用ETF收盘价计算收益率
|
||||
returns_data = {}
|
||||
for code in valid_codes:
|
||||
etf_code = etf_code_map.get(code)
|
||||
if etf_code and etf_code in etf_data.columns:
|
||||
etf_close = etf_data[etf_code].dropna()
|
||||
# 对齐到A股日历
|
||||
etf_aligned = etf_close.reindex(a_share_dates, method='ffill')
|
||||
returns_aligned = etf_aligned.pct_change(fill_method=None)
|
||||
# 使用指数代码作为列名(与信号匹配)
|
||||
returns_data[f'日收益率_{code}'] = returns_aligned
|
||||
else:
|
||||
# 没有ETF映射的标的,回退使用指数数据
|
||||
index_data = data.get('index_data', {})
|
||||
if code in index_data and 'close' in index_data[code].columns:
|
||||
close_series = index_data[code]['close'].dropna()
|
||||
close_aligned = close_series.reindex(a_share_dates, method='ffill')
|
||||
returns_data[f'日收益率_{code}'] = close_aligned.pct_change(fill_method=None)
|
||||
|
||||
returns_df = pd.DataFrame(returns_data)
|
||||
|
||||
# 对齐日期
|
||||
common_dates = signals.index.intersection(returns_df.index)
|
||||
signals = signals.loc[common_dates]
|
||||
returns_df = returns_df.loc[common_dates]
|
||||
|
||||
print(f" Mode B 对齐后日期: {len(common_dates)} 天")
|
||||
print(f" 使用ETF计算收益: {len([c for c in valid_codes if etf_code_map.get(c)])} 只")
|
||||
|
||||
executor = BacktestExecutor(
|
||||
initial_capital=100000,
|
||||
trade_cost=trade_cost,
|
||||
select_num=select_num
|
||||
)
|
||||
|
||||
portfolio = executor.execute(signals, returns_df)
|
||||
|
||||
if hasattr(portfolio, 'backtest_result'):
|
||||
return {'result': portfolio.backtest_result, 'portfolio': portfolio}
|
||||
|
||||
return {'result': None}
|
||||
|
||||
|
||||
def main():
|
||||
# 加载配置
|
||||
config_path = project_root / 'strategies/rotation/config.yaml'
|
||||
with open(config_path, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
# 设置回测区间
|
||||
config['start_date'] = '2020-01-02'
|
||||
config['end_date'] = '2026-05-19'
|
||||
|
||||
print('='*70)
|
||||
print('Mode B 验证: 指数信号 → ETF收益')
|
||||
print('='*70)
|
||||
|
||||
# 初始化策略
|
||||
strategy = RotationStrategy(config)
|
||||
|
||||
# 获取数据
|
||||
print('\n获取数据...')
|
||||
data = strategy.get_data(use_flask_api=False)
|
||||
|
||||
# 计算因子(使用指数数据)
|
||||
print('\n计算因子(指数信号)...')
|
||||
factor_df = strategy.compute_factors(data)
|
||||
|
||||
# 生成信号
|
||||
print('\n生成信号...')
|
||||
signals = strategy.generate_signals(factor_df)
|
||||
|
||||
# 执行 Mode B 回测
|
||||
print('\n执行 Mode B 回测(ETF收益)...')
|
||||
result_b = run_mode_b_backtest(
|
||||
data=data,
|
||||
signals=signals,
|
||||
valid_codes=data['valid_codes'],
|
||||
etf_code_map=data['etf_code_map'],
|
||||
a_share_dates=data.get('a_share_dates'),
|
||||
trade_cost=config.get('trade_cost', 0.001),
|
||||
select_num=config.get('select_num', 3)
|
||||
)
|
||||
|
||||
if result_b.get('result') is None:
|
||||
print('❌ Mode B 回测未生成结果')
|
||||
return
|
||||
|
||||
# 计算指标
|
||||
nav_b = result_b['result']['策略净值']
|
||||
metrics_b = calculate_metrics(nav_b)
|
||||
|
||||
# 输出结果
|
||||
print('\n' + '='*70)
|
||||
print('Mode B 回测结果')
|
||||
print('='*70)
|
||||
print(f"回测区间: {metrics_b['start_date']} ~ {metrics_b['end_date']}")
|
||||
print(f"回测年数: {metrics_b['years']:.2f} 年")
|
||||
print(f"交易天数: {metrics_b['days']} 天")
|
||||
print('-'*70)
|
||||
print(f"CAGR: {metrics_b['cagr']:.2%}")
|
||||
print(f"最大回撤: {metrics_b['max_dd']:.2%}")
|
||||
print(f"夏普比率: {metrics_b['sharpe']:.3f}")
|
||||
print(f"Calmar比率: {metrics_b['calmar']:.3f}")
|
||||
print(f"日胜率: {metrics_b['win_rate']:.2%}")
|
||||
print(f"累计收益: {metrics_b['total_return']:.2%}")
|
||||
print('='*70)
|
||||
|
||||
# 文档预期对比
|
||||
print('\n文档预期 (Mode B):')
|
||||
print(' CAGR: 28.07%, MaxDD -13.34%, Sharpe 1.685, Calmar 2.104')
|
||||
|
||||
cagr_diff = abs(metrics_b['cagr'] - 0.2807)
|
||||
print(f'\nCAGR差异: {cagr_diff:.2%}')
|
||||
|
||||
if cagr_diff < 0.05:
|
||||
print('✓ 结果与文档预期基本一致')
|
||||
else:
|
||||
print('⚠ 结果与文档预期有差异')
|
||||
|
||||
return metrics_b
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
477
archive/tests/verify_premium_calculation.py
Normal file
477
archive/tests/verify_premium_calculation.py
Normal file
@@ -0,0 +1,477 @@
|
||||
"""
|
||||
ETF溢价率计算验证脚本
|
||||
|
||||
验证当前代码是否能完美复现集思录的历史溢价率数据
|
||||
|
||||
使用方法:
|
||||
1. 设置 FLASK_API_URL 为 k3s 服务的地址
|
||||
2. 从集思录获取对照数据(手动或爬虫)
|
||||
3. 运行脚本对比结果
|
||||
|
||||
python tests/verify_premium_calculation.py --api-url http://your-k3s-service:5000
|
||||
"""
|
||||
|
||||
import requests
|
||||
import pandas as pd
|
||||
import argparse
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def fetch_api_premium(api_url: str, etf_code: str, start_date: str, end_date: str) -> pd.DataFrame:
|
||||
"""
|
||||
从 Flask API 获取ETF溢价率历史序列
|
||||
|
||||
使用 /api/v1/ohlcv 端点(该端点已包含价格、净值、溢价率)
|
||||
|
||||
Returns:
|
||||
DataFrame with columns: date, price, nav, nav_date, premium
|
||||
"""
|
||||
# 使用 ohlcv 端点(已包含溢价率)
|
||||
endpoint = f"{api_url}/api/v1/ohlcv"
|
||||
params = {
|
||||
'code': etf_code,
|
||||
'start': start_date,
|
||||
'end': end_date
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(endpoint, params=params, timeout=30)
|
||||
data = response.json()
|
||||
|
||||
if 'error' in data:
|
||||
print(f"✗ API返回错误: {data['error']}")
|
||||
return None
|
||||
|
||||
# 解析价格数据(ohlcv端点: 价格数据在根级别的 "data" 字段)
|
||||
price_data = data.get('data', [])
|
||||
price_df = pd.DataFrame(price_data)
|
||||
if len(price_df) > 0 and 'date' in price_df.columns:
|
||||
price_df['date'] = pd.to_datetime(price_df['date'])
|
||||
price_df = price_df.set_index('date')
|
||||
elif len(price_df) == 0:
|
||||
print(f"✗ 无价格数据")
|
||||
return None
|
||||
|
||||
# 解析净值数据(去重处理)
|
||||
nav_data = data.get('nav', {}).get('data', [])
|
||||
nav_df = pd.DataFrame(nav_data)
|
||||
if 'date' in nav_df.columns:
|
||||
nav_df['date'] = pd.to_datetime(nav_df['date'])
|
||||
nav_df = nav_df.set_index('date')
|
||||
# 去重(API返回有重复)
|
||||
if nav_df.index.has_duplicates:
|
||||
nav_df = nav_df[~nav_df.index.duplicated(keep='last')]
|
||||
|
||||
# 解析溢价率序列
|
||||
premium_data = data.get('premium_series', [])
|
||||
premium_df = pd.DataFrame(premium_data)
|
||||
if 'date' in premium_df.columns:
|
||||
premium_df['date'] = pd.to_datetime(premium_df['date'])
|
||||
premium_df = premium_df.set_index('date')
|
||||
|
||||
# 合并数据
|
||||
result = price_df[['close']].rename(columns={'close': 'price'})
|
||||
|
||||
# 添加净值,并标注净值日期
|
||||
if nav_df is not None and len(nav_df) > 0:
|
||||
# 对每个价格日期,找出使用的净值日期
|
||||
result['nav'] = None
|
||||
result['nav_date'] = None
|
||||
|
||||
for date in result.index:
|
||||
# 优先检查当天净值
|
||||
if date in nav_df.index:
|
||||
result.loc[date, 'nav'] = nav_df.loc[date, 'nav']
|
||||
result.loc[date, 'nav_date'] = date
|
||||
else:
|
||||
# 检查T-1净值
|
||||
t1_date = date - pd.Timedelta(days=1)
|
||||
if t1_date in nav_df.index:
|
||||
result.loc[date, 'nav'] = nav_df.loc[t1_date, 'nav']
|
||||
result.loc[date, 'nav_date'] = t1_date
|
||||
|
||||
# 添加溢价率
|
||||
if premium_df is not None and len(premium_df) > 0:
|
||||
result['premium_api'] = premium_df['premium']
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 获取数据失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def calculate_manual_premium(result_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
手动计算溢价率,验证API计算逻辑
|
||||
|
||||
溢价率 = (价格 - 净值) / 净值
|
||||
"""
|
||||
result_df['premium_manual'] = None
|
||||
|
||||
for date in result_df.index:
|
||||
price = result_df.loc[date, 'price']
|
||||
nav = result_df.loc[date, 'nav']
|
||||
|
||||
if pd.notna(price) and pd.notna(nav) and nav > 0:
|
||||
result_df.loc[date, 'premium_manual'] = (price - nav) / nav
|
||||
|
||||
return result_df
|
||||
|
||||
|
||||
def verify_single_etf(api_url: str, etf_code: str, days: int = 30):
|
||||
"""
|
||||
验证单个ETF的溢价率计算
|
||||
"""
|
||||
end_date = datetime.now().strftime('%Y-%m-%d')
|
||||
start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"验证ETF: {etf_code}")
|
||||
print(f"时间范围: {start_date} ~ {end_date}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# 获取API数据
|
||||
result = fetch_api_premium(api_url, etf_code, start_date, end_date)
|
||||
|
||||
if result is None or len(result) == 0:
|
||||
print("✗ 无法获取数据")
|
||||
return
|
||||
|
||||
# 手动计算溢价率
|
||||
result = calculate_manual_premium(result)
|
||||
|
||||
# 对比结果
|
||||
print("\n溢价率对比(最近10天):")
|
||||
print(f"{'日期':<12} {'价格':<8} {'净值':<8} {'净值日期':<12} {'API溢价率':<10} {'手动溢价率':<10} {'差异':<8}")
|
||||
print("-" * 70)
|
||||
|
||||
# 只显示最近10天
|
||||
recent = result.tail(10)
|
||||
|
||||
for date, row in recent.iterrows():
|
||||
date_str = date.strftime('%Y-%m-%d')
|
||||
price_str = f"{row['price']:.3f}" if pd.notna(row['price']) else "—"
|
||||
nav_str = f"{row['nav']:.4f}" if pd.notna(row['nav']) else "—"
|
||||
nav_date_str = row['nav_date'].strftime('%Y-%m-%d') if pd.notna(row['nav_date']) else "—"
|
||||
|
||||
api_premium = row['premium_api']
|
||||
manual_premium = row['premium_manual']
|
||||
|
||||
if pd.notna(api_premium) and pd.notna(manual_premium):
|
||||
api_str = f"{api_premium*100:.2f}%"
|
||||
manual_str = f"{manual_premium*100:.2f}%"
|
||||
diff = abs(api_premium - manual_premium)
|
||||
diff_str = f"{diff*100:.4f}%" if diff < 0.0001 else f"{diff*100:.2f}%"
|
||||
match = "✓" if diff < 0.0001 else "⚠"
|
||||
else:
|
||||
api_str = "—"
|
||||
manual_str = "—"
|
||||
diff_str = "—"
|
||||
match = "?"
|
||||
|
||||
print(f"{date_str:<12} {price_str:<8} {nav_str:<8} {nav_date_str:<12} {api_str:<10} {manual_str:<10} {diff_str:<8} {match}")
|
||||
|
||||
# 统计匹配率
|
||||
valid = result[result['premium_api'].notna() & result['premium_manual'].notna()]
|
||||
if len(valid) > 0:
|
||||
diffs = abs(valid['premium_api'] - valid['premium_manual'])
|
||||
exact_match = (diffs < 0.0001).sum()
|
||||
close_match = (diffs < 0.001).sum()
|
||||
|
||||
print(f"\n匹配统计:")
|
||||
print(f" 完全匹配(差异<0.0001): {exact_match}/{len(valid)} ({exact_match/len(valid)*100:.1f}%)")
|
||||
print(f" 接近匹配(差异<0.001): {close_match}/{len(valid)} ({close_match/len(valid)*100:.1f}%)")
|
||||
|
||||
if exact_match == len(valid):
|
||||
print(" ✓ API溢价率计算正确!")
|
||||
else:
|
||||
print(" ⚠ 存在计算差异,需要检查")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def verify_vs_jisilu(api_url: str, etf_code: str, jisilu_data: dict):
|
||||
"""
|
||||
与集思录数据对比验证
|
||||
|
||||
Args:
|
||||
jisilu_data: 集思录数据,格式如下:
|
||||
{
|
||||
'price_date': '2026-05-15',
|
||||
'price': 3.970,
|
||||
'nav_date': '2026-05-15', # 或 '2026-05-14' (T-1)
|
||||
'nav': 3.9402,
|
||||
'premium': 0.0076, # 溢价率(小数形式)
|
||||
}
|
||||
"""
|
||||
price_date = jisilu_data['price_date']
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"对比集思录数据: {etf_code} @ {price_date}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# 获取API数据(只取最近几天)
|
||||
start_date = (datetime.strptime(price_date, '%Y-%m-%d') - timedelta(days=5)).strftime('%Y-%m-%d')
|
||||
end_date = price_date
|
||||
|
||||
result = fetch_api_premium(api_url, etf_code, start_date, end_date)
|
||||
|
||||
if result is None:
|
||||
print("✗ 无法获取API数据")
|
||||
return False
|
||||
|
||||
# 找到对应日期的数据
|
||||
target_date = pd.to_datetime(price_date)
|
||||
if target_date not in result.index:
|
||||
print(f"✗ API数据中没有 {price_date}")
|
||||
return False
|
||||
|
||||
row = result.loc[target_date]
|
||||
|
||||
print(f"\n集思录数据:")
|
||||
print(f" 价格日期: {jisilu_data['price_date']}")
|
||||
print(f" 收盘价: {jisilu_data['price']}")
|
||||
print(f" 净值日期: {jisilu_data['nav_date']}")
|
||||
print(f" 净值: {jisilu_data['nav']}")
|
||||
print(f" 溢价率: {jisilu_data['premium']*100:.2f}%")
|
||||
|
||||
print(f"\nAPI数据:")
|
||||
print(f" 价格日期: {price_date}")
|
||||
print(f" 收盘价: {row['price']:.3f}")
|
||||
print(f" 净值日期: {row['nav_date'].strftime('%Y-%m-%d') if pd.notna(row['nav_date']) else '无'}")
|
||||
print(f" 净值: {row['nav']:.4f if pd.notna(row['nav']) else '无'}")
|
||||
print(f" 溢价率: {row['premium_api']*100:.2f}%")
|
||||
|
||||
# 对比
|
||||
print(f"\n对比结果:")
|
||||
|
||||
# 1. 价格对比
|
||||
price_diff = abs(row['price'] - jisilu_data['price'])
|
||||
price_match = price_diff < 0.01
|
||||
print(f" 价格差异: {price_diff:.3f} {'✓' if price_match else '⚠'}")
|
||||
|
||||
# 2. 净值日期对比(关键)
|
||||
api_nav_date = row['nav_date'].strftime('%Y-%m-%d') if pd.notna(row['nav_date']) else None
|
||||
nav_date_match = api_nav_date == jisilu_data['nav_date']
|
||||
print(f" 净值日期: API={api_nav_date}, 集思录={jisilu_data['nav_date']} {'✓' if nav_date_match else '⚠ 不匹配!'}")
|
||||
|
||||
# 3. 净值对比
|
||||
if pd.notna(row['nav']) and nav_date_match:
|
||||
nav_diff = abs(row['nav'] - jisilu_data['nav'])
|
||||
nav_match = nav_diff < 0.01
|
||||
print(f" 净值差异: {nav_diff:.4f} {'✓' if nav_match else '⚠'}")
|
||||
|
||||
# 4. 溢价率对比(核心)
|
||||
if pd.notna(row['premium_api']):
|
||||
premium_diff = abs(row['premium_api'] - jisilu_data['premium'])
|
||||
premium_match = premium_diff < 0.001
|
||||
print(f" 溢价率差异: {premium_diff*100:.2f}% {'✓' if premium_match else '⚠ 不匹配!'}")
|
||||
|
||||
if premium_match and nav_date_match:
|
||||
print(f"\n✓✓✓ 完全匹配!API溢价率计算正确!")
|
||||
return True
|
||||
else:
|
||||
print(f"\n⚠⚠⚠ 存在差异,需要排查")
|
||||
return False
|
||||
else:
|
||||
print(f" 溢价率: API无数据")
|
||||
return False
|
||||
|
||||
|
||||
# config.yaml 中所有ETF列表
|
||||
ALL_CONFIG_ETFS = [
|
||||
'159915.SZ', # 创业板ETF (A股)
|
||||
'512890.SH', # 红利低波ETF (A股)
|
||||
'513100.SH', # 纳指ETF (美股QDII)
|
||||
'513520.SH', # 日经ETF (日本QDII)
|
||||
'513030.SH', # 德国DAX ETF (欧洲QDII)
|
||||
'159920.SZ', # 恒生ETF (港股)
|
||||
'513130.SH', # 恒生科技ETF (港股)
|
||||
'518880.SH', # 黄金ETF (商品)
|
||||
'160723.SZ', # 原油ETF (商品QDII)
|
||||
'159980.SZ', # 有色ETF (商品)
|
||||
'511090.SH', # 国债ETF (债券)
|
||||
]
|
||||
|
||||
ETF_MARKET_MAP = {
|
||||
'159915.SZ': 'A',
|
||||
'512890.SH': 'A',
|
||||
'513100.SH': 'US', # 美股QDII - T-1净值规则
|
||||
'513520.SH': 'JP', # 日经QDII - 当天净值规则(华夏基金)
|
||||
'513030.SH': 'EU', # 欧洲QDII - T-1净值规则
|
||||
'159920.SZ': 'HK',
|
||||
'513130.SH': 'HK',
|
||||
'518880.SH': 'COMMODITY',
|
||||
'160723.SZ': 'COMMODITY', # 原油QDII - T-1净值规则
|
||||
'159980.SZ': 'COMMODITY',
|
||||
'511090.SH': 'BOND',
|
||||
}
|
||||
|
||||
# 集思录对照数据(需要手动更新最新数据)
|
||||
# 来源: https://www.jisilu.cn/data/etf/ 和 https://www.jisilu.cn/data/qdii/
|
||||
JISILU_REFERENCE_DATA = {
|
||||
'159915.SZ': { # 创业板ETF - 当天净值
|
||||
'price_date': '2026-05-15',
|
||||
'price': 3.970,
|
||||
'nav_date': '2026-05-15',
|
||||
'nav': 3.9402,
|
||||
'premium': 0.0076,
|
||||
},
|
||||
'513100.SH': { # 纳指ETF - T-1净值(美股QDII)
|
||||
'price_date': '2026-05-15',
|
||||
'price': 2.100,
|
||||
'nav_date': '2026-05-14',
|
||||
'nav': 2.0200,
|
||||
'premium': 0.0396,
|
||||
},
|
||||
'513520.SH': { # 日经ETF - 当天净值(华夏基金当天披露)
|
||||
'price_date': '2026-05-15',
|
||||
'price': 2.085,
|
||||
'nav_date': '2026-05-15',
|
||||
'nav': 2.0626,
|
||||
'premium': 0.0109,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def verify_all_etfs(api_url: str, days: int = 10):
|
||||
"""
|
||||
批量验证config.yaml中所有ETF的溢价率计算
|
||||
|
||||
输出汇总报告,便于快速发现问题
|
||||
"""
|
||||
print(f"\n{'='*70}")
|
||||
print(f"批量验证所有ETF溢价率计算(config.yaml)")
|
||||
print(f"API地址: {api_url}")
|
||||
print(f"{'='*70}")
|
||||
|
||||
end_date = datetime.now().strftime('%Y-%m-%d')
|
||||
start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
|
||||
|
||||
results = []
|
||||
|
||||
for etf_code in ALL_CONFIG_ETFS:
|
||||
market = ETF_MARKET_MAP.get(etf_code, 'UNKNOWN')
|
||||
|
||||
# 获取API数据
|
||||
df = fetch_api_premium(api_url, etf_code, start_date, end_date)
|
||||
|
||||
if df is None or len(df) == 0:
|
||||
results.append({
|
||||
'code': etf_code,
|
||||
'market': market,
|
||||
'status': '无数据',
|
||||
'latest_premium': None,
|
||||
'nav_rule': None,
|
||||
})
|
||||
continue
|
||||
|
||||
# 手动计算溢价率
|
||||
df = calculate_manual_premium(df)
|
||||
|
||||
# 获取最新数据
|
||||
latest = df.iloc[-1]
|
||||
latest_date = df.index[-1].strftime('%Y-%m-%d')
|
||||
|
||||
api_premium = latest.get('premium_api')
|
||||
manual_premium = latest.get('premium_manual')
|
||||
nav_date = latest.get('nav_date')
|
||||
|
||||
# 判断净值规则
|
||||
if pd.notna(nav_date):
|
||||
nav_date_str = nav_date.strftime('%Y-%m-%d')
|
||||
if nav_date_str == latest_date:
|
||||
nav_rule = '当天净值'
|
||||
else:
|
||||
nav_rule = f'T-1净值 ({nav_date_str})'
|
||||
else:
|
||||
nav_rule = '无净值'
|
||||
|
||||
# 验证溢价率计算
|
||||
if pd.notna(api_premium) and pd.notna(manual_premium):
|
||||
diff = abs(api_premium - manual_premium)
|
||||
if diff < 0.0001:
|
||||
status = '✓ 正确'
|
||||
elif diff < 0.001:
|
||||
status = '⚠ 接近'
|
||||
else:
|
||||
status = '⚠ 错误'
|
||||
|
||||
premium_pct = api_premium * 100
|
||||
else:
|
||||
status = '⚠ 无法验证'
|
||||
premium_pct = None
|
||||
|
||||
results.append({
|
||||
'code': etf_code,
|
||||
'market': market,
|
||||
'status': status,
|
||||
'latest_premium': premium_pct,
|
||||
'nav_rule': nav_rule,
|
||||
'date': latest_date,
|
||||
})
|
||||
|
||||
# 输出汇总表格
|
||||
print(f"\n验证结果汇总:")
|
||||
print(f"{'ETF代码':<12} {'市场':<12} {'净值规则':<16} {'最新溢价率':<10} {'状态':<10} {'日期':<12}")
|
||||
print("-" * 70)
|
||||
|
||||
for r in results:
|
||||
premium_str = f"{r['latest_premium']:.2f}%" if r['latest_premium'] else "—"
|
||||
date_str = r['date'] if r['date'] else "—"
|
||||
print(f"{r['code']:<12} {r['market']:<12} {r['nav_rule']:<16} {premium_str:<10} {r['status']:<10} {date_str:<12}")
|
||||
|
||||
# 统计
|
||||
correct_count = sum(1 for r in results if r['status'] == '✓ 正确')
|
||||
error_count = sum(1 for r in results if '错误' in r['status'] or '无法' in r['status'])
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f"统计: 正确={correct_count}, 错误={error_count}, 总数={len(results)}")
|
||||
|
||||
if error_count == 0:
|
||||
print(f"✓✓✓ 所有ETF溢价率计算验证通过!")
|
||||
else:
|
||||
print(f"⚠⚠⚠ 有 {error_count} 个ETF验证失败,需要检查")
|
||||
print(f"{'='*70}")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='验证ETF溢价率计算')
|
||||
parser.add_argument('--api-url', required=True, help='Flask API URL (k3s服务地址)')
|
||||
parser.add_argument('--etf', default='159915.SZ', help='ETF代码')
|
||||
parser.add_argument('--days', type=int, default=30, help='回看天数')
|
||||
parser.add_argument('--jisilu', action='store_true', help='使用集思录对照数据验证')
|
||||
parser.add_argument('--all', action='store_true', help='验证config.yaml中所有ETF')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.all:
|
||||
# 批量验证所有ETF
|
||||
verify_all_etfs(args.api_url, args.days)
|
||||
|
||||
elif args.jisilu:
|
||||
# 使用集思录对照数据批量验证
|
||||
print("\n批量验证集思录对照数据...")
|
||||
|
||||
all_match = True
|
||||
for etf_code, jisilu_data in JISILU_REFERENCE_DATA.items():
|
||||
match = verify_vs_jisilu(args.api_url, etf_code, jisilu_data)
|
||||
all_match = all_match and match
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
if all_match:
|
||||
print("✓✓✓ 所有ETF溢价率验证通过!API计算逻辑正确!")
|
||||
else:
|
||||
print("⚠⚠⚠ 部分ETF溢价率验证失败,需要检查代码")
|
||||
print(f"{'='*60}")
|
||||
|
||||
else:
|
||||
# 验证单个ETF
|
||||
verify_single_etf(args.api_url, args.etf, args.days)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user