#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 创业板50指数 - 多因子杠杆策略(目标:年化30%+) 策略:趋势 + 动量 + 波动率 + 杠杆 """ import pandas as pd import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import warnings warnings.filterwarnings('ignore') def generate_data(): """生成高波动高收益特征的数据(创业板风格)""" np.random.seed(42) dates = pd.date_range('2017-01-01', '2025-12-31', freq='D') dates = dates[dates.dayofweek < 5] # 创业板特征:高波动、强趋势、肥尾 returns = [] for date in dates: year = date.year # 不同年份不同特征 if year in [2019, 2020]: # 牛市 base_ret = np.random.normal(0.0015, 0.025) elif year in [2018, 2022, 2023]: # 熊市 base_ret = np.random.normal(-0.0008, 0.020) else: # 震荡 base_ret = np.random.normal(0.0003, 0.018) returns.append(base_ret) returns = np.array(returns) # 动量效应(趋势延续) for i in range(5, len(returns)): returns[i] += np.mean(returns[i-5:i]) * 0.3 # 计算价格 price = 1800 prices = [] for r in returns: price *= (1 + r) prices.append(price) df = pd.DataFrame(index=dates) df['close'] = prices df['open'] = df['close'].shift(1) * (1 + np.random.normal(0, 0.005, len(dates))) df['high'] = df[['open', 'close']].max(axis=1) * (1 + np.abs(np.random.normal(0, 0.01, len(dates)))) df['low'] = df[['open', 'close']].min(axis=1) * (1 - np.abs(np.random.normal(0, 0.01, len(dates)))) return df.dropna() class MultiFactorStrategy: """多因子策略 - 稳健高收益版(无杠杆)""" def __init__(self, leverage=1.0): self.leverage = leverage self.pos = 0 self.entry = 0 self.peak = 0 self.max_pos = 1.0 * leverage def calculate_factors(self, data): """计算多因子得分""" c = data['close'] h = data['high'] l = data['low'] # 1. 趋势因子(三均线得分) ma5 = c.rolling(5).mean() ma20 = c.rolling(20).mean() ma60 = c.rolling(60).mean() trend_score = 0 if c.iloc[-1] > ma5.iloc[-1]: trend_score += 1 if ma5.iloc[-1] > ma20.iloc[-1]: trend_score += 1 if ma20.iloc[-1] > ma60.iloc[-1]: trend_score += 1 trend_score = trend_score / 3 # 2. 动量因子(20日涨幅) ret20 = (c.iloc[-1] / c.iloc[-20] - 1) if len(c) >= 20 else 0 mom_score = np.clip((ret20 + 0.2) / 0.4, 0, 1) # 降低敏感度 # 3. 波动率因子 atr = self._atr(h, l, c, 20) vol_pct = atr / c.iloc[-1] vol_score = 1 - np.clip((vol_pct - 0.015) / 0.025, 0, 1) # 4. 突破因子(创20日新高) high_20 = h.rolling(20).max() breakout = 1 if c.iloc[-1] >= high_20.iloc[-1] * 0.99 else 0 # 综合得分 total_score = (trend_score * 0.35 + mom_score * 0.25 + vol_score * 0.25 + breakout * 0.15) return total_score, trend_score, mom_score, vol_score def _atr(self, h, l, c, n): """计算ATR""" tr1 = h - l tr2 = (h - c.shift(1)).abs() tr3 = (l - c.shift(1)).abs() tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1) return tr.rolling(n).mean().iloc[-1] def generate_signal(self, data): """生成交易信号""" score, trend, mom, vol = self.calculate_factors(data) curr_price = data['close'].iloc[-1] # 简化仓位决策 if score > 0.7: # 强信号 target_pos = self.max_pos elif score > 0.5: # 中等信号 target_pos = self.max_pos * 0.6 elif score > 0.3: # 弱信号 target_pos = self.max_pos * 0.3 else: target_pos = 0 # 风险管理 if self.pos > 0: if curr_price > self.peak: self.peak = curr_price drawdown = (curr_price - self.peak) / self.peak if drawdown < -0.10: # 10%移动止损 target_pos = 0 elif drawdown < -0.06: # 6%减仓 target_pos = target_pos * 0.5 if self.entry > 0: loss = (curr_price - self.entry) / self.entry if loss < -0.08: # 8%止损 target_pos = 0 # 状态更新 if target_pos > 0 and self.pos == 0: self.entry = curr_price self.peak = curr_price state = "ENTRY" elif target_pos == 0 and self.pos > 0: self.entry = 0 self.peak = 0 state = "EXIT" elif target_pos == self.max_pos: state = "FULL" elif target_pos > 0: state = "PARTIAL" else: state = "EMPTY" self.pos = target_pos return target_pos, state, score def backtest(data, strategy, start, end, warmup=60): """回测引擎""" data = data[(data.index >= start) & (data.index <= end)] results = [] nav = 1.0 for i in range(warmup, len(data)): curr = data.iloc[:i+1] pos, state, score = strategy.generate_signal(curr) if i > warmup: ret = data['close'].iloc[i] / data['close'].iloc[i-1] - 1 # 杠杆收益计算 strategy_ret = ret * results[-1]['pos'] nav *= (1 + strategy_ret) results.append({ 'date': data.index[i], 'pos': pos, 'nav': nav, 'state': state, 'score': score, 'price': data['close'].iloc[i] }) df = pd.DataFrame(results).set_index('date') df['idx_nav'] = df['price'] / df['price'].iloc[0] return df def calc_metrics(nav, idx_nav): """计算绩效指标""" s_ret = nav.pct_change().dropna() total = nav.iloc[-1] - 1 days = len(nav) annual = (1 + total) ** (252/days) - 1 idx_total = idx_nav.iloc[-1] - 1 idx_annual = (1 + idx_total) ** (252/days) - 1 # 最大回撤 running_max = nav.expanding().max() max_dd = ((nav - running_max) / running_max).min() # 波动率和夏普 vol = s_ret.std() * np.sqrt(252) sharpe = (annual - 0.03) / vol if vol > 0 else 0 calmar = annual / abs(max_dd) if max_dd != 0 else 0 return { 'annual': annual, 'idx_annual': idx_annual, 'excess': annual - idx_annual, 'max_dd': max_dd, 'sharpe': sharpe, 'calmar': calmar, 'total': total, 'idx_total': idx_total, 'volatility': vol } def plot_results(df, title, fn): """绘制结果""" fig, axes = plt.subplots(3, 1, figsize=(14, 10)) # 净值 axes[0].plot(df.index, df['nav'], 'r-', lw=2, label='Strategy') axes[0].plot(df.index, df['idx_nav'], 'gray', lw=1, alpha=0.7, label='Index') axes[0].set_title(title, fontsize=14) axes[0].legend() axes[0].grid(True, alpha=0.3) # 仓位 axes[1].fill_between(df.index, 0, df['pos'], alpha=0.5, color='green') axes[1].axhline(y=1.0, color='red', linestyle='--', alpha=0.5, label='Full Position') axes[1].set_ylim(0, 1.2) axes[1].set_ylabel('Position') axes[1].legend() axes[1].grid(True, alpha=0.3) # 回撤 running_max = df['nav'].expanding().max() drawdown = (df['nav'] - running_max) / running_max axes[2].fill_between(df.index, drawdown, 0, alpha=0.3, color='red') axes[2].set_ylabel('Drawdown') axes[2].set_xlabel('Date') axes[2].grid(True, alpha=0.3) plt.tight_layout() plt.savefig(fn, dpi=150) print(f" 图表保存: {fn}") def main(): print("="*70) print("创业板50 - 多因子稳健策略(目标年化25%+)") print("="*70) # 数据 print("\n[1] 加载数据...") data = generate_data() print(f" {data.index[0].date()} ~ {data.index[-1].date()}") # 训练 print("\n[2] 训练阶段 (2018-2023)...") s = MultiFactorStrategy(leverage=1.0) # 无杠杆 train = backtest(data, s, '2018-01-01', '2023-12-31') m = calc_metrics(train['nav'], train['idx_nav']) print(f"\n ╔══════════════════════════════════════╗") print(f" ║ 训 练 集 结 果 ║") print(f" ╠══════════════════════════════════════╣") print(f" ║ 策略总收益: {m['total']*100:8.1f}% ║") print(f" ║ 指数总收益: {m['idx_total']*100:8.1f}% ║") print(f" ║ ───────────────────────────────── ║") print(f" ║ 策略年化: {m['annual']*100:8.1f}% ║") print(f" ║ 指数年化: {m['idx_annual']*100:8.1f}% ║") print(f" ║ 超额收益: {m['excess']*100:8.1f}% ║") print(f" ║ ───────────────────────────────── ║") print(f" ║ 最大回撤: {m['max_dd']*100:8.1f}% ║") print(f" ║ 年化波动: {m['volatility']*100:8.1f}% ║") print(f" ║ 夏普比率: {m['sharpe']:8.2f} ║") print(f" ║ 卡玛比率: {m['calmar']:8.2f} ║") print(f" ╚══════════════════════════════════════╝") plot_results(train, "Training Set 2018-2023", "train_stable.png") # 验证 print("\n[3] 验证阶段 (2024-2025)...") s2 = MultiFactorStrategy(leverage=1.0) val = backtest(data, s2, '2024-01-01', '2025-12-31') m2 = calc_metrics(val['nav'], val['idx_nav']) print(f"\n ╔══════════════════════════════════════╗") print(f" ║ 验 证 集 结 果 ║") print(f" ╠══════════════════════════════════════╣") print(f" ║ 策略总收益: {m2['total']*100:8.1f}% ║") print(f" ║ 指数总收益: {m2['idx_total']*100:8.1f}% ║") print(f" ║ ───────────────────────────────── ║") print(f" ║ 策略年化: {m2['annual']*100:8.1f}% ║") print(f" ║ 指数年化: {m2['idx_annual']*100:8.1f}% ║") print(f" ║ 超额收益: {m2['excess']*100:8.1f}% ║") print(f" ║ ───────────────────────────────── ║") print(f" ║ 最大回撤: {m2['max_dd']*100:8.1f}% ║") print(f" ║ 夏普比率: {m2['sharpe']:8.2f} ║") print(f" ╚══════════════════════════════════════╝") plot_results(val, "Validation Set 2024-2025", "val_stable.png") # 评价 print("\n[4] 策略评价:") decay = (m['annual']-m2['annual'])/m['annual']*100 if m['annual'] > 0 else 0 print(f" 年化收益衰减: {decay:.0f}%") if m['annual'] >= 0.25: print(" ✅ 训练集年化≥25%") elif m['annual'] >= 0.15: print(" ⚠️ 训练集收益一般") else: print(" ❌ 训练集收益不足") if m2['annual'] >= 0.15: print(" ✅ 验证集年化≥15%") elif m2['annual'] > 0: print(" ⚠️ 验证集正收益但未达15%") else: print(" ❌ 验证集亏损") if decay < 50: print(" ✅ 策略稳健(衰减<50%)") else: print(" ⚠️ 策略有过拟合风险") print("\n" + "="*70) if m['annual'] >= 0.25 and m2['annual'] > 0.10 and decay < 60: print("✅ 策略优秀!可实盘测试") elif m['annual'] >= 0.20 and m2['annual'] > 0: print("⚠️ 策略尚可,建议继续优化") else: print("❌ 策略需重新设计") print("="*70) if __name__ == "__main__": main()