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:
2026-06-03 23:41:46 +08:00
parent d700bc1dfd
commit c905230a40
98 changed files with 0 additions and 714 deletions

View File

@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""V1 vs V2简单版 vs V2正式版 三版本回测结果对比"""
import pandas as pd
import numpy as np
from datetime import datetime
print("=" * 80)
print("V1 vs V2简单版 vs V2正式版 三版本回测对比报告")
print("=" * 80)
print(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print()
# 读取三个版本的结果
versions = {
'V1 (原始框架)': {
'file': 'results/v1_comparison_2020_2026_nav.csv',
'date_col': 'cal_date',
'nav_col': '策略净值'
},
'V2简单版': {
'file': 'framework_v2/results/simple_rotation_equity.csv',
'date_col': 'date',
'nav_col': '0' # 第二列名是 '0'
},
'V2正式版': {
'file': 'framework_v2/results/global_rotation_equity.csv',
'date_col': 'date',
'nav_col': '0' # 第二列名是 '0'
}
}
results = {}
for version_name, config in versions.items():
print(f"{version_name}")
print("-" * 80)
nav = pd.read_csv(config['file'])
nav[config['date_col']] = pd.to_datetime(nav[config['date_col']])
nav = nav.set_index(config['date_col'])
start_nav = nav.iloc[0][config['nav_col']]
end_nav = nav.iloc[-1][config['nav_col']]
total_days = len(nav)
years = total_days / 252
total_return = (end_nav - start_nav) / start_nav * 100
annual_return = ((end_nav / start_nav) ** (1/years) - 1) * 100
# 计算最大回撤
cummax = nav[config['nav_col']].cummax()
drawdown = (nav[config['nav_col']] - cummax) / cummax
max_drawdown = drawdown.min() * 100
# 计算夏普比率
daily_returns = nav[config['nav_col']].pct_change().dropna()
sharpe = daily_returns.mean() / daily_returns.std() * np.sqrt(252)
results[version_name] = {
'start_date': nav.index[0],
'end_date': nav.index[-1],
'total_days': total_days,
'start_nav': start_nav,
'end_nav': end_nav,
'total_return': total_return,
'annual_return': annual_return,
'max_drawdown': max_drawdown,
'sharpe': sharpe
}
print(f"回测区间: {nav.index[0].strftime('%Y-%m-%d')} ~ {nav.index[-1].strftime('%Y-%m-%d')}")
print(f"交易天数: {total_days}")
print(f"起始净值: {start_nav:.4f}")
print(f"结束净值: {end_nav:.4f}")
print(f"总收益: {total_return:.2f}%")
print(f"年化收益: {annual_return:.2f}%")
print(f"最大回撤: {max_drawdown:.2f}%")
print(f"夏普比率: {sharpe:.2f}")
print()
# 对比分析
print("=" * 80)
print("【三版本对比分析】")
print("=" * 80)
header = f"{'指标':<15}"
for version_name in versions.keys():
header += f" {version_name:>20}"
print(header)
print("-" * 80)
# 回测区间
row = f"{'回测区间':<15}"
for version_name in versions.keys():
r = results[version_name]
date_str = f"{r['start_date'].strftime('%Y-%m')}~{r['end_date'].strftime('%Y-%m')}"
row += f" {date_str:>20}"
print(row)
# 交易天数
row = f"{'交易天数':<15}"
for version_name in versions.keys():
row += f" {results[version_name]['total_days']:>20}"
print(row)
# 起始净值
row = f"{'起始净值':<15}"
for version_name in versions.keys():
row += f" {results[version_name]['start_nav']:>20.4f}"
print(row)
# 结束净值
row = f"{'结束净值':<15}"
for version_name in versions.keys():
row += f" {results[version_name]['end_nav']:>20.4f}"
print(row)
# 总收益
row = f"{'总收益':<15}"
for version_name in versions.keys():
row += f" {results[version_name]['total_return']:>19.2f}%"
print(row)
# 年化收益
row = f"{'年化收益':<15}"
for version_name in versions.keys():
row += f" {results[version_name]['annual_return']:>19.2f}%"
print(row)
# 最大回撤
row = f"{'最大回撤':<15}"
for version_name in versions.keys():
row += f" {results[version_name]['max_drawdown']:>19.2f}%"
print(row)
# 夏普比率
row = f"{'夏普比率':<15}"
for version_name in versions.keys():
row += f" {results[version_name]['sharpe']:>20.2f}"
print(row)
print()
# 差异分析
print("=" * 80)
print("【关键差异分析】")
print("=" * 80)
v1_return = results['V1 (原始框架)']['total_return']
v2_simple_return = results['V2简单版']['total_return']
v2_full_return = results['V2正式版']['total_return']
print(f"""
V1 vs V2简单版
- 收益差异: {v2_simple_return - v1_return:+.2f}%
- V2简单版缺少交易成本、调仓控制、溢价过滤、动态阈值
- V2简单版优势信号-交易分离更清晰
V1 vs V2正式版
- 收益差异: {v2_full_return - v1_return:+.2f}%
- V2正式版已实现交易成本(0.1%)、动态短债阈值、溢价过滤、调仓控制
- V2正式版调仓次数: 829 次vs V1 的 404 次)
- 差异来源:调仓频率不同、实现细节差异
V2简单版 vs V2正式版
- 收益差异: {v2_full_return - v2_simple_return:+.2f}%
- 正式版增加了交易成本(-829 * 0.1% ≈ -82.9%
- 正式版增加了动态阈值(更保守)
- 正式版增加了溢价过滤(避免高溢价)
""")
print("=" * 80)
print("【结论】")
print("=" * 80)
print("""
1. V2 简单版981.95%):未计入交易成本,每日调仓,收益虚高
2. V2 正式版135.63%):已计入交易成本,收益更接近真实
3. V1 原始版103.29%):最保守,调仓次数最少
V2 正式版与 V1 的差异(+32.34%)主要来自:
- 调仓频率更高829 vs 404 次)
- 实现细节差异(信号生成、溢价过滤等)
- 数据获取方式差异
V2 正式版已经是一个可用的生产版本!
""")
print("=" * 80)

View File

@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""V1 vs V2 回测结果对比"""
import pandas as pd
import numpy as np
from datetime import datetime
print("=" * 80)
print("V1 vs V2 回测结果对比报告")
print("=" * 80)
print(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print()
# V1 结果
print("【V1 回测结果】(原始框架)")
print("-" * 80)
v1_nav = pd.read_csv('results/v1_comparison_2020_2026_nav.csv')
v1_nav['cal_date'] = pd.to_datetime(v1_nav['cal_date'])
v1_nav = v1_nav.set_index('cal_date')
start_nav = v1_nav.iloc[0]['策略净值']
end_nav = v1_nav.iloc[-1]['策略净值']
total_days = len(v1_nav)
years = total_days / 252
total_return = (end_nav - start_nav) / start_nav * 100
annual_return = ((end_nav / start_nav) ** (1/years) - 1) * 100
# 计算最大回撤
cummax = v1_nav['策略净值'].cummax()
drawdown = (v1_nav['策略净值'] - cummax) / cummax
max_drawdown = drawdown.min() * 100
# 计算夏普比率
daily_returns = v1_nav['策略净值'].pct_change().dropna()
sharpe = daily_returns.mean() / daily_returns.std() * np.sqrt(252)
print(f"回测区间: {v1_nav.index[0].strftime('%Y-%m-%d')} ~ {v1_nav.index[-1].strftime('%Y-%m-%d')}")
print(f"交易天数: {total_days}")
print(f"起始净值: {start_nav:.4f}")
print(f"结束净值: {end_nav:.4f}")
print(f"总收益: {total_return:.2f}%")
print(f"年化收益: {annual_return:.2f}%")
print(f"最大回撤: {max_drawdown:.2f}%")
print(f"夏普比率: {sharpe:.2f}")
print()
# V2 结果
print("【V2 回测结果】(framework_v2)")
print("-" * 80)
v2_nav = pd.read_csv('framework_v2/results/simple_rotation_equity.csv')
v2_nav['date'] = pd.to_datetime(v2_nav['date'])
v2_nav = v2_nav.set_index('date')
# V2 的第二列名是 '0'(需要确认)
v2_equity_col = v2_nav.columns[0] # 获取第一列
start_nav_v2 = v2_nav.iloc[0][v2_equity_col]
end_nav_v2 = v2_nav.iloc[-1][v2_equity_col]
total_days_v2 = len(v2_nav)
years_v2 = total_days_v2 / 252
total_return_v2 = (end_nav_v2 - start_nav_v2) / start_nav_v2 * 100
annual_return_v2 = ((end_nav_v2 / start_nav_v2) ** (1/years_v2) - 1) * 100
cummax_v2 = v2_nav[v2_equity_col].cummax()
drawdown_v2 = (v2_nav[v2_equity_col] - cummax_v2) / cummax_v2
max_drawdown_v2 = drawdown_v2.min() * 100
daily_returns_v2 = v2_nav[v2_equity_col].pct_change().dropna()
sharpe_v2 = daily_returns_v2.mean() / daily_returns_v2.std() * np.sqrt(252)
print(f"回测区间: {v2_nav.index[0].strftime('%Y-%m-%d')} ~ {v2_nav.index[-1].strftime('%Y-%m-%d')}")
print(f"交易天数: {total_days_v2}")
print(f"起始净值: {start_nav_v2:.4f}")
print(f"结束净值: {end_nav_v2:.4f}")
print(f"总收益: {total_return_v2:.2f}%")
print(f"年化收益: {annual_return_v2:.2f}%")
print(f"最大回撤: {max_drawdown_v2:.2f}%")
print(f"夏普比率: {sharpe_v2:.2f}")
print()
# 对比分析
print("=" * 80)
print("【对比分析】")
print("=" * 80)
print(f"{'指标':<15} {'V1':>15} {'V2':>15} {'差异':>15}")
print("-" * 80)
start_str = f"{v1_nav.index[0].strftime('%Y-%m')}~{v1_nav.index[-1].strftime('%Y-%m')}"
end_str = f"{v2_nav.index[0].strftime('%Y-%m')}~{v2_nav.index[-1].strftime('%Y-%m')}"
print(f"{'回测区间':<15} {start_str:>15} {end_str:>15} {'':>15}")
print(f"{'交易天数':<15} {total_days:>15} {total_days_v2:>15} {total_days_v2 - total_days:>+15}")
print(f"{'起始净值':<15} {start_nav:>15.4f} {start_nav_v2:>15.4f} {start_nav_v2 - start_nav:>+15.4f}")
print(f"{'结束净值':<15} {end_nav:>15.4f} {end_nav_v2:>15.4f} {end_nav_v2 - end_nav:>+15.4f}")
print(f"{'总收益':<15} {total_return:>14.2f}% {total_return_v2:>14.2f}% {total_return_v2 - total_return:>+14.2f}%")
print(f"{'年化收益':<15} {annual_return:>14.2f}% {annual_return_v2:>14.2f}% {annual_return_v2 - annual_return:>+14.2f}%")
print(f"{'最大回撤':<15} {max_drawdown:>14.2f}% {max_drawdown_v2:>14.2f}% {max_drawdown_v2 - max_drawdown:>+14.2f}%")
print(f"{'夏普比率':<15} {sharpe:>15.2f} {sharpe_v2:>15.2f} {sharpe_v2 - sharpe:>+15.2f}")
print()
# 收益差异分析
print("=" * 80)
print("【收益差异分析】")
print("=" * 80)
diff = total_return_v2 - total_return
if diff > 0:
print(f"V2 比 V1 多赚 {diff:.2f}%")
print(f"如果初始资金 100 万V2 多赚 {1000000 * diff / 100:,.0f}")
else:
print(f"V2 比 V1 少赚 {abs(diff):.2f}%")
print(f"如果初始资金 100 万V2 少赚 {1000000 * abs(diff) / 100:,.0f}")
print()
# 关键差异原因分析
print("=" * 80)
print("【关键差异原因分析】")
print("=" * 80)
print("""
✅ 已修复的问题:
1. ✓ 交易日过滤V2 现在只保留 A 股交易日1539 天)
2. ✓ 起始日期对齐V2 从 2020-01-10 开始(与 V1 一致)
⚠️ 仍然存在的核心差异(导致 V2 收益 +878%
1. 调仓逻辑差异(最关键):
- V1: 完整的调仓控制
* rebalance_days: 1 (最低持仓1天)
* rebalance_threshold: 0.0 (新组合需超过当前组合0%才调仓)
* 实际效果:减少不必要的调仓
- V2: 简化版(每日调仓,无阈值)
* 每天都根据信号重新选股
* 频繁调仓可能导致更高的收益(但也可能增加交易成本)
2. 交易成本:
- V1: trade_cost: 0.001 (0.1%)
* 每次调仓扣除 0.1% 成本
* 404 次调仓 * 0.1% = 约 40.4% 的累计成本
- V2: 未计入交易成本
* 这是收益差异的重要来源
3. 溢价控制:
- V1: 启用溢价过滤premium_control.enabled: true
* 默认阈值: 10%
* 过滤高溢价 ETF避免买入亏损
* 可能错过一些机会,但降低风险
- V2: 未实现溢价控制
* 可以买入任何 ETF包括高溢价的
4. 动态阈值:
- V1: bond_threshold 启用
* 标的动量 < 短债动量 → 不持有
* 更保守的策略,避免负动量资产
- V2: 使用 fixed_value: 0.0
* 只过滤负动量,不如 V1 严格
5. 数据获取方式:
- V1: 使用 ETF 净值数据etf_nav_data
* 更准确的实际交易价格
- V2: 使用 trade_source 指定的 ETF/指数收盘价
* 可能存在差异(特别是跨境 ETF
6. 信号-交易分离实现:
- V1: 通过 etf 字段映射
- V2: 通过 signal_source/trade_source 显式字段
- 理论上应该一致,但实现细节可能不同
📊 收益差异量化分析:
V2 收益 981.95% - V1 收益 103.29% = 878.66% 差异
可能来源:
1. 交易成本缺失:约 -40%V1 有V2 无)
2. 频繁调仓:可能 +200~300%V2 每日调仓 vs V1 有阈值)
3. 溢价控制缺失:可能 +50~100%V2 可买高溢价 ETF
4. 动态阈值差异:可能 +100~200%V2 更激进)
5. 其他实现细节:约 +200~300%
⚠️ 结论:
V2 的超高收益主要来自:
1. 未计入交易成本(虚增约 40%
2. 每日调仓 vs 有阈值的调仓(显著差异)
3. 缺少溢价控制和动态阈值(更激进)
要进行完全公平的对比,需要在 V2 中实现:
1. ✓ 交易成本计算
2. ✓ 调仓阈值控制
3. ✓ 溢价过滤
4. ✓ 动态短债阈值
""")
print("=" * 80)

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""
ETF轮动策略回测入口
用法:
python run_rotation.py
python run_rotation.py --config strategies/rotation/config.yaml
python run_rotation.py --save-path results/my_rotation
"""
import argparse
import time
from datetime import datetime
from strategies.rotation.strategy import RotationStrategy
def main():
parser = argparse.ArgumentParser(description="ETF轮动策略回测")
parser.add_argument(
"--config",
type=str,
default="strategies/rotation/config.yaml",
help="配置文件路径",
)
parser.add_argument(
"--save-path",
type=str,
default="results/rotation",
help="报告保存路径前缀",
)
args = parser.parse_args()
start_time = time.time()
# 从配置创建策略
strategy = RotationStrategy.from_yaml(args.config)
# 运行回测
result = strategy.run_backtest(save_path=args.save_path)
elapsed = time.time() - start_time
print(f"\n总耗时: {elapsed:.1f}")
return result
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""
美股动量轮动策略回测入口
运行方式:
python run_us_rotation.py
python run_us_rotation.py --save results/us_rotation
"""
import argparse
import time
from pathlib import Path
from strategies.us_rotation.strategy import USRotationStrategy
def main():
parser = argparse.ArgumentParser(description='美股动量轮动策略回测')
parser.add_argument(
'--config',
type=str,
default='strategies/us_rotation/config.yaml',
help='配置文件路径'
)
parser.add_argument(
'--save',
type=str,
default='results/us_rotation',
help='报告保存路径前缀'
)
args = parser.parse_args()
start_time = time.time()
print("=" * 60)
print("美股动量轮动策略")
print("=" * 60)
print(f"配置文件: {args.config}")
print(f"保存路径: {args.save}")
# 创建策略实例
strategy = USRotationStrategy.from_yaml(args.config)
# 运行回测
result = strategy.run_backtest(save_path=args.save)
elapsed = time.time() - start_time
print(f"\n总耗时: {elapsed:.1f}")
return result
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,25 @@
"""测试 Flask API 日期参数"""
import os
from datasource.flask_api_source import FlaskAPIDataSource
# 设置环境变量
os.environ['FLASK_API_URL'] = 'https://k3s.tokenpluse.xyz'
api = FlaskAPIDataSource()
# 测试获取 2020-2024 年的数据
print("测试 1: 获取 2020-2024 年数据")
df = api.fetch("399006.SZ", "2020-01-01", "2024-12-31")
if df is not None:
print(f" 数据量: {len(df)}")
print(f" 日期范围: {df.index[0]} ~ {df.index[-1]}")
else:
print(" 获取失败")
print("\n测试 2: 获取 2023-2024 年数据")
df2 = api.fetch("399006.SZ", "2023-01-01", "2024-12-31")
if df2 is not None:
print(f" 数据量: {len(df2)}")
print(f" 日期范围: {df2.index[0]} ~ {df2.index[-1]}")
else:
print(" 获取失败")