Skip to content

学会识别价格“惯性”,手把手带你搭建一套智能动量捕捉系统(附代码)

作者:老余捞鱼

原创不易,转载请标明出处及原作者。

写在前面的话:常有人问:没内幕、没资金,普通人怎么在市场里生存?我的答案很简单:借力。别总想着逆市去接那些下坠的飞刀,要学会识别那些已经跑起来的“快车”并跳上去。今天我就把这套基于物理学“惯性”原理的动量策略拆解开,告诉你如何利用市场的情绪惯性,做一个聪明的随风起舞者。

一、别把动量当成了追涨杀跌

刚开始接触动量策略那会儿,我的想法特别简单:价格突破昨天的高点,我就跟进;跌破昨天的低点,我就离场。看起来合情合理,对吧?

但当我把这套逻辑扩展到更多品种、更长时间段时,噩梦来了。我给它起了个名字——”千刀万剐式亏损”。

具体来说,就是小亏不断,偶尔大赚,但总体收益曲线居然跑不赢最简单的”买了放着不动”。问题在哪?我后来发现,我把所有突破都当成了一回事,完全没考虑当时的市场环境是平静还是躁动,就像用开跑车的力度去开拖拉机,能不出事吗?

我意识到自己缺两样东西:一是更靠谱的趋势判断方法,二是更聪明的波动过滤机制。这就把我引向了VWAP这个指标。

VWAP指标示例

图1:VWAP(成交量加权平均价)在图表中的典型表现 | 来源:Alchemy Markets

二、VWAP:找到”真金白银聚集的地方”

VWAP,中文叫成交量加权平均价。听起来高大上,其实逻辑特朴实:

假设一个标的价格在10元成交了100手,在11元成交了300手。那平均成本不是简单的(10+11)/2=10.5元,而是(10×100+11×300)/(100+300)=10.75元。

看出门道了吗?成交越多的地方,对平均价格的影响越大。11元成交了更多,所以平均成本更靠近11元。这就是市场真实的”成本重心”。

我当时的灵光一闪是:如果不用单日的VWAP,而是把很多天的VWAP再做一次移动平均呢?这样既能保留”成交密集区更重要”这个特性,又能平滑短期噪音。

于是我的”SMVWAP”(简单移动VWAP)诞生了:

  • 短期SMVWAP:用2-60天的周期,捕捉近期动态;
  • 长期SMVWAP:用100-250天的周期,识别大方向。

核心规则就一句话:只有当长期SMVWAP向上倾斜时,才考虑参与多头机会;如果它向下走,那就乖乖看戏。

就这么一条简单的过滤,把那些”在下跌趋势里硬要抢反弹”的糟糕操作干掉了大半。要知道,会躲坑本身就是一门被严重低估的技能。

VWAP支撑阻力

图2:VWAP在实战中常扮演动态支撑或阻力角色 | 来源:TrueData

三、标准差:给策略加上”减震器”

解决了趋势方向的问题,但还有一个麻烦:即使在上升趋势里,价格也会上蹿下跳,围绕SMVWAP来回折腾。这时候如果每个小波动都跟着动,会被震得七荤八素。

这时候我引入了标准差这个统计工具。说白了,它就是告诉你”这东西平时一般晃悠多大范围”。

举个例子:假设过去20天,收盘价和长期SMVWAP的平均距离是2元,标准差是1元。那我可以设定一个门槛:只有当价格偏离SMVWAP达到0.7个标准差以上时,才考虑行动。

计算示例: 

门槛 = SMVWAP + 0.7 × 标准差 = SMVWAP + 0.7 × 1元 = SMVWAP + 0.7元

这个数字看着小,但在流动性好的市场里,足以过滤掉大量无意义的噪音。市场躁动时,价格可能在SMVWAP附近±0.5元来回晃悠,但达不到0.7元的门槛,系统就不会被诱骗。

我在代码里让这个”标准差偏移量”可以在-2到+2之间调整,步长0.1。正值意味着追逐更剧烈的波动,负值则要求价格更贴近趋势线,追求稳健和干净的交易机会。

图3:利用波动率收缩和突破寻找高概率时机 | 来源:Dot Net Tutorials

四、那个让我惊出一身汗的回测结果

把短期SMVWAP、长期SMVWAP和标准差偏移这三个组件串起来,我扔进了优化器。这玩意儿就像个不知疲倦的实习生,能帮你测试几千种参数组合,而你只需要在旁边喝咖啡。

在某个主流资产的多年度数据上,最优参数组合是:

组件最优参数含义
短期SMVWAP16周期约3周左右的短期视角
长期SMVWAP189周期约9个月的大趋势判断
标准差偏移-0.7偏向保守,要求价格更贴近趋势

回测结果如下(请注意,这只是历史数据模拟):

指标动量策略简单持有差异
总交易次数40次1次
成功次数占比约48%100%不到一半
累计增长约10,000%约1,400%7倍左右
期末资金(1,000元起步)约101,000元约15,000元6.7倍
在场时间675天1,364天仅50%

最有意思的是最后那行:系统在1,364天中只参与了675天,剩下近2年都是空仓状态。这意味着它避开了大量的市场回调和震荡期。

重要提醒:看到这里你可能会心动,但我必须泼盆冷水。任何回测结果都可能是”过度优化”的产物——就是让历史数据拟合得太完美,实战却一塌糊涂。看到漂亮的收益曲线,第一反应应该是怀疑,而不是兴奋。

回测收益曲线

图4:典型的策略回测收益曲线示例(非本策略实际结果,仅供示意) 

五、如何避免”数据拟合”的坑

我不建议任何人直接用上面那组参数。真正值得借鉴的是思路,不是数字。这里提供一个简化版的三步落地法:

第一步:选对标的

挑一个你熟悉的、流动性好的标的。可以是主流指数ETF、大型标的,或者你长期关注的品种。流动性差的东西,VWAP的意义会大打折扣。

第二步:搭建双周期框架

在日K线图上计算两条线:

  • 长期线:150-250天的VWAP移动平均,判断大方向。
  • 短期线:10-30天的VWAP移动平均,寻找时机。

第三步:加入波动缓冲

计算价格与长期线的偏离程度,只有当偏离超过一定阈值(比如用标准差衡量)时才行动。这个阈值要保守起步,宁可错过一些机会,也别被假信号反复打脸。

如果你不会写代码,TradingView这类平台基本都能实现这个逻辑。实在不行,用Excel手动算几组数据,也能感受其中的规律。

六、我现在是怎么用这套东西的

说实话,我现在并没有用那组”最优参数”做实盘。它现在对我有两个更实际的用途:

第一,当作市场环境过滤器。如果长期VWAP线往下走,我会自动降低参与意愿,或者干脆观望。这个动作帮我避免了无数次”接飞刀”的冲动。

第二,当作独立想法的校验器。如果我看中某个机会,但长期趋势线是平的或向下的,那面黄旗就会亮起。这种情况下即使参与,我也会降低预期,当成试探而非主攻。

这里有个可能得罪人的观点:大多数散户太在意”赢的次数”,却不够在意”资金暴露在风险中的时间”。在我的回测里,把参与时间砍掉一半,比把成功率从40%提到50%重要得多。

这套思路可以延伸出很多变体:应用到一篮子标的、设置收盘前提醒、打包成指标自动监控……但核心始终是那句话:区分真信号和噪音,区分该行动和该等待。

给新手的建议:别急着找”最优参数”。先用肉眼观察,在历史图表上手动标记几个信号,体会一下这个逻辑的节奏。这种手工回测的直觉培养,比跑一万次程序都有价值。

七、几个实用的操作原则

如果你打算尝试类似思路,这里有几条我踩坑踩出来的经验:

原则具体做法为什么重要
把VWAP当成本线价格在线上=多数人获利,市场偏强;在线下=多数人被套,市场偏弱它反映的是真金白银的聚集区,不是随便画的线
双周期搭配长期定方向,短期找机会,再加个波动缓冲避免在错误的时间做正确的事
看三个维度收益、回撤、在场时间,三个一起看高收益如果伴随高回撤和长期在场,风险可能很大
手动验证至少在历史数据上手动标记10-20个信号培养直觉,发现程序可能忽略的细节
保守起步偏移量设得保守些,宁可错过别做错假信号的成本往往比错过机会高得多

八、关于过度优化的冷思考

我得再强调一下过度优化(Overfitting)这个问题。我见过太多人在历史数据上把参数调得天花乱坠,实战一塌糊涂。

典型的过度优化长这样:

  • 参数稍微变一点,收益断崖式下跌;
  • 加入一堆条件后,回测曲线漂亮得不像真的;
  • 回测胜率70%,实盘却连50%都保不住。

怎么识别?做个简单的参数敏感性测试。如果你的最优参数是13周期,试试12和14。如果收益从50%暴跌到10%,那这个参数可能就是”孤岛”——只适合那段特定的历史数据,换个环境就失效。

正确的做法是:在训练集上找参数,在测试集上验证,然后用滚动窗口持续检验。回测只是入学考试,实盘才是终身学习。

波动率突破交易

图5:波动率突破交易的完整流程示意 | 来源:TradingView

写在最后

这篇文章的核心不是让你抄我的参数,而是分享一个思路:真正的优势不在于预测得多准,而在于进场有多精、等待有多耐心、过滤有多严格。

当你发现长期趋势指标向下时,无论你的主观判断多么乐观,都请先让 ego(自负)退一步。市场不会因为你的坚持而 rewarded(奖励)你。

记住这个小规则:”当我的长期趋势线向下时,我没有资格看多。”

附录:Python代码手把手教学

这里我给大家提供一个简化的Python代码示例。

环境准备

首先你需要安装必要的库:

pip install pandas numpy yfinance matplotlib

第一步:获取数据

import pandas as pd

import numpy as np

import yfinance as yf

import matplotlib.pyplot as plt


# 设置中文显示

plt.rcParams['font.sans-serif'] = ['SimHei']

plt.rcParams['axes.unicode_minus'] = False


# 获取数据(以沪深300ETF为例)

ticker = "510300.SS"  # 沪深300ETF

data = yf.download(ticker, start="2020-01-01", end="2024-01-01")


print(data.head())

第二步:计算VWAP

def calculate_vwap(df):

    """

    计算VWAP(成交量加权平均价)

    """

    # 典型价格 = (最高价 + 最低价 + 收盘价) / 3

    df['Typical_Price'] = (df['High'] + df['Low'] + df['Close']) / 3


    # 价格×成交量

    df['Price_Volume'] = df['Typical_Price'] * df['Volume']


    # 累计价格×成交量

    df['Cumulative_PV'] = df['Price_Volume'].cumsum()


    # 累计成交量

    df['Cumulative_Volume'] = df['Volume'].cumsum()


    # VWAP = 累计(价格×成交量) / 累计成交量

    df['VWAP'] = df['Cumulative_PV'] / df['Cumulative_Volume']


    return df


data = calculate_vwap(data)

print(data[['Close', 'VWAP']].tail())

第三步:计算SMVWAP(VWAP的移动平均)

def calculate_smvwap(df, short_period=16, long_period=189):

    """

    计算SMVWAP(VWAP的简单移动平均)

    """

    # 先计算每日VWAP(这里简化为典型价格)

    df['Typical_Price'] = (df['High'] + df['Low'] + df['Close']) / 3


    # 短期SMVWAP

    df['Short_SMVWAP'] = df['Typical_Price'].rolling(window=short_period).mean()


    # 长期SMVWAP

    df['Long_SMVWAP'] = df['Typical_Price'].rolling(window=long_period).mean()


    return df


data = calculate_smvwap(data)

print(data[['Close', 'Short_SMVWAP', 'Long_SMVWAP']].tail())

第四步:计算标准差和交易信号

def generate_signals(df, std_shift=-0.7):

    """

    生成交易信号

    """

    # 计算价格偏离长期SMVWAP的标准差

    df['Price_Deviation'] = df['Close'] - df['Long_SMVWAP']

    df['Std_Dev'] = df['Price_Deviation'].rolling(window=20).std()


    # 计算进场阈值

    df['Entry_Threshold'] = df['Long_SMVWAP'] + (std_shift * df['Std_Dev'])


    # 判断长期趋势方向(长期SMVWAP是否向上)

    df['Trend_Up'] = df['Long_SMVWAP'] > df['Long_SMVWAP'].shift(1)


    # 生成信号

    df['Signal'] = 0


    # 买入信号:长期趋势向上 且 价格突破短期SMVWAP

    df.loc[(df['Trend_Up']) & 

           (df['Close'] > df['Short_SMVWAP']) & 

           (df['Close'] > df['Entry_Threshold']), 'Signal'] = 1


    # 卖出信号:长期趋势向下 或 价格跌破短期SMVWAP

    df.loc[(~df['Trend_Up']) | 

           (df['Close'] < df['Short_SMVWAP']), 'Signal'] = -1


    return df


data = generate_signals(data)

print(data[['Close', 'Signal']].tail(20))

第五步:回测策略

def backtest_strategy(df, initial_capital=100000):

    """

    简单回测

    """

    df['Position'] = df['Signal'].replace(-1, 0)  # 1表示持仓,0表示空仓

    df['Position'] = df['Position'].shift(1).fillna(0)  # 信号延迟一天执行


    # 计算每日收益率

    df['Daily_Return'] = df['Close'].pct_change()


    # 计算策略收益率

    df['Strategy_Return'] = df['Position'] * df['Daily_Return']


    # 计算累计收益

    df['Cumulative_Market_Return'] = (1 + df['Daily_Return']).cumprod()

    df['Cumulative_Strategy_Return'] = (1 + df['Strategy_Return']).cumprod()


    # 计算最终收益

    final_market_value = initial_capital * df['Cumulative_Market_Return'].iloc[-1]

    final_strategy_value = initial_capital * df['Cumulative_Strategy_Return'].iloc[-1]


    print(f"初始资金: {initial_capital:,.0f}元")

    print(f"买入持有最终价值: {final_market_value:,.0f}元")

    print(f"策略最终价值: {final_strategy_value:,.0f}元")

    print(f"买入持有收益率: {(final_market_value/initial_capital - 1)*100:.2f}%")

    print(f"策略收益率: {(final_strategy_value/initial_capital - 1)*100:.2f}%")


    # 计算在市场中的天数

    days_in_market = df['Position'].sum()

    total_days = len(df)

    print(f"在市场中的天数: {days_in_market}/{total_days} ({days_in_market/total_days*100:.1f}%)")


    return df


data = backtest_strategy(data)

第六步:可视化结果

def plot_results(df):

    """

    绘制回测结果

    """

    fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(15, 12))


    # 图1: 价格和SMVWAP

    ax1.plot(df.index, df['Close'], label='收盘价', alpha=0.7)

    ax1.plot(df.index, df['Short_SMVWAP'], label='短期SMVWAP(16)', alpha=0.7)

    ax1.plot(df.index, df['Long_SMVWAP'], label='长期SMVWAP(189)', alpha=0.7)

    ax1.set_title('价格与SMVWAP指标', fontsize=14, fontweight='bold')

    ax1.set_ylabel('价格(元)')

    ax1.legend()

    ax1.grid(True, alpha=0.3)


    # 图2: 交易信号

    buy_signals = df[df['Signal'] == 1]

    sell_signals = df[df['Signal'] == -1]


    ax2.plot(df.index, df['Close'], label='收盘价', alpha=0.5, color='gray')

    ax2.scatter(buy_signals.index, buy_signals['Close'], 

                color='green', marker='^', s=100, label='买入信号', zorder=5)

    ax2.scatter(sell_signals.index, sell_signals['Close'], 

                color='red', marker='v', s=100, label='卖出信号', zorder=5)

    ax2.set_title('交易信号', fontsize=14, fontweight='bold')

    ax2.set_ylabel('价格(元)')

    ax2.legend()

    ax2.grid(True, alpha=0.3)


    # 图3: 累计收益对比

    ax3.plot(df.index, (df['Cumulative_Market_Return'] - 1) * 100, 

             label='买入持有', linewidth=2)

    ax3.plot(df.index, (df['Cumulative_Strategy_Return'] - 1) * 100, 

             label='动量策略', linewidth=2)

    ax3.set_title('累计收益率对比', fontsize=14, fontweight='bold')

    ax3.set_xlabel('日期')

    ax3.set_ylabel('收益率(%)')

    ax3.legend()

    ax3.grid(True, alpha=0.3)


    plt.tight_layout()

    plt.savefig('动量策略回测结果.png', dpi=300, bbox_inches='tight')

    plt.show()


    print("\n图表已保存为: 动量策略回测结果.png")


plot_results(data)

完整代码使用说明

  1. 复制上面所有代码块,按顺序粘贴到一个Python文件中(比如momentum_strategy.py)
  2. 运行代码: python momentum_strategy.py
  3. 查看结果:
    • 控制台会输出回测统计数据;
    • 会生成一张包含三个子图的图表;
    • 图表会自动保存为PNG文件。

参数调优建议

你还可以尝试调整这些参数:

# 在calculate_smvwap函数中
short_period = 16    # 短期周期,可以尝试10-30
long_period = 189    # 长期周期,可以尝试150-250

# 在generate_signals函数中
std_shift = -0.7     # 标准差偏移,可以尝试-2到+2

注意事项

  1. 这是一个简化版本,实际交易需要考虑:
    • 交易手续费
    • 滑点
    • 仓位管理
    • 止损止盈
  2. 历史回测不代表未来收益,务必在模拟盘充分测试后再考虑实盘。
  3. 代码中的数据来自Yahoo Finance,可能有延迟,实盘请使用实时数据源。
  4. 建议先在小资金上测试,逐步增加仓位。

#量化交易 #技术分析 #VWAP指标 #趋势跟踪 #波动率策略 #交易系统的构建 #回测方法 #风险管理

感谢阅读!愿本文为您带来新启发与实用知识。若觉有益,请点赞分享,您的支持是我创作的动力,欢迎留言必复。


风险提示:文仅供参考,不构成投资建议。量化策略开发应以学习和技术交流为目的。投资有风险,入市需谨慎。

Published inAI&Invest专栏

Be First to Comment

    发表回复