Skip to content

策略开发流程

量化策略开发不是一蹴而就的事情。一个从脑海中闪过的交易想法,要变成一个可以在真实市场中运行的策略,需要经过严谨的设计、编码、测试和验证。跳过任何一个步骤,都可能导致策略在实盘中表现与预期大相径庭。

本文将介绍一套标准化的量化策略开发流程,帮助你用工程化的方法来管理策略开发的全生命周期。

为什么要标准化流程

很多初学者在开发量化策略时容易犯以下错误:

  • 过度优化:反复调整参数直到回测收益最高,结果只是"拟合"了历史数据
  • 忽视交易成本:回测赚翻了,实盘一扣手续费和滑点就亏钱
  • 没有样本外验证:在全部数据上开发和测试,无法验证策略的泛化能力
  • 想法太多,落地太少:有了想法不记录、不测试,最后不了了之

标准化流程的意义在于:用可重复的方法,降低犯错概率,提高策略的可靠性和可维护性。

完整开发流程总览

一个标准的量化策略开发流程包含以下八个步骤:

想法 → 假设 → 数据准备 → 策略编码 → 回测 → 参数调优 → 样本外验证 → 实盘

下面逐步展开每个环节。

第一步:想法(Idea)

一切策略都源于一个交易想法。想法的来源可以是:

  • 技术分析观察:"股价突破20日均线后往往会继续上涨"
  • 基本面逻辑:"ROE连续三年超过15%的公司长期表现优于大盘"
  • 市场微观结构:"尾盘大单买入的股票,次日高开概率较高"
  • 学术研究:"动量效应:过去表现好的股票在未来一段时间内仍会表现好"
  • 生活经验:"冬天来临前煤炭股通常有一波行情"

想法不需要完美,但需要明确。一个模糊的想法如"低买高卖"是无法转化为策略的。

记录想法的模板

markdown
### 策略想法记录

- **日期**:2024-03-15
- **想法来源**:观察盘面发现
- **核心观察**:当5日均线上穿20日均线,且成交量放大时,
  未来5个交易日的平均收益为正
- **预期逻辑**:均线金叉代表短期趋势转强,放量确认资金入场,
  二者结合提高信号可靠性
- **初步判断**:逻辑上说得通,值得验证

养成记录想法的习惯,即使当下没有时间验证,也为将来的策略储备了素材。

第二步:假设(Hypothesis)

将模糊的想法转化为可测试的假设,是策略开发中最关键的一步。一个好的假设需要满足以下条件:

  • 可量化:能用具体数字来衡量
  • 可证伪:存在明确的条件来判断假设是否成立
  • 有经济学逻辑支撑:不是纯粹的数据挖掘

想法到假设的转化示例

模糊的想法可测试的假设
"均线金叉后股价会涨""当5日均线从下方穿越20日均线时,以次日开盘价买入,持有10个交易日后卖出,平均收益率显著大于0"
"RSI 超卖的股票会反弹""当RSI(14)跌破30时买入,持有5个交易日后卖出,胜率是否显著高于50%"
"小盘股表现更好""市值排名后30%的股票,在未来20个交易日的平均收益是否高于市值排名前30%的股票"

假设越具体,后续的编码和验证就越容易。

python
# 将假设参数化
hypothesis = {
    "name": "均线金叉策略",
    "entry_signal": "MA5 上穿 MA20",
    "entry_price": "信号次日开盘价",
    "exit_signal": "持有N个交易日后卖出",
    "holding_period": 10,
    "benchmark": "同期沪深300指数收益",
    "metric": "平均收益率 > 0 且 胜率 > 50%"
}

print("策略假设:")
for k, v in hypothesis.items():
    print(f"  {k}: {v}")

第三步:数据准备(Data Preparation)

数据是策略开发的原材料。在这个阶段需要确定:

3.1 数据范围

  • 时间范围:至少覆盖一个完整的牛熊周期(A股通常3-5年),建议5年以上
  • 股票池:全市场还是特定板块?是否需要剔除 ST、停牌、涨跌停无法交易的股票?
  • 复权方式:使用前复权数据保证价格连续性

3.2 数据质量检查

python
import pandas as pd
import akshare as ak

# 获取数据
df = ak.stock_zh_a_hist(
    symbol="000001",
    period="daily",
    start_date="20190101",
    end_date="20241231",
    adjust="qfq"
)

df = df.rename(columns={
    "日期": "Date", "开盘": "Open", "收盘": "Close",
    "最高": "High", "最低": "Low", "成交量": "Volume"
})
df["Date"] = pd.to_datetime(df["Date"])
df = df.set_index("Date")

# 数据质量检查清单
print("=== 数据质量报告 ===")
print(f"数据范围: {df.index[0].date()} ~ {df.index[-1].date()}")
print(f"总记录数: {len(df)}")
print(f"缺失值:\n{df.isnull().sum()}")
print(f"零成交量天数: {(df['Volume'] == 0).sum()}")
print(f"数据类型:\n{df.dtypes}")

# 检查是否有异常值(如价格为负数)
print(f"价格异常: {(df['Close'] <= 0).sum()} 条")

3.3 数据分割

将数据分为样本内(In-Sample)样本外(Out-of-Sample) 两部分。样本内用于策略开发和参数调优,样本外用于最终验证。常见的分割比例是 70:30 或 60:40。

python
# 按时间分割数据
total_len = len(df)
split_idx = int(total_len * 0.7)

df_is = df.iloc[:split_idx]    # 样本内(70%)
df_oos = df.iloc[split_idx:]   # 样本外(30%)

print(f"样本内: {df_is.index[0].date()} ~ {df_is.index[-1].date()} ({len(df_is)} 条)")
print(f"样本外: {df_oos.index[0].date()} ~ {df_oos.index[-1].date()} ({len(df_oos)} 条)")

关键原则:在样本外数据上只能做一次最终验证,不能反复在样本外数据上调整参数。否则样本外数据就变成了"伪样本外",失去了验证的意义。

第四步:策略编码(Coding)

将假设转化为可执行的代码。一个完整的策略需要定义以下四个要素:

  1. 入场条件:什么时候买入?
  2. 出场条件:什么时候卖出?
  3. 仓位管理:每次买卖多少?
  4. 资金管理:初始资金、手续费率、滑点等

策略编码模板

python
class Strategy:
    """策略基类模板"""

    def __init__(self, initial_capital=100000, commission_rate=0.001):
        self.initial_capital = initial_capital
        self.commission_rate = commission_rate  # 手续费率,默认万三双边
        self.slippage = 0.001  # 滑点,默认千分之一

    def generate_signals(self, df):
        """
        生成买卖信号

        参数:
            df: DataFrame,包含价格数据

        返回:
            DataFrame,新增 signal 列:1=买入, -1=卖出, 0=无操作
        """
        raise NotImplementedError("子类必须实现此方法")

    def calculate_position(self, capital, price):
        """
        计算每次交易的仓位

        简单策略:每次全仓买入(等资金分配)
        """
        shares = int(capital / (price * (1 + self.commission_rate + self.slippage)))
        return shares // 100 * 100  # A股最小交易单位为100股

    def run(self, df):
        """
        运行策略回测

        返回:
            dict,包含交易记录和绩效指标
        """
        signals = self.generate_signals(df)

        trades = []
        capital = self.initial_capital
        position = 0  # 当前持仓数量

        for i in range(1, len(signals)):
            date = signals.index[i]
            price = signals["Open"].iloc[i]  # 以次日开盘价成交
            signal = signals["signal"].iloc[i]

            if signal == 1 and position == 0:
                # 买入
                shares = self.calculate_position(capital, price)
                if shares > 0:
                    cost = shares * price * (1 + self.commission_rate)
                    capital -= cost
                    position = shares
                    trades.append({
                        "date": date,
                        "action": "BUY",
                        "price": price,
                        "shares": shares,
                        "capital": capital
                    })

            elif signal == -1 and position > 0:
                # 卖出
                revenue = position * price * (1 - self.commission_rate)
                capital += revenue
                trades.append({
                    "date": date,
                    "action": "SELL",
                    "price": price,
                    "shares": position,
                    "capital": capital
                })
                position = 0

        # 计算最终资产
        final_price = signals["Close"].iloc[-1]
        total_asset = capital + position * final_price

        return {
            "trades": trades,
            "total_asset": total_asset,
            "return_rate": (total_asset / self.initial_capital - 1) * 100,
            "trade_count": len(trades)
        }

第五步:回测(Backtesting)

回测是用历史数据模拟策略运行,评估策略表现的过程。回测不仅要看收益率,还要关注风险调整后的收益。

关键绩效指标

指标英文含义
总收益率Total Return策略在整个回测期间的累计收益
年化收益率Annualized Return将总收益率折算为年化水平
最大回撤Max Drawdown从历史最高点到最低点的最大跌幅,衡量策略风险
夏普比率Sharpe Ratio每承担一单位风险获得的超额收益,> 1 为良好
胜率Win Rate盈利交易次数占总交易次数的比例
盈亏比Profit/Loss Ratio平均盈利金额与平均亏损金额的比值
python
def calc_metrics(trades, initial_capital, period_years):
    """计算策略绩效指标"""
    import numpy as np

    if len(trades) < 2:
        return {"error": "交易记录不足"}

    # 计算每笔交易的收益
    buy_sell_pairs = []
    for i in range(0, len(trades) - 1, 2):
        if i + 1 < len(trades):
            buy_trade = trades[i]
            sell_trade = trades[i + 1]
            profit = (sell_trade["price"] - buy_trade["price"]) * buy_trade["shares"]
            profit_rate = profit / (buy_trade["price"] * buy_trade["shares"])
            buy_sell_pairs.append({
                "profit": profit,
                "profit_rate": profit_rate
            })

    profits = [p["profit_rate"] for p in buy_sell_pairs]
    total_return = (trades[-1]["capital"] / initial_capital - 1)
    win_count = sum(1 for p in profits if p > 0)

    metrics = {
        "总收益率": f"{total_return * 100:.2f}%",
        "年化收益率": f"{((1 + total_return) ** (1 / period_years) - 1) * 100:.2f}%",
        "交易次数": len(buy_sell_pairs),
        "胜率": f"{win_count / len(buy_sell_pairs) * 100:.1f}%",
        "平均收益": f"{np.mean(profits) * 100:.2f}%",
        "盈亏比": f"{abs(np.mean([p for p in profits if p > 0]) / np.mean([p for p in profits if p < 0])):.2f}",
    }

    return metrics

最大回撤(Max Drawdown) 的计算非常重要。如果一个策略的最大回撤是 50%,意味着你需要 100% 的收益才能回本。对于大多数投资者来说,20%~30% 的最大回撤已经是心理承受的上限。

第六步:参数调优(Optimization)

参数调优是在样本内数据上寻找最佳参数组合的过程。但要注意避免过度拟合(Overfitting)

什么是过度拟合

过度拟合是指策略参数过于精确地匹配了历史数据的特征,导致在面对新数据时表现大幅下降。典型的过度拟合信号:

  • 参数稍微改变,收益就大幅变化(参数不稳定)
  • 回测收益高得离谱(年化 100%+)
  • 样本外表现远差于样本内

调优方法:网格搜索

python
def grid_search(df, param_grid, strategy_class):
    """
    网格搜索寻找最佳参数

    参数:
        df: DataFrame,样本内数据
        param_grid: dict,参数网格,如 {"fast": [5,10], "slow": [20,30]}
        strategy_class: 策略类

    返回:
        DataFrame,所有参数组合的绩效排名
    """
    from itertools import product

    results = []

    # 生成所有参数组合
    keys = list(param_grid.keys())
    values = list(param_grid.values())
    combinations = list(product(*values))

    for combo in combinations:
        params = dict(zip(keys, combo))

        try:
            strategy = strategy_class(**params)
            result = strategy.run(df)

            results.append({
                **params,
                "total_return": result["return_rate"],
                "trade_count": result["trade_count"]
            })
        except Exception as e:
            results.append({**params, "total_return": None, "error": str(e)})

    results_df = pd.DataFrame(results)
    results_df = results_df.sort_values("total_return", ascending=False)

    return results_df

# 示例:搜索均线策略的最佳参数
# param_grid = {"fast_period": [5, 10, 15], "slow_period": [20, 30, 40, 60]}
# results = grid_search(df_is, param_grid, MACrossStrategy)
# print(results.head(10))

避免过度拟合的原则

  1. 参数不要太多:策略参数不超过 3 个
  2. 参数要有经济学含义:不要用 "17日均线" 这种没有逻辑依据的参数
  3. 关注参数稳健性:最优参数附近的参数组合也应表现良好
  4. 优先选择简单的策略:奥卡姆剃刀——在效果相近时,选更简单的

第七步:样本外验证(Out-of-Sample Testing)

样本外验证是策略开发流程中最重要的一步。它的目的是检验策略是否具有泛化能力,而不是仅仅记住了历史数据。

python
# 用样本内数据确定的最优参数,在样本外数据上验证
# optimal_params = {"fast_period": 10, "slow_period": 30}

# strategy = MACrossStrategy(**optimal_params)
# oos_result = strategy.run(df_oos)

# print("=== 样本外验证结果 ===")
# print(f"样本外收益率: {oos_result['return_rate']:.2f}%")
# print(f"样本外交易次数: {oos_result['trade_count']}")

# 对比样本内外表现
# if oos_result['return_rate'] > is_result['return_rate'] * 0.5:
#     print("策略通过样本外验证")
# else:
#     print("策略可能过度拟合,需要重新审视")

样本外验证的评判标准

  • 样本外收益率不应低于样本内收益率的 50%(经验法则)
  • 样本外最大回撤不应显著大于样本内
  • 样本外胜率与样本内相差不超过 10 个百分点

如果策略没有通过样本外验证,应该回到假设阶段重新思考策略逻辑,而不是在样本外数据上继续调参数。

第八步:模拟盘 / 实盘(Live Trading)

通过样本外验证后,策略可以进入实盘阶段。但建议先用模拟盘运行 1-3 个月,观察以下问题:

  • 实际成交价与预期价格的偏差(滑点)
  • 信号生成到实际下单的延迟
  • 停牌、涨跌停导致无法交易的情况
  • 策略在真实市场环境中的心理压力

实盘资金管理

即使策略通过了所有验证,实盘也应从小资金开始:

  • 第1-3个月:投入计划资金的 10%~20%
  • 第4-6个月:如果策略运行正常,逐步增加到 50%
  • 6个月后:确认策略稳定后,可以考虑增加到 100%

策略失效的信号

需要持续监控策略表现,以下信号可能意味着策略正在失效:

  • 连续亏损超过历史最大连续亏损
  • 月度收益持续低于预期
  • 最大回撤接近或超过历史最大回撤
  • 市场环境发生了结构性变化(如交易制度改变)

策略文档模板

每个策略开发完成后,都应撰写文档存档。以下是推荐的模板:

markdown
# 策略文档:[策略名称]

## 1. 基本信息
- 策略名称:
- 开发日期:
- 最后更新:
- 策略类型:趋势跟踪 / 均值回归 / 动量 / 其他

## 2. 策略逻辑
- 核心假设:
- 入场条件:
- 出场条件:
- 仓位管理:

## 3. 参数设置
- 参数列表及含义:
- 参数选择依据:

## 4. 回测结果
- 回测区间:
- 样本内收益 / 样本外收益:
- 最大回撤:
- 夏普比率:
- 胜率 / 盈亏比:

## 5. 已知局限
- 在什么市场环境下可能失效:
- 风险点:

## 6. 实盘记录
- 上线日期:
- 当前状态:

从简单策略开始

对于量化新手,一个重要的建议是:从最简单的策略开始

不要一上来就追求复杂的机器学习模型或多因子策略。一个简单的双均线交叉策略,经过严谨的开发和验证流程,比一个未经充分测试的复杂策略要可靠得多。

原因如下:

  1. 简单策略容易理解:你知道每一笔交易为什么发生
  2. 简单策略容易调试:出问题时能快速定位原因
  3. 简单策略不容易过度拟合:参数少,自由度低
  4. 简单策略是复杂策略的基础:先掌握方法论,再提升策略复杂度

建议的学习路径:

单指标策略(如均线交叉)
  → 双指标组合(如均线 + RSI)
    → 多因子策略
      → 机器学习策略

小结

量化策略开发是一个严谨的工程化过程,核心步骤如下:

  1. 想法:记录有依据的交易想法
  2. 假设:将想法转化为可量化的、可证伪的假设
  3. 数据准备:确保数据质量,分割样本内外
  4. 策略编码:定义入场、出场、仓位和资金管理规则
  5. 回测:在样本内数据上评估策略表现
  6. 参数调优:在样本内寻找稳健的参数组合
  7. 样本外验证:检验策略的泛化能力
  8. 实盘:从小资金开始,持续监控

每个步骤都不可跳过。慢即是快——一个经过完整流程验证的简单策略,远胜过一个跳过验证步骤的复杂策略。

免责声明:本文仅介绍量化策略开发的方法论,不构成任何投资建议。量化交易存在风险,过往回测表现不代表未来收益。投资有风险,入市需谨慎。

仅供学习交流,不构成任何投资建议