1. 从一道赛题看量化投资的实战起点

如果你对量化投资感兴趣,或者正打算从理论迈向实践,那么“2022华中杯B题”是一个绝佳的切入点。这道赛题没有给你一堆复杂的金融理论公式,而是直接抛出了一个非常具体、也非常“量化”的问题:给你一支股票(比如赛题中可能指定的某支股票)的历史交易数据,让你去预测未来某个时间点的价格,或者构建一个简单的交易策略。这听起来简单,但恰恰是量化投资最核心、最底层的逻辑——用数据说话,用模型决策。

很多朋友一提到量化,脑子里就是高频交易、机器学习、神经网络这些听起来很高大上的词,然后就被吓退了。其实,量化投资的入门远没有那么玄乎。它更像是一个“数据驱动的决策系统”,核心流程可以概括为: 获取数据 -> 处理数据 -> 构建特征 -> 训练模型 -> 回测验证 -> 实盘执行 。华中杯的这道题,本质上就是让你完整地走一遍这个流程的前半部分,尤其是“构建特征”和“训练模型”这两个关键环节。

为什么说这道题有代表性?因为它避开了实盘中复杂的资金管理、风险控制和交易成本问题,让你专注于最纯粹的“预测”或“信号生成”。这正是新手建立量化思维的第一步: 你能否从历史数据中,找到某种规律或模式,并相信这种模式在未来短期内会重复? 这道题就是对这个问题的直接检验。接下来,我将结合这道赛题常见的解题框架,拆解量化投资从思路到代码的完整路径,并分享一些只有真正动手做过才会知道的“坑”和技巧。

2. 解题核心:量化策略的通用构建框架

面对“预测股价”或“构建交易策略”的题目,一个清晰、可复现的框架至关重要。这个框架不仅适用于比赛,也是你日后开发任何策略的基石。我们可以将其分解为以下几个环环相扣的步骤。

2.1 数据理解与预处理:一切分析的基础

赛题通常会提供一支股票一段时间内的日级或分钟级行情数据,字段一般包括:日期( Date )、开盘价( Open )、最高价( High )、最低价( Low )、收盘价( Close )、成交量( Volume )。你的第一步不是急着跑模型,而是像侦探一样审视这些数据。

首先,检查数据的完整性。 是否有缺失的交易日?特别是A股市场,节假日是不交易的,如果数据里出现了节假日且有价格,那肯定是错误数据。对于缺失值,常见的处理方式有:向前填充(用前一个交易日的值)、删除该行,或者对于价格序列,使用线性插值。但要注意,成交量缺失通常直接删除或填0,因为插值出来的成交量没有意义。

其次,进行必要的特征计算。 原始的OHLCV数据是“原料”,我们需要加工出“特征”。最经典、最有效的一批特征就是技术指标。对于这类预测题,以下几个指标家族几乎是必选的:

  1. 趋势指标 :用于判断价格运动的方向。

    • 移动平均线(MA) MA5 MA10 MA20 MA60 。计算短期均线上穿长期均线(金叉)或下穿(死叉)是常见的交易信号。
    • 指数平滑移动平均线(EMA) :相比MA,EMA给予近期价格更高的权重,反应更灵敏。
    • MACD(异同移动平均线) :由DIF线、DEA线和MACD柱状图组成。DIF上穿DEA为买入信号,反之为卖出信号。它是趋势和动能的综合指标。
  2. 动量与振荡指标 :用于判断价格变化的速度和超买超卖状态。

    • 相对强弱指数(RSI) :取值0-100。通常认为RSI>70为超买(可能下跌),RSI<30为超卖(可能反弹)。这是反转策略的常用指标。
    • 随机震荡指标(KDJ) :由K线、D线和J线组成。K、D值在20以下超卖,80以上超买。J线反应更剧烈。
    • 布林带(Bollinger Bands) :由中轨(MA)、上轨和下轨组成。价格触及或突破上轨可能回调,触及下轨可能反弹。布林带宽度本身也是波动率的度量。
  3. 成交量指标 :确认价格趋势的强度。

    • 成交量移动平均(VMA) :观察成交量是否放大。
    • 量价关系 :价涨量增、价跌量缩是健康趋势;价涨量缩或价跌量增则可能预示趋势衰竭。

注意 :计算指标时,务必注意 未来函数 问题。即,你在t时刻计算指标时,只能用t时刻及之前的数据。例如,计算t时刻的MA20,应该用 [t-19, t] 这20个数据点。如果你不小心用到了 [t-18, t+1] 的数据,那就是引入了未来信息,回测结果会无比完美但毫无意义,实盘必亏。这是新手最容易犯的致命错误。

2.2 目标变量定义:你要预测什么?

这是决定策略方向的关键一步。预测“明天的收盘价”是一个回归问题,但直接预测价格绝对值非常困难,因为价格非平稳且受太多因素影响。更常见的做法是将其转化为分类问题或更容易预测的相对值问题。以下是几种主流定义方式:

  • 方向预测(分类问题) :定义 label = 1 如果 明日收益率 > 阈值 (如0),否则 label = 0 。这就是一个二分类问题(涨/跌)。阈值可以设为0,也可以设一个小的正数来过滤震荡。
  • 收益率预测(回归问题) :直接预测 明日收益率 = (明日收盘价 - 今日收盘价) / 今日收盘价 。虽然还是回归,但预测相对变化比预测绝对价格更稳定。
  • 价格位置预测 :预测明日收盘价是否突破布林带上轨或下轨,是否创N日新高/新低等。

在华中杯这类比赛中, 将问题转化为涨跌二分类,并使用机器学习模型(如LightGBM、XGBoost)进行预测,是平衡效果和复杂度的主流选择 。它避免了回归问题对预测精度过于严苛的要求,更关注信号方向的正确性。

2.3 特征工程:策略的“阿尔法”来源

特征工程是量化策略的灵魂,决定了模型能从数据中学到什么。除了2.1中计算的基础技术指标,我们还需要创造更多有信息量的特征。

  1. 指标衍生特征 :不要只使用指标的原始值。

    • 交叉信号 MA5 - MA20 (差值), MA5 / MA20 (比率)。当差值由负转正,就是金叉。
    • 位置特征 收盘价 / 布林带上轨 收盘价 / 布林带中轨 。反映当前价格在布林带中的相对位置。
    • 动量变化 今日RSI - 昨日RSI RSI的N日移动平均 。捕捉指标本身的变化趋势。
  2. 统计特征

    • 波动率 :过去N日收益率的标准差。高波动率可能意味着风险或机会。
    • 收益率分布 :过去N日收益率的偏度、峰度。
    • 自相关性 :昨日收益率对今日收益率的影响(可以用滞后特征,如 Lag1_Return )。
  3. 时间序列特征

    • 滞后特征 :将过去几天的收盘价、成交量、各指标值作为特征( Close_t-1 , Close_t-2 , ...)。这相当于让模型自己学习历史模式。
    • 滚动统计量 :过去N日的最高价、最低价、成交量均值等。
  4. 交互特征 :尝试组合不同指标。例如, (收盘价-最低价) / (最高价-最低价) * 成交量 ,这个特征结合了价格位置和成交量信息。

一个强大的特征集可能包含几十甚至上百个特征。但 特征不是越多越好 ,冗余和无关的特征会引入噪声,导致模型过拟合。因此,特征筛选(如基于特征重要性、相关性分析)是必不可少的后续步骤。

2.4 模型选择与训练:从逻辑回归到树模型

对于金融时间序列数据,尤其是赛题规模的样本量(通常几年日线数据,约1000个样本),以下模型是经过实践检验的选择:

  • 逻辑回归(Logistic Regression) :最简单的基线模型。优点是可解释性强,能直接看到每个特征的系数(贡献度)。缺点是只能学习线性关系,而市场关系往往是非线性的。但它是一个完美的起点,用于验证特征是否基本有效。
  • 支持向量机(SVM) :在处理非线性问题上比逻辑回归强,但调参相对复杂,且对大数据量训练较慢。
  • 梯度提升树(LightGBM/XGBoost) 这是当前量化竞赛和业界的主流选择 。它们能自动处理特征间的非线性关系和交互作用,对缺失值不敏感,且训练速度快。LightGBM相比XGBoost通常更快,内存消耗更小。

训练中的关键点:

  • 数据划分 绝对不能随机划分! 必须按时间顺序划分。例如,用前70%的数据做训练集,中间15%做验证集,最后15%做测试集。这模拟了在历史数据上训练,并在未知的未来数据上测试的真实场景。
  • 避免前瞻性偏差 :确保在训练集上计算任何滚动统计量(如移动平均、标准差)时,只使用训练集内部的数据,绝不能“看到”验证集或测试集的数据。这通常需要在整体划分后,对每个数据集独立进行特征计算,或者使用非常谨慎的滚动窗口方法。
  • 样本不均衡处理 :股票市场上涨和下跌的天数不一定均衡。如果涨跌样本比例悬殊(如7:3),需要对少数类样本进行上采样(如SMOTE算法)或在模型参数中设置 class_weight (如 balanced )。

2.5 回测与评价:策略的试金石

模型预测出每天的涨跌概率后,需要将其转化为具体的交易信号,并进行回测。一个最简单的规则是:当模型预测上涨的概率超过一个阈值(如0.55)时,在次日开盘买入;当预测下跌的概率超过阈值时,在次日开盘卖出(或空仓)。

回测时需要计算关键的绩效指标:

  • 累计收益率 :策略从开始到结束的总收益。
  • 年化收益率 :将累计收益率折算到每年的水平。
  • 最大回撤 :策略净值从高点回落的最大幅度。 这是衡量风险最重要的指标 ,一个回撤过大的策略实盘时很难坚持。
  • 夏普比率 :衡量每承担一单位风险所获得的超额回报。越高越好。
  • 胜率 :盈利交易次数占总交易次数的比例。
  • 盈亏比 :平均盈利金额 / 平均亏损金额。

在比赛中,除了这些指标,组织方可能还会有特定的评价函数,如综合考虑收益率和回撤。你需要确保你的回测逻辑与赛题要求完全一致。

3. 针对2022华中杯B题的深度思路拆解

虽然无法获取原题数据,但基于此类赛题的普遍模式,我们可以构建一个更具实战性的解题方案。假设题目是提供一支股票(例如“000001.SZ 平安银行”)2018-2021年的日线数据,要求预测2022年上半年的每日涨跌。

3.1 数据预处理与特征构建的具体实现

我们使用Python的 pandas ta (Technical Analysis)库来演示。首先,计算一个丰富的特征池。

import pandas as pd
import numpy as np
import ta # 技术指标库

# 假设df包含‘Date', ‘Open', ‘High', ‘Low', ‘Close', ‘Volume'列,且已按日期排序
df = df.sort_values('Date').reset_index(drop=True)

# 1. 基础特征:收益率、波动率
df['Return'] = df['Close'].pct_change()
df['Return_5d'] = df['Close'].pct_change(5) # 5日收益率
df['Volatility_20d'] = df['Return'].rolling(20).std() # 20日波动率

# 2. 使用ta库快速添加技术指标
# 趋势指标
df['MA5'] = ta.trend.sma_indicator(df['Close'], window=5)
df['MA20'] = ta.trend.sma_indicator(df['Close'], window=20)
df['EMA12'] = ta.trend.ema_indicator(df['Close'], window=12)
df['EMA26'] = ta.trend.ema_indicator(df['Close'], window=26)
macd = ta.trend.MACD(df['Close'])
df['MACD'] = macd.macd()
df['MACD_Signal'] = macd.macd_signal()
df['MACD_Diff'] = macd.macd_diff() # 柱状图

# 动量指标
df['RSI'] = ta.momentum.rsi(df['Close'], window=14)
stoch = ta.momentum.StochasticOscillator(df['High'], df['Low'], df['Close'])
df['Stoch_K'] = stoch.stoch()
df['Stoch_D'] = stoch.stoch_signal()

# 波动率指标
bollinger = ta.volatility.BollingerBands(df['Close'])
df['BB_Upper'] = bollinger.bollinger_hband()
df['BB_Lower'] = bollinger.bollinger_lband()
df['BB_Width'] = (df['BB_Upper'] - df['BB_Lower']) / df['Close'] # 布林带宽度标准化

# 成交量指标
df['Volume_MA5'] = df['Volume'].rolling(5).mean()
df['Volume_Ratio'] = df['Volume'] / df['Volume_MA5'] # 量比

# 3. 衍生特征与滞后特征
df['MA5_MA20_Cross'] = df['MA5'] - df['MA20'] # 均线差值
df['Close_to_BB_Upper'] = df['Close'] / df['BB_Upper'] # 价格在布林带上轨位置
df['Close_to_BB_Lower'] = df['Close'] / df['BB_Lower'] # 价格在布林带下轨位置

# 创建滞后特征(过去1-3天的关键指标)
for lag in [1, 2, 3]:
    df[f'Return_lag{lag}'] = df['Return'].shift(lag)
    df[f'RSI_lag{lag}'] = df['RSI'].shift(lag)
    df[f'Volume_Ratio_lag{lag}'] = df['Volume_Ratio'].shift(lag)

# 4. 目标变量:预测次日涨跌(1涨,0跌)
df['Target'] = (df['Close'].shift(-1) > df['Close']).astype(int) # 注意,这里用了未来信息定义目标,实际训练时要对应shift

# 删除因计算指标产生的NaN行
df = df.dropna()

这个特征池已经包含了趋势、动量、波动、成交量等多个维度的信息,为模型提供了丰富的“食材”。

3.2 模型训练、验证与特征筛选

接下来,我们使用LightGBM进行建模,并采用时序交叉验证来更稳健地评估模型。

import lightgbm as lgb
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import accuracy_score, classification_report

# 准备特征X和目标y
# 注意:需要确保特征中不包含未来信息。我们之前用shift(-1)定义了Target,所以特征X应该是shift(1)后的Target对应的那些行。
# 简化处理:我们直接使用df中已有的特征,但确保在划分数据集时,训练集的特征不“看到”测试集的目标。
feature_cols = [col for col in df.columns if col not in ['Date', 'Target', 'Close', 'Open', 'High', 'Low']] # 剔除原始价格和日期
X = df[feature_cols].values
y = df['Target'].values

# 按时间顺序划分索引(8:2)
split_idx = int(len(df) * 0.8)
X_train, X_test = X[:split_idx], X[split_idx:]
y_train, y_test = y[:split_idx], y[split_idx:]

# 使用LightGBM
model = lgb.LGBMClassifier(
    n_estimators=200,
    learning_rate=0.05,
    max_depth=5,
    num_leaves=31,
    subsample=0.8,
    colsample_bytree=0.8,
    random_state=42,
    class_weight='balanced' # 处理样本不均衡
)

# 训练
model.fit(X_train, y_train,
          eval_set=[(X_test, y_test)],
          eval_metric='binary_logloss',
          callbacks=[lgb.early_stopping(stopping_rounds=30)])

# 预测
y_pred_prob = model.predict_proba(X_test)[:, 1]
y_pred = (y_pred_prob > 0.5).astype(int)

# 评估
print("Test Accuracy:", accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred))

# 特征重要性分析
feature_importance = pd.DataFrame({
    'feature': feature_cols,
    'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
print(feature_importance.head(20))

通过特征重要性排序,你可以发现哪些指标最有效。可能 RSI MA5_MA20_Cross BB_Width Volume_Ratio 等特征排名靠前。你可以尝试只保留重要性最高的前20个特征重新训练,模型性能可能不变甚至提升,且更不容易过拟合。

3.3 策略回测与绩效分析

我们将预测概率转化为交易信号,并进行一个简单的“次日开盘买入/卖出”的回测。

# 为测试集数据添加预测概率和信号
test_df = df.iloc[split_idx:].copy()
test_df['Pred_Prob'] = y_pred_prob
test_df['Signal'] = 0 # 默认空仓
test_df.loc[test_df['Pred_Prob'] > 0.55, 'Signal'] = 1 # 看多信号
test_df.loc[test_df['Pred_Prob'] < 0.45, 'Signal'] = -1 # 看空信号(假设可以做空,否则为0)

# 简单回测逻辑:信号为1时,次日以开盘价买入并持有;信号为-1时,次日以开盘价卖出(或做空);信号为0时空仓。
# 初始资金为1
capital = 1.0
position = 0 # 持仓比例,0为空仓,1为满仓
equity_curve = [capital]

for i in range(1, len(test_df)):
    # 昨日信号决定今日操作
    prev_signal = test_df.iloc[i-1]['Signal']
    # 今日开盘价
    today_open = test_df.iloc[i]['Open']
    # 昨日收盘价(用于计算持仓市值)
    prev_close = test_df.iloc[i-1]['Close']

    # 根据信号调整仓位
    if prev_signal == 1 and position == 0:
        # 买入
        position = capital / today_open
        capital = 0
    elif prev_signal == -1 and position > 0:
        # 卖出
        capital = position * today_open
        position = 0
    # 如果信号为0,保持仓位不变(这里简化,实际可能平仓)

    # 计算当日权益(市值+现金)
    if position > 0:
        today_equity = position * test_df.iloc[i]['Close']
    else:
        today_equity = capital
    equity_curve.append(today_equity)

# 计算绩效指标
equity_series = pd.Series(equity_curve, index=test_df.index)
returns = equity_series.pct_change().dropna()

cumulative_return = equity_series.iloc[-1] / equity_series.iloc[0] - 1
annual_return = (1 + cumulative_return) ** (252 / len(test_df)) - 1 # 假设252个交易日

# 最大回撤
peak = equity_series.expanding().max()
drawdown = (equity_series - peak) / peak
max_drawdown = drawdown.min()

# 夏普比率(假设无风险利率为0)
sharpe_ratio = returns.mean() / returns.std() * np.sqrt(252)

print(f"累计收益率: {cumulative_return:.2%}")
print(f"年化收益率: {annual_return:.2%}")
print(f"最大回撤: {max_drawdown:.2%}")
print(f"夏普比率: {sharpe_ratio:.2f}")

这个回测非常简化,忽略了交易费用、滑点、涨停跌停无法买卖等现实因素。但在比赛初期,用于快速验证策略逻辑是否有效已经足够。如果结果为正收益且夏普比率大于1,最大回撤可控,说明你的特征和模型基本有效。

4. 从解题到实战:必须跨越的鸿沟与进阶思考

在比赛中取得不错的结果是一回事,但要让这个策略具备实战价值,中间还隔着无数个“坑”。以下是我从多次实践中总结出的关键点,也是你从“解题者”成长为“实战者”必须思考的问题。

4.1 过拟合:量化策略的“头号杀手”

你在测试集上表现优异的策略,很可能只是完美地拟合了历史数据中的噪声。过拟合在量化中无处不在:

  • 特征过拟合 :使用了过多的特征,尤其是通过“数据挖掘”方式穷举组合出来的特征,可能只是偶然与历史涨跌相关。
  • 参数过拟合 :对模型参数(如LightGBM的 max_depth num_leaves )或交易规则参数(如RSI超买阈值为70还是72)进行过度优化,使得策略仅仅适应某一段特定行情。

如何应对?

  • 坚持样本外测试 :严格划分训练集、验证集、测试集,且测试集必须是时间上最新的数据。 绝对不能用测试集参与任何形式的训练或参数调整
  • 使用交叉验证,但要小心 :对于时间序列数据,标准的K-Fold交叉验证是无效的(会造成未来数据泄露)。必须使用时序交叉验证(TimeSeriesSplit),它保证训练集总是在验证集之前。
  • 简化模型 :在效果相近的情况下,选择更简单的模型(如线性模型)或更少的特征。简单的模型往往泛化能力更强。
  • 观察参数敏感性 :将某个参数(如买卖阈值)在合理范围内微调(例如从0.5调到0.6),如果策略绩效发生剧烈变化(“悬崖效应”),说明策略很可能过拟合了。

4.2 交易成本与流动性:被忽略的“隐形杀手”

比赛回测通常假设可以瞬时以开盘价/收盘价成交,且没有费用。现实中这是不可能的。

  • 手续费 :A股买卖双向收取约万分之三到千分之一的手续费(加上印花税等)。一个每天交易的策略,光手续费一年就能吃掉几十个百分点的收益。
  • 滑点 :你的订单可能无法完全以理想价格成交。尤其是在下单量较大或市场波动剧烈时,实际成交价会比预期差。通常回测中会假设一个固定的滑点(如0.1%)。
  • 流动性 :对于小盘股,你的买卖订单本身就会影响价格(冲击成本)。在回测中,如果你假设用100万元去交易一支日均成交额只有1000万元的股票,现实是根本不可能按历史价格买到的。

实战建议 :在回测后期, 必须加入交易成本和滑点模型 。一个粗略但有效的方法是,在每次买入和卖出时,直接从收益中扣除一个固定比例(例如0.2%)作为总成本。如果加了成本后策略收益归零甚至为负,那么这个策略就没有实战价值。

4.3 策略的逻辑与经济学解释

你的策略为什么能赚钱?如果回答是“因为模型预测准确率高”,这个答案是不及格的。你需要为策略的盈利找到一个合理的、可持续的逻辑解释。

  • 趋势跟踪 :你的策略是否在捕捉“涨了还会涨,跌了还会跌”的动量效应?这需要市场存在一定的趋势性。
  • 均值回归 :你的策略是否在“涨多了就卖,跌多了就买”,捕捉价格向价值中枢回归的过程?这需要市场存在反转效应。
  • 市场微观结构 :你的策略是否利用了订单流、价差等短期信息?

例如,如果你发现 RSI 布林带位置 是最重要的特征,那么你的策略逻辑可能是在捕捉“超买超卖后的短期反转”机会。你需要去验证,在A股历史上,这种反转效应是否普遍存在?它在不同市场环境(牛市、熊市、震荡市)下表现是否稳定?一个说不清逻辑的策略,就像一座建立在沙地上的城堡,今天可能赚钱,明天可能就会崩塌。

4.4 实盘部署的工程细节

从回测到实盘,还有很长的工程之路:

  • 数据实时获取与更新 :你需要一个稳定、低延迟的数据源来实时获取行情,并实时计算你的特征。
  • 模型定期重训练 :市场风格会变,模型会失效。你需要制定一个重训练计划,例如每周或每月用最新的数据重新训练模型。但重训练本身也可能引入过拟合,需要谨慎。
  • 信号生成与执行 :如何将模型预测的概率稳定、及时地转化为交易指令,并通过券商API下单?这涉及到系统的稳定性和延迟。
  • 风险监控 :实盘后需要实时监控策略的净值、回撤、成交情况等,设置止损线。当最大回撤超过预定阈值时,应自动暂停策略。

对于个人和小团队,我建议从 模拟盘 开始。很多券商和量化平台提供模拟交易接口,可以用真实的市场行情进行零资金风险的交易。在模拟盘上稳定运行3-6个月,并经历不同的市场阶段(上涨、下跌、震荡),再考虑投入实盘资金。

回过头看,“2022华中杯B题”就像量化投资的一个微缩沙盘。它让你在可控的环境下,体验了从数据处理、特征工程、模型训练到回测评估的全过程。解题的过程,就是学习量化思维的过程。真正的量化投资,远不止一个预测模型那么简单,它是一个融合了金融理论、统计学、计算机科学和工程实践的复杂系统。这道题的价值,在于为你打开了这扇门,并提供了第一块坚实的垫脚石。当你沿着这个路径继续深入,去思考过拟合、交易成本、策略逻辑和实盘工程这些问题时,你才真正开始触摸到量化投资实战的脉搏。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐