因子挖掘+回测+RL交易:量化金融完整系统

因子挖掘+回测+RL交易:量化金融完整系统

一、引言

量化交易的核心公式:信号(Alpha因子) × 风险控制 × 执行效率 = 收益。传统多因子模型依赖人工特征,而机器学习(GBDT/RL)正在重塑这一领域。本文将实现一个完整的量化系统:因子挖掘→回测验证→RL交易策略。

二、Alpha因子挖掘

python 复制代码
import pandas as pd
import numpy as np
from scipy import stats

class AlphaFactory:
    """Alpha因子工厂"""
    
    def __init__(self, df):
        self.df = df  # OHLCV数据
        self.factors = pd.DataFrame(index=df.index)
    
    def generate_all(self):
        # === 动量因子 ===
        self.factors['momentum_5d'] = self.df['close'].pct_change(5)
        self.factors['momentum_20d'] = self.df['close'].pct_change(20)
        self.factors['rsi_14'] = self._rsi(14)
        
        # === 均值回归因子 ===
        ma20 = self.df['close'].rolling(20).mean()
        self.factors['mean_reversion'] = (ma20 - self.df['close']) / self.df['close'].rolling(20).std()
        
        # === 波动率因子 ===
        self.factors['volatility_20d'] = self.df['close'].pct_change().rolling(20).std()
        self.factors['volume_ratio'] = self.df['volume'] / self.df['volume'].rolling(20).mean()
        
        # === 技术指标 ===
        self.factors['macd'] = self._macd()
        self.factors['bb_width'] = self._bollinger_band_width()
        
        # === 资金流因子 ===
        self.factors['money_flow'] = ((self.df['close'] - self.df['low']) - 
                                       (self.df['high'] - self.df['close'])) / \
                                      (self.df['high'] - self.df['low']) * self.df['volume']
        
        # === 截面因子(横截面排名) ===
        self.factors['cross_sectional_rank'] = self.df['close'].pct_change(5).rank(pct=True)
        
        return self.factors
    
    def _rsi(self, period):
        delta = self.df['close'].diff()
        gain = delta.clip(lower=0).rolling(period).mean()
        loss = (-delta.clip(upper=0)).rolling(period).mean()
        rs = gain / loss
        return 100 - (100 / (1 + rs))
    
    def _macd(self):
        ema12 = self.df['close'].ewm(span=12).mean()
        ema26 = self.df['close'].ewm(span=26).mean()
        macd = ema12 - ema26
        signal = macd.ewm(span=9).mean()
        return macd - signal  # MACD柱
    
    def _bollinger_band_width(self):
        ma20 = self.df['close'].rolling(20).mean()
        std20 = self.df['close'].rolling(20).std()
        return 2 * std20 / ma20  # 带宽

# 因子IC分析(信息系数)
def evaluate_factors(factors, forward_returns, horizon=5):
    """计算每个因子对未来收益的预测能力"""
    forward_ret = forward_returns.shift(-horizon)
    
    ic_summary = {}
    for col in factors.columns:
        valid = factors[col].notna() & forward_ret.notna()
        ic, p_value = stats.spearmanr(factors[col][valid], forward_ret[valid])
        ic_summary[col] = {"IC": ic, "p_value": p_value, "valid_samples": valid.sum()}
    
    return pd.DataFrame(ic_summary).T.sort_values('IC', ascending=False)

三、Backtrader回测

python 复制代码
import backtrader as bt

class MLStrategy(bt.Strategy):
    """机器学习驱动的交易策略"""
    
    params = (
        ('risk_per_trade', 0.02),  # 每笔风险2%
        ('max_positions', 5),       # 最多5个持仓
    )
    
    def __init__(self):
        # ML模型(预训练的LightGBM)
        import joblib
        self.model = joblib.load('lgbm_model.pkl')
        self.orders = {}
    
    def next(self):
        # 1. 计算当前因子
        features = self._compute_features()
        
        # 2. ML预测未来收益
        predicted_return = self.model.predict(features.reshape(1, -1))[0]
        
        # 3. 交易信号
        current_positions = len(self.positions)
        
        if predicted_return > 0.005 and current_positions < self.p.max_positions:
            # 买入
            size = self.broker.getcash() * self.p.risk_per_trade / self.data.close[0]
            self.buy(size=size)
            print(f"BUY @ {self.data.close[0]:.2f}, Pred: {predicted_return:.4f}")
        
        elif predicted_return < -0.003:
            # 卖出
            self.close()
            print(f"SELL @ {self.data.close[0]:.2f}")
    
    def _compute_features(self):
        return np.array([
            self.data.close[0] / self.data.close[-5] - 1,  # 5日动量
            self.data.close[0] / self.data.close[-20] - 1,  # 20日动量
            self.data.volume[0] / self.data.volume.get(size=20).mean(),  # 量比
            # ... 更多因子
        ])

# 运行回测
cerebro = bt.Cerebro()
cerebro.addstrategy(MLStrategy)
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001)  # 0.1%手续费
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.02)
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')

results = cerebro.run()
strat = results[0]

print(f"Sharpe Ratio: {strat.analyzers.sharpe.get_analysis()['sharperatio']:.3f}")
print(f"Max Drawdown: {strat.analyzers.drawdown.get_analysis()['max']['drawdown']:.2f}%")
print(f"Annual Return: {strat.analyzers.returns.get_analysis()['rnorm100']:.2f}%")

cerebro.plot()

四、PPO强化学习交易

python 复制代码
import gymnasium as gym
from stable_baselines3 import PPO
import numpy as np

class TradingEnv(gym.Env):
    """交易环境"""
    
    def __init__(self, df, initial_balance=100000, window=60):
        super().__init__()
        self.df = df
        self.window = window
        
        # 观测: [price, volume, 技术指标...] * window
        self.observation_space = gym.spaces.Box(-np.inf, np.inf, (window * 10,))
        
        # 动作: [-1(满仓卖), -0.5, 0(持有), 0.5, 1(满仓买)]
        self.action_space = gym.spaces.Box(-1, 1, (1,))
        
        self.reset()
    
    def reset(self):
        self.current_step = self.window
        self.balance = self.initial_balance
        self.shares = 0
        self.portfolio_history = []
        return self._get_obs(), {}
    
    def step(self, action):
        action = action[0]  # 解包
        
        # 执行交易
        current_price = self.df.iloc[self.current_step]['close']
        trade_size = abs(action) * self.balance * 0.1  # 最多用10%余额
        
        if action > 0:
            # 买入
            shares_to_buy = trade_size / current_price
            cost = shares_to_buy * current_price * (1 + 0.001)  # 手续费
            if cost <= self.balance:
                self.shares += shares_to_buy
                self.balance -= cost
        
        elif action < 0:
            # 卖出
            shares_to_sell = min(self.shares, trade_size / current_price)
            revenue = shares_to_sell * current_price * (1 - 0.001)
            self.shares -= shares_to_sell
            self.balance += revenue
        
        self.current_step += 1
        
        # 新组合价值
        new_value = self.balance + self.shares * self.df.iloc[self.current_step]['close']
        
        # 奖励 = 收益 - 波动惩罚
        if len(self.portfolio_history) > 0:
            returns = new_value / self.portfolio_history[-1] - 1
            volatility = np.std([new_value / v - 1 for v in self.portfolio_history[-20:]]) if len(self.portfolio_history) >= 20 else 0
            reward = returns - 0.5 * volatility  # Sharpe-like reward
        else:
            reward = 0
        
        self.portfolio_history.append(new_value)
        
        done = self.current_step >= len(self.df) - 1
        
        return self._get_obs(), reward, done, False, {"portfolio_value": new_value}
    
    def _get_obs(self):
        window_data = self.df.iloc[self.current_step - self.window:self.current_step]
        
        obs = np.concatenate([
            (window_data['close'] / window_data['close'].iloc[-1] - 1).values,  # 归一化价格
            (window_data['volume'] / window_data['volume'].mean() - 1).values,  # 归一化量
            np.array([self.balance / self.initial_balance]),  # 余额占比
            np.array([self.shares * window_data['close'].iloc[-1] / self.initial_balance]),  # 持仓占比
        ])
        
        # padding到固定大小
        padded = np.zeros(self.observation_space.shape[0])
        padded[:len(obs)] = obs
        return padded

# 训练
env = TradingEnv(train_df)
model = PPO("MlpPolicy", env, n_steps=2048, learning_rate=3e-4, verbose=1)
model.learn(total_timesteps=200_000)
model.save("trading_ppo")

# 回测
env = TradingEnv(test_df)
obs, _ = env.reset()
while True:
    action, _ = model.predict(obs)
    obs, reward, done, _, info = env.step(action)
    if done:
        final_value = info['portfolio_value']
        print(f"Final: {final_value:.0f} (Return: {(final_value/100000-1)*100:.1f}%)")
        break

五、风险管理

python 复制代码
class RiskManager:
    def __init__(self, max_drawdown=0.15, var_limit=0.02, position_limit=0.2):
        self.max_dd = max_drawdown
        self.var_limit = var_limit
        self.position_limit = position_limit
    
    def check_order(self, order, portfolio):
        checks = []
        
        # 1. 最大回撤检查
        if portfolio.drawdown > self.max_dd:
            checks.append(("BLOCKED", f"Drawdown {portfolio.drawdown:.1%} > {self.max_dd:.1%}"))
            return 0
        
        # 2. VaR检查
        var = self._calculate_var(portfolio)
        if var > self.var_limit:
            order_size = order.size * (self.var_limit / var)
            checks.append(("REDUCED", f"VaR {var:.2%} → size {order_size:.1f}"))
        
        # 3. 仓位限制
        position_ratio = order.size * order.price / portfolio.total_value
        if position_ratio > self.position_limit:
            order_size = order.size * (self.position_limit / position_ratio)
            checks.append(("REDUCED", f"Position limit → {order_size:.1f}"))
        
        return order_size
    
    def _calculate_var(self, portfolio, confidence=0.95):
        returns = portfolio.daily_returns[-252:]
        var = np.percentile(returns, (1 - confidence) * 100)
        return abs(var)

六、总结

量化系统三大组件:

  1. 因子挖掘 → 动量/均值回归/波动率 → IC分析筛选
  2. 回测验证 → Backtrader → Sharpe/Drawdown评估
  3. RL交易 → PPO + 定制环境 → 自适应策略

关键警示:回测≠实盘!过拟合、滑点、流动性是量化最大的敌人。

相关推荐
梦想三三1 小时前
YOLOv5 口罩目标检测实战(一):项目整体介绍与数据准备
人工智能·yolo·目标检测
梵构广告2 小时前
平台怎么做品牌营销策划?—品牌营销
大数据·人工智能·平面·品牌策划
土豆12502 小时前
DeepSeek V4-Flash 正式版深度解读:一行 changelog 里的暗涌与野心
人工智能·llm
Aloudata2 小时前
Prompt 驱动分析 vs Skill 驱动分析:企业 AI 分析如何从会问走向可复用
大数据·人工智能·数据分析·prompt·skill·语义层
笨鸟先飞,勤能补拙2 小时前
下一个十年:网络安全正在被重写
网络·人工智能·安全·web安全·网络安全·github
weixin_429615992 小时前
快速生成流程图
人工智能·chatgpt·aigc·流程图
骇客野人2 小时前
PGSQL运维筛选近30天有数据改动数据库表和表名
人工智能
孪生质数-2 小时前
AI 应用实践篇——让大模型真正开始工作
linux·运维·服务器·人工智能·深度学习·语言模型·llama