
当深度学习遇上金融时序,会碰撞出怎样的火花?本文带你从零构建一套完整的AI量化交易系统,涵盖数据清洗、特征工程、LSTM预测、强化学习交易及回测评估,并深入探讨实战中的“坑”与对策。
传统量化策略依赖人工设计的因子(如均线、MACD、RSI等)和线性模型,但金融市场具有高噪声、非平稳、非线性等特点,简单规则往往难以捕捉复杂模式。人工智能,尤其是深度学习,能够自动从海量数据中提取高阶特征,并适应市场结构的变化。
然而,AI量化绝不是“调参即可印钞”,它面临过拟合、生存偏差、交易成本等严峻挑战。本文力求客观,既展示技术路径,也揭示风险,希望对有志于AI量化的开发者提供一份可落地的参考。
一个完整的AI量化系统包含四个层次:
层次 | 内容 | 技术手段 |
|---|---|---|
数据层 | 行情、财报、舆情、另类数据 | 爬虫、API、数据仓库 |
特征层 | 因子构建、降维、归一化 | PCA、标准化、时序特征提取 |
模型层 | 预测或决策模型 | XGBoost、LSTM、Transformer、DQN |
执行层 | 信号生成、风险控制、下单 | 回测引擎、实盘接口、止损止盈 |
本文聚焦于模型层与执行层,以股票日频数据为例,完整走一遍流程。
我们使用akshare(免费)获取A股历史数据,当然也可用yfinance。以贵州茅台(600519)为例。
import akshare as ak
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
import warnings
warnings.filterwarnings('ignore')
# 获取股票日线数据
stock_code = "600519"
df = ak.stock_zh_a_hist(symbol=stock_code, period="daily", start_date="2015-01-01", end_date="2025-01-01", adjust="qfq")
df.columns = ['date', 'open', 'close', 'high', 'low', 'volume', 'amount', 'amplitude', 'pct_change', 'change', 'turnover']
df['date'] = pd.to_datetime(df['date'])
df.sort_values('date', inplace=True)
df.reset_index(drop=True, inplace=True)
# 检查缺失
print(df.isnull().sum())
# 使用前复权价格,已包含分红除权调整
# 可视化
plt.figure(figsize=(12,5))
plt.plot(df['date'], df['close'], label='Close Price')
plt.title('Moutai (600519) Close Price')
plt.legend()
plt.show()预处理要点:
ffill)或剔除StandardScaler或MinMaxScaler,注意避免未来信息。AI模型需要输入特征,我们构建三类因子:
我们手动计算常用因子,避免依赖TA-Lib(减少环境问题)。
def add_technical_features(df):
# 收益率
df['return_1d'] = df['close'].pct_change()
df['return_5d'] = df['close'].pct_change(5)
df['return_20d'] = df['close'].pct_change(20)
# 波动率 (滚动标准差)
df['volatility_5d'] = df['return_1d'].rolling(5).std()
df['volatility_20d'] = df['return_1d'].rolling(20).std()
# 成交量比率
df['volume_ratio'] = df['volume'] / df['volume'].rolling(20).mean()
# RSI (14天)
delta = df['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
rs = gain / loss
df['rsi_14'] = 100 - (100 / (1 + rs))
# MACD (快速12, 慢速26, 信号9)
exp1 = df['close'].ewm(span=12, adjust=False).mean()
exp2 = df['close'].ewm(span=26, adjust=False).mean()
df['macd'] = exp1 - exp2
df['macd_signal'] = df['macd'].ewm(span=9, adjust=False).mean()
df['macd_hist'] = df['macd'] - df['macd_signal']
# 价格位置 (当前价在20日区间的位置)
high_20 = df['high'].rolling(20).max()
low_20 = df['low'].rolling(20).min()
df['price_position'] = (df['close'] - low_20) / (high_20 - low_20 + 1e-6)
return df
df = add_technical_features(df)
# 删除前面因滚动产生的NaN
df.dropna(inplace=True)
print(df.shape)模型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
线性回归/逻辑回归 | 可解释性强 | 非线性捕捉弱 | 低噪声、稳定市场 |
随机森林/XGBoost | 非线性、特征重要性 | 无法处理时序依赖 | 因子组合、分类预测 |
LSTM/GRU | 捕捉长期时序依赖 | 训练慢、易过拟合 | 价格/波动率预测 |
Transformer | 并行、长程依赖 | 数据量大、参数多 | 高频/多模态数据 |
强化学习 (DQN/PPO) | 直接优化收益-风险 | 环境不稳定、收敛难 | 动态仓位管理 |
本文选择:LSTM进行价格方向预测(二分类:涨/跌),并基于预测结果生成交易信号。
我们预测未来一天的涨跌方向(label = 1 if return_1d_shift > 0 else 0)。使用前N天的特征序列作为输入。
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping
# 选取特征列
feature_cols = ['return_1d', 'return_5d', 'return_20d', 'volatility_5d', 'volatility_20d',
'volume_ratio', 'rsi_14', 'macd', 'macd_signal', 'macd_hist', 'price_position']
# 标签:次日收益率正负
df['target'] = (df['return_1d'].shift(-1) > 0).astype(int)
# 去掉标签为NaN的行(最后一天)
df_model = df.dropna(subset=['target']).copy()
# 归一化特征
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df_model[feature_cols])
# 构建时序样本:用过去WINDOW天的数据预测下一天
WINDOW = 20 # 回溯天数
X_seq, y_seq = [], []
for i in range(WINDOW, len(df_model) - 1): # 保留第i+1天作为标签 (已有target)
X_seq.append(X_scaled[i-WINDOW:i])
y_seq.append(df_model['target'].iloc[i]) # 注意:target是当天对第二天的预测,所以i对应的target已经是对i+1的预测
X_seq = np.array(X_seq)
y_seq = np.array(y_seq)
print(f"X shape: {X_seq.shape}, y shape: {y_seq.shape}")
# 划分训练集(80%)、验证集(10%)、测试集(10%)按时间顺序
split1 = int(len(X_seq) * 0.8)
split2 = int(len(X_seq) * 0.9)
X_train, X_val, X_test = X_seq[:split1], X_seq[split1:split2], X_seq[split2:]
y_train, y_val, y_test = y_seq[:split1], y_seq[split1:split2], y_seq[split2:]
print(f"Train: {X_train.shape}, Val: {X_val.shape}, Test: {X_test.shape}")model = Sequential()
model.add(LSTM(64, input_shape=(WINDOW, len(feature_cols)), return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(32, return_sequences=False))
model.add(Dropout(0.2))
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer=Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy'])
model.summary()
# 早停
early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
history = model.fit(X_train, y_train, epochs=100, batch_size=32,
validation_data=(X_val, y_val), callbacks=[early_stop], verbose=1)# 在测试集上预测概率
y_pred_prob = model.predict(X_test).flatten()
y_pred_class = (y_pred_prob > 0.5).astype(int)
# 计算准确率(作为参考,但不是核心)
from sklearn.metrics import accuracy_score, classification_report
print("Test Accuracy:", accuracy_score(y_test, y_pred_class))
print(classification_report(y_test, y_pred_class))
# 生成交易信号:将概率映射为仓位(阈值可调)
# 简化:预测涨则买入(全仓),预测跌则空仓(或做空,这里仅做多)
signal = np.where(y_pred_prob > 0.55, 1, 0) # 提高阈值减少交易次数我们使用自定义回测,计算累计收益和风险指标。假设无交易成本(实际需扣除滑点和佣金)。
# 获取测试集对应的日期和真实收益率(用于回测)
test_start = split2 + WINDOW # 因为X_seq索引偏移
test_dates = df_model.index[test_start:test_start + len(y_pred_prob)] # 注意对齐
# 实际收益率序列(测试期间每日收益)
test_returns = df_model['return_1d'].iloc[test_start:test_start + len(y_pred_prob)].values
# 策略信号与日期对齐
# signal长度等于预测样本数,但每日收益率数量少一天?仔细检查:
# 我们预测的是第i天的标签(对应第i+1天收益率),所以信号应在第i天收盘后使用,用于第i+1天的交易。
# 简化:信号序列从第i天开始,对应第i+1天的收益
# 构造一个DataFrame
backtest_df = pd.DataFrame({
'date': df_model['date'].iloc[test_start:test_start + len(y_pred_prob)],
'return': test_returns,
'signal': signal
})
# 策略收益 = signal * 当日收益率(因为信号在当天生效,用于当天收盘价买入?实际上信号是预测次日,所以应该用次日收益)
# 更准确:信号在第t天产生,用于第t+1天的收益。
# 所以将信号shift(1)
backtest_df['strategy_return'] = backtest_df['signal'].shift(1) * backtest_df['return']
# 去掉首日NaN
backtest_df.dropna(inplace=True)
# 计算累计收益
backtest_df['cum_return'] = (1 + backtest_df['return']).cumprod()
backtest_df['cum_strategy'] = (1 + backtest_df['strategy_return']).cumprod()
# 计算风险指标
total_return = backtest_df['cum_strategy'].iloc[-1] - 1
annual_return = (1 + total_return) ** (252 / len(backtest_df)) - 1
daily_std = backtest_df['strategy_return'].std()
sharpe_ratio = np.sqrt(252) * daily_std # 无风险利率为0
# 最大回撤
cum_wealth = backtest_df['cum_strategy']
rolling_max = cum_wealth.expanding().max()
drawdown = (cum_wealth - rolling_max) / rolling_max
max_drawdown = drawdown.min()
print(f"总收益率: {total_return:.2%}")
print(f"年化收益率: {annual_return:.2%}")
print(f"夏普比率: {sharpe_ratio:.3f}")
print(f"最大回撤: {max_drawdown:.2%}")
# 绘图
plt.figure(figsize=(14,6))
plt.plot(backtest_df['date'], backtest_df['cum_strategy'], label='Strategy')
plt.plot(backtest_df['date'], backtest_df['cum_return'], label='Buy & Hold')
plt.title('Backtest Performance')
plt.legend()
plt.grid()
plt.show()注意:实际回测应加上交易成本(假设千分之三),并考虑滑点。上述代码为简化演示。
与有监督预测不同,强化学习(RL)将交易视为序列决策问题,智能体(Agent)根据当前状态(价格、持仓等)选择动作(买入、卖出、持有),环境返回奖励(收益或夏普)。优点是可以直接优化风险调整后的收益。
下面给出一个极简DQN框架(基于gym和tensorflow),仅示意:
import gym
from gym import spaces
from tensorflow.keras import layers, models
class TradingEnv(gym.Env):
def __init__(self, data, initial_capital=100000):
super().__init__()
self.data = data
self.initial_capital = initial_capital
self.current_step = 0
self.action_space = spaces.Discrete(3) # 0:持有, 1:买入, 2:卖出
self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(len(feature_cols)+1,), dtype=np.float32) # 特征+持仓
def reset(self):
self.current_step = 0
self.capital = self.initial_capital
self.position = 0 # 持股数量
return self._get_obs()
def _get_obs(self):
features = self.data.iloc[self.current_step][feature_cols].values
return np.append(features, [self.position])
def step(self, action):
# 简化:价格变化,执行动作
# ... 实现买卖逻辑,计算奖励
pass实际应用中,RL需要大量训练和稳定环境,且存在过拟合风险,部署难度较高。目前学术界和工业界都在探索,但尚未成为主流。
金融数据信噪比极低(通常小于0.01),复杂模型容易记住噪声。解决方案:
市场风格、波动率、制度都会变化。模型需要定期在线学习或自适应更新。
预测准确率即便只有52%,扣除手续费后可能亏损。必须将成本纳入回测。
2020年熔断、2015年股灾等极端行情,AI模型没有经验,容易爆仓。应设置止损线和最大仓位限制。
绝对避免使用未来信息,如用未来数据归一化、标签错误等。需严格顺序划分。
本文从数据获取、特征工程、LSTM预测到回测评估,完整实现了一个AI量化策略。但必须清醒认识到:回测中的高收益往往无法复现,真正的壁垒在于对市场的深刻理解和持续的风险管理。
作为开发者,我们应当将AI视为辅助工具,而非自动取款机。建议从模拟交易开始,逐步完善系统,并始终保持敬畏之心。
所有代码已开源在GitHub(示例地址),欢迎探讨和改进。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。