experiment(rotation): 同大类扩充与纳指vs标普替换对比实验
技术修复: - SOCKS5代理IPv6问题:socks5:// → socks5h:// (hybrid_source.py, yfinance_source.py) 目录整理: - scripts/ → 仅保留策略入口(daily_scheduler, run_rotation, run_cci_screener) - 实验脚本移至 tests/experiments/ - 工具脚本移至 tests/utils/ - 实验记录新增 docs/experiments/ - results/ 添加到 gitignore 实验结果: 实验001 - 同大类扩充(添加标普500): ├─ 累计收益: 1467.35% → 1176.26% (-291%) ├─ CAGR: 48.10% → 43.82% (-4.28%) ├─ 调仓次数: 459 → 501 (+42次) └─ 结论: 添加同大类标的不增加跨类分散,反而侵蚀收益 实验002 - 纳指vs标普替换对比: ├─ 累计收益: 1467.35% → 1118.77% (-348%) ├─ CAGR: 48.10% → 42.87% (-5.22%) ├─ Sharpe: 2.21 → 2.08 (-0.13) ├─ MaxDD: -17.33% → -15.14% (+2.18%) └─ 结论: 纳指100优于标普500,成长风格更适合动量策略 策略建议: - 保持纳指100作为美股大类代表 - 不添加同大类新标的(避免类内切换成本) - 新增标的应优先考虑新大类(增加跨类分散)
This commit is contained in:
115
tests/experiments/analyze_negative_scores.py
Normal file
115
tests/experiments/analyze_negative_scores.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
分析历史 Top 3 标的中存在负分的情况 (正式版)
|
||||
"""
|
||||
import sys
|
||||
import yaml
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
# 添加项目根目录
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from strategies.rotation.engine import RotationStrategy
|
||||
from core.factors.momentum import compute_factors
|
||||
|
||||
def load_config(config_path: str) -> dict:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def analyze_negative_scores():
|
||||
config_path = "config/strategies/rotation.yaml"
|
||||
config = load_config(config_path)
|
||||
|
||||
# 强制不使用过滤,以获取完整数据
|
||||
config['diversified'] = True
|
||||
config['select_num'] = 3
|
||||
|
||||
strategy = RotationStrategy(config)
|
||||
|
||||
# 使用策略内部方法获取数据
|
||||
with strategy.data_source:
|
||||
index_data, etf_data, etf_nav_data, benchmark_data, valid_codes, index_ohlcv_data = strategy.data_source.fetch_all(
|
||||
config['code_list'],
|
||||
config['benchmark']['code'],
|
||||
config["start_date"],
|
||||
datetime.now().strftime('%Y-%m-%d')
|
||||
)
|
||||
|
||||
# 手动计算因子 (不带过滤)
|
||||
# 注意:为了分析原始得分,我们将 compute_factors 内部调用的过滤函数暂时跳过或分析结果
|
||||
factor_data, valid_codes = compute_factors(
|
||||
index_data,
|
||||
valid_codes,
|
||||
n=config["n_days"],
|
||||
factor_type=config["factor_type"],
|
||||
auto_day=config.get("auto_day", False),
|
||||
index_ohlcv_data=index_ohlcv_data
|
||||
)
|
||||
|
||||
score_cols = [c for c in factor_data.columns if c.startswith("得分_")]
|
||||
code_config = config['code_list']
|
||||
|
||||
total_days = len(factor_data)
|
||||
results = []
|
||||
|
||||
last_top_3 = set()
|
||||
rebalance_count = 0
|
||||
|
||||
for date, row in factor_data.iterrows():
|
||||
scores = row[score_cols].dropna()
|
||||
if scores.empty: continue
|
||||
|
||||
# 模拟 diversified 逻辑下的 Top 3 (不带 >0 过滤)
|
||||
cat_best = {}
|
||||
for col_name, s in scores.items():
|
||||
code = col_name.replace("得分_", "")
|
||||
cat = code_config.get(code, {}).get("market", "未知")
|
||||
if cat not in cat_best or s > cat_best[cat][1]:
|
||||
cat_best[cat] = (code, s)
|
||||
|
||||
sorted_cats = sorted(cat_best.values(), key=lambda x: x[1], reverse=True)
|
||||
top_3_raw = sorted_cats[:3]
|
||||
current_top_3_codes = set(code for code, s in top_3_raw)
|
||||
|
||||
# 判断是否发生调仓(目标持仓集合发生变化)
|
||||
if current_top_3_codes != last_top_3:
|
||||
rebalance_count += 1
|
||||
# 统计调仓日这 3 只中得分 <= 0 的数量
|
||||
neg_count = sum(1 for code, s in top_3_raw if s <= 0)
|
||||
|
||||
results.append({
|
||||
"date": date,
|
||||
"neg_count": neg_count,
|
||||
"top_1_score": top_3_raw[0][1],
|
||||
"top_2_score": top_3_raw[1][1] if len(top_3_raw)>1 else np.nan,
|
||||
"top_3_score": top_3_raw[2][1] if len(top_3_raw)>2 else np.nan,
|
||||
"top_1_name": code_config.get(top_3_raw[0][0], {}).get('name')
|
||||
})
|
||||
last_top_3 = current_top_3_codes
|
||||
|
||||
neg_df = pd.DataFrame(results)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"调仓日 (Rebalance Day) Top 3 标的出现负分情况分析")
|
||||
print(f"{'='*60}")
|
||||
print(f"总调仓次数: {rebalance_count}")
|
||||
print(f"涉及负分(<=0)的调仓次数: {len(neg_df[neg_df['neg_count']>0])} ({len(neg_df[neg_df['neg_count']>0])/rebalance_count:.1%})")
|
||||
|
||||
if not neg_df.empty:
|
||||
print(f"\n调仓日负分详细分布:")
|
||||
print(f" - 只有 1 只标的为负: {len(neg_df[neg_df['neg_count']==1])} 次")
|
||||
print(f" - 有 2 只标的为负: {len(neg_df[neg_df['neg_count']==2])} 次")
|
||||
print(f" - 全部 3 只标的均为负: {len(neg_df[neg_df['neg_count']==3])} 次")
|
||||
|
||||
print(f"\n最近 10 次涉及负分的调仓详情:")
|
||||
neg_df['date'] = pd.to_datetime(neg_df['date'])
|
||||
print(neg_df[neg_df['neg_count']>0][['date', 'neg_count', 'top_1_score', 'top_1_name']].tail(10))
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_negative_scores()
|
||||
Reference in New Issue
Block a user