量化趋势分析:基于Python的统计建模·进阶篇
📊

量化趋势分析:基于Python的统计建模·进阶篇

(2/3)· 从数据获取到滑动窗口识别,把EURUSD与GBPUSD的趋势规律变成可验证的数字

进阶 第 2/3 篇
很多交易者嘴上说着「跟随趋势」,却从没算过自己盯的货币对上涨和下跌平均能跑几天。靠肉眼数 K 线容易把噪声当信号,用 Python 拉历史数据跑一遍统计,雾里的路才会显形。

◍ 滑动窗口凭什么快

把局部高点定义为窗口里比左右邻居都高的那根 K 线,这套逻辑不用训练、不用拟合,盯着相邻坐标就能判,读起来比任何形态学规则都直白。 计算上它只维护一个定长队列,每来一根新柱只做窗口内的一次扫描,MT5 跑 1 分钟图 10 万根历史柱,iBar 循环耗时通常在几十毫秒级,远快于重算全样本的趋势算法。 窗口大小直接决定灵敏度:W=5 容易把毛刺当拐点,W=20 能滤噪但会滞后 3~5 根柱。外汇与贵金属波动跳变频繁,参数选错可能漏掉真实突破,属高风险调试项。 它也有死角——某些快速反抽的 V 形底并不满足「邻侧皆低」的极值定义,会被算法跳过。后续可接小波或时序分割做补识别,但当前版本就靠调窗口救场。

「把趋势数据跑成可验证的统计报表」

趋势识别只是第一步,真正能指导仓位的是对历史趋势的量化剖面。上面这段 Python 风格的处理函数,演示了如何把一段段趋势记录聚合成均值、极值与方向分布,并落盘成 txt 供复盘。 函数先判空,若 trend_df 无记录直接退出,避免后续除零。随后在输出目录建 trend_statistics.txt,写总样本数、全样本平均持续时间(天数,保留两位小数)、平均点数与平均百分比——这三个数能让你快速判断某品种趋势是「短促暴力」还是「绵长温和」。 方向拆分才是重点:up_trends 与 down_trends 分别计数并算均值,上行趋势额外输出最大/最小点数。若你跑 EURUSD H1 近一年数据,可能看到上行均值 80 点、下行均值 120 点,这种非对称本身就是外汇高波动时段的预警信号。 让小布替你跑这套 把这段代码塞进你的分析脚本,换上自己导出的 MT5 历史趋势 CSV,重点比对多空均值差。若差值持续偏离历史中位数,后续价格反转概率倾向升高,但外汇/贵金属杠杆风险极高,仅作概率参考。

MQL5 / C++
def analyze_trends(trend_df, output_dir):
    """Trend analysis and statistics printing with saving of results"""
    if trend_df.empty:
        print("No data available for trend analysis")
        class="kw">return
    
    # Create file to save statistics
    stats_file = os.path.join(output_dir, &class="macro">#x27;trend_statistics.txt&class="macro">#x27;)
    with open(stats_file, &class="macro">#x27;w&class="macro">#x27;, encoding=&class="macro">#x27;utf-class="num">8&class="macro">#x27;) as f:
        # Basic statistics on all trends
        header = "=" * class="num">50 + "\nOverall trend statistics:\n" + "=" * class="num">50
        print(header)
        f.write(header + "\n")
        
        stats_text = f"Total number of trends: {len(trend_df)}\n"
        stats_text += f"Mean trend duration: {trend_df[&class="macro">#x27;duration&class="macro">#x27;].mean():.2f} days\n"
        stats_text += f"Mean trend value: {trend_df[&class="macro">#x27;points&class="macro">#x27;].mean():.2f} points\n"
        stats_text += f"Mean trend value: {trend_df[&class="macro">#x27;percentage&class="macro">#x27;].mean():.2f}%\n"
        
        print(stats_text)
        f.write(stats_text)
        
        # Statistics on trend types
        up_trends = trend_df[trend_df[&class="macro">#x27;type&class="macro">#x27;] == &class="macro">#x27;Uptrend&class="macro">#x27;]
        down_trends = trend_df[trend_df[&class="macro">#x27;type&class="macro">#x27;] == &class="macro">#x27;Downtrend&class="macro">#x27;]
        
        up_stats = "Statistics on upward trends:\n"
        up_stats += f"Quantity: {len(up_trends)}\n"
        if not up_trends.empty:
            up_stats += f"Mean duration: {up_trends[&class="macro">#x27;duration&class="macro">#x27;].mean():.2f} days\n"
            up_stats += f"Mean value: {up_trends[&class="macro">#x27;points&class="macro">#x27;].mean():.2f} points\n"
            up_stats += f"Mean value: {up_trends[&class="macro">#x27;percentage&class="macro">#x27;].mean():.2f}%\n"
            up_stats += f"Maximum value: {up_trends[&class="macro">#x27;points&class="macro">#x27;].max():.2f} points\n"
            up_stats += f"Minimum value: {up_trends[&class="macro">#x27;points&class="macro">#x27;].min():.2f} points\n"
        
        print(up_stats)
        f.write(up_stats)
        
        # Statistics on downtrends and other categories of trends...

趋势图表的三层拆解视角

把历史趋势丢进图表,比看裸 K 线更容易抓到统计规律。这套分析逻辑围绕三个维度展开:幅度、持续时间、以及两者之间的关系。 幅度直方图按点数把上涨和下跌趋势分箱(默认 10 个 bin),你能直接看到某货币对趋势大多落在哪个点数区间。比如 EURUSD 日线若多数趋势集中在 200~400 点,那把单笔盈利预期硬设到 800 点就偏离了样本分布,实际持仓目标可能要往低了调。 持续时间直方图给出趋势活了几天。结合幅度图交叉看,若样本里 70% 的趋势在 5~15 天内走完,你却习惯扛单一个月,持仓周期就和外汇市场的高风险统计特征拧着了。 相关性散点图把每个趋势的「天数」和「点数」摊开成绿红两点云。如果点的分布明显向右上倾斜,说明拖得越久倾向走出越大级别趋势;若乱成一团,持续时间对终值的预测力就弱。下面这段 Python 负责把三张图落盘,逻辑可直接搬到本地回测脚本里跑。

MQL5 / C++
<span class="keyword">def</span> save_distribution_plots(trend_df, output_dir):
&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"""Creating and saving additional distribution charts"""</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># class="num">1. Chart of trend distribution by magnitude</span>
&nbsp;&nbsp;&nbsp;&nbsp;plt.figure(figsize=(<span class="number">class="num">12</span>, <span class="number">class="num">8</span>))
&nbsp;&nbsp;&nbsp;&nbsp;up_trends = trend_df[trend_df[<span class="class="type">class="kw">string">&class="macro">#x27;type&class="macro">#x27;</span>] == <span class="class="type">class="kw">string">&class="macro">#x27;Uptrend&class="macro">#x27;</span>]
&nbsp;&nbsp;&nbsp;&nbsp;down_trends = trend_df[trend_df[<span class="class="type">class="kw">string">&class="macro">#x27;type&class="macro">#x27;</span>] == <span class="class="type">class="kw">string">&class="macro">#x27;Downtrend&class="macro">#x27;</span>]
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;plt.hist([up_trends[<span class="class="type">class="kw">string">&class="macro">#x27;points&class="macro">#x27;</span>], down_trends[<span class="class="type">class="kw">string">&class="macro">#x27;points&class="macro">#x27;</span>]], 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bins=<span class="number">class="num">10</span>, alpha=<span class="number">class="num">0.7</span>, class="type">class="kw">color=[<span class="class="type">class="kw">string">&class="macro">#x27;green&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;red&class="macro">#x27;</span>], 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; label=[<span class="class="type">class="kw">string">&class="macro">#x27;Uptrends&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;Downtrends&class="macro">#x27;</span>])
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;plt.title(<span class="class="type">class="kw">string">&class="macro">#x27;Distribution of trend values(in points)&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.xlabel(<span class="class="type">class="kw">string">&class="macro">#x27;Trend value(points)&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.ylabel(<span class="class="type">class="kw">string">&class="macro">#x27;Number of trends&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.grid(<span class="literal">True</span>, alpha=<span class="number">class="num">0.3</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.legend()
&nbsp;&nbsp;&nbsp;&nbsp;plt.tight_layout()
&nbsp;&nbsp;&nbsp;&nbsp;plt.savefig(os.path.join(output_dir, <span class="class="type">class="kw">string">&class="macro">#x27;trend_magnitude_distribution.png&class="macro">#x27;</span>))
&nbsp;&nbsp;&nbsp;&nbsp;plt.close()
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># class="num">2. Chart of trend distribution by duration</span>
&nbsp;&nbsp;&nbsp;&nbsp;plt.figure(figsize=(<span class="number">class="num">12</span>, <span class="number">class="num">8</span>))
&nbsp;&nbsp;&nbsp;&nbsp;plt.hist([up_trends[<span class="class="type">class="kw">string">&class="macro">#x27;duration&class="macro">#x27;</span>], down_trends[<span class="class="type">class="kw">string">&class="macro">#x27;duration&class="macro">#x27;</span>]], 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bins=<span class="number">class="num">10</span>, alpha=<span class="number">class="num">0.7</span>, class="type">class="kw">color=[<span class="class="type">class="kw">string">&class="macro">#x27;green&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;red&class="macro">#x27;</span>], 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; label=[<span class="class="type">class="kw">string">&class="macro">#x27;Uptrends&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;Downtrends&class="macro">#x27;</span>])
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;plt.title(<span class="class="type">class="kw">string">&class="macro">#x27;Distribution of trend duration(in days)&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.xlabel(<span class="class="type">class="kw">string">&class="macro">#x27;Trend duration(days)&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.ylabel(<span class="class="type">class="kw">string">&class="macro">#x27;Number of trends&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.grid(<span class="literal">True</span>, alpha=<span class="number">class="num">0.3</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.legend()
&nbsp;&nbsp;&nbsp;&nbsp;plt.tight_layout()
&nbsp;&nbsp;&nbsp;&nbsp;plt.savefig(os.path.join(output_dir, <span class="class="type">class="kw">string">&class="macro">#x27;trend_duration_distribution.png&class="macro">#x27;</span>))
&nbsp;&nbsp;&nbsp;&nbsp;plt.close()
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># class="num">3. Chart of the ratio of duration and magnitude of trends</span>
&nbsp;&nbsp;&nbsp;&nbsp;plt.figure(figsize=(<span class="number">class="num">12</span>, <span class="number">class="num">8</span>))
&nbsp;&nbsp;&nbsp;&nbsp;plt.scatter(up_trends[<span class="class="type">class="kw">string">&class="macro">#x27;duration&class="macro">#x27;</span>], up_trends[<span class="class="type">class="kw">string">&class="macro">#x27;points&class="macro">#x27;</span>], 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;green&class="macro">#x27;</span>, alpha=<span class="number">class="num">0.7</span>, label=<span class="class="type">class="kw">string">&class="macro">#x27;Uptrends&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.scatter(down_trends[<span class="class="type">class="kw">string">&class="macro">#x27;duration&class="macro">#x27;</span>], down_trends[<span class="class="type">class="kw">string">&class="macro">#x27;points&class="macro">#x27;</span>], 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;red&class="macro">#x27;</span>, alpha=<span class="number">class="num">0.7</span>, label=<span class="class="type">class="kw">string">&class="macro">#x27;Downtrends&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;plt.title(<span class="class="type">class="kw">string">&class="macro">#x27;Ratio of duration and magnitude of trends&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.xlabel(<span class="class="type">class="kw">string">&class="macro">#x27;Trend duration(days)&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.ylabel(<span class="class="type">class="kw">string">&class="macro">#x27;Trend magnitude(points)&class="macro">#x27;</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.grid(<span class="literal">True</span>, alpha=<span class="number">class="num">0.3</span>)
&nbsp;&nbsp;&nbsp;&nbsp;plt.legend()
&nbsp;&nbsp;&nbsp;&nbsp;plt.tight_layout()
&nbsp;&nbsp;&nbsp;&nbsp;plt.savefig(os.path.join(output_dir, <span class="class="type">class="kw">string">&class="macro">#x27;trend_correlation.png&class="macro">#x27;</span>))
&nbsp;&nbsp;&nbsp;&nbsp;plt.close()

◍ 把趋势直接画进价格图

光看统计分布不够直观,把算法识别出的每段趋势叠到 EURUSD 收盘价图上,才能快速判断拐点抓得准不准。下面这段 Python 可视化函数把主图分成上下两栏,上栏画价格与趋势线,下栏留作分布图。 函数开头建了 15×10 英寸画布,主图用 plt.subplot(2,1,1) 占位,收盘价以蓝色半透明线绘制,alpha=0.7 避免遮挡后续标记。 遍历 trend_df 时,上升段起点标绿、终点标红,下降段反之;趋势本身用橙色虚线连接起止点,alpha=0.5 让线弱化为参考而非干扰。散点 s=50 足够在日线图肉眼分辨。 实盘验证时,把 output_dir 接上你导出的 EURUSD 1H csv,跑完看图:若橙色虚线频繁穿越实体而非贴合影线,说明极值判定阈值可能过松,外汇品种杠杆高、假突破多,需回测调参后再上模拟盘。

MQL5 / C++
def plot_trends(df, trend_df, output_dir):
    """Visualization of trends on a chart with saving"""
    plt.figure(figsize=(class="num">15, class="num">10))
    
    # Main price chart
    plt.subplot(class="num">2, class="num">1, class="num">1)
    plt.plot(df.index, df[&class="macro">#x27;close&class="macro">#x27;], label=&class="macro">#x27;EURUSD Close&class="macro">#x27;, class="type">class="kw">color=&class="macro">#x27;blue&class="macro">#x27;, alpha=class="num">0.7)
    plt.title(&class="macro">#x27;EURUSD Trend Analysis&class="macro">#x27;)
    plt.grid(True, alpha=class="num">0.3)
    plt.legend()
    
    # Mark local highs and lows
    for _, trend in trend_df.iterrows():
        start_color = &class="macro">#x27;green&class="macro">#x27; if trend[&class="macro">#x27;type&class="macro">#x27;] == &class="macro">#x27;Uptrend&class="macro">#x27; else &class="macro">#x27;red&class="macro">#x27;
        end_color = &class="macro">#x27;red&class="macro">#x27; if trend[&class="macro">#x27;type&class="macro">#x27;] == &class="macro">#x27;Downtrend&class="macro">#x27; else &class="macro">#x27;green&class="macro">#x27;
        
        # Starting point of trend
        plt.scatter(trend[&class="macro">#x27;start_date&class="macro">#x27;], trend[&class="macro">#x27;start_price&class="macro">#x27;], class="type">class="kw">color=start_color, s=class="num">50)
        
        # Endpoint of trend
        plt.scatter(trend[&class="macro">#x27;end_date&class="macro">#x27;], trend[&class="macro">#x27;end_price&class="macro">#x27;], class="type">class="kw">color=end_color, s=class="num">50)
        
        # Trend line
        plt.plot([trend[&class="macro">#x27;start_date&class="macro">#x27;], trend[&class="macro">#x27;end_date&class="macro">#x27;]], 
                 [trend[&class="macro">#x27;start_price&class="macro">#x27;], trend[&class="macro">#x27;end_price&class="macro">#x27;]], 
                 class="type">class="kw">color=&class="macro">#x27;orange&class="macro">#x27;, alpha=class="num">0.5, linestyle=&class="macro">#x27;--&class="macro">#x27;)
    
    # The second distribution chart
    # ...

「给趋势加几层概率滤镜」

平均值和分布只是趋势画像的底稿。要把趋势特征看透,得把季节性、状态转移和跨品种联动这几层统计结构叠上去,才能在 MT5 里做出有条件的策略调整。 不少货币对存在季节性倾向。公开统计显示,EURUSD 在夏季几个月的波动率与趋势幅度往往走低,顺势持仓的盈亏比可能随之压缩。用分月回测把这段窗口单独拎出来,能看出策略夏冬两季的表现断层。 马尔可夫链适合给趋势状态建模:当前是下降段,下一步翻成上涨的概率有多少,下一段幅度是否吃前一段的老本,都能用状态转移矩阵算。它给的是概率预测,不是方向保证,外汇和贵金属杠杆高,错判状态一样会快速吞掉本金。 跨品种联动也值得盯。EURUSD 起强劲上行时,GBPUSD 通常跟着走类似结构。把两对的趋势方向序列做交叉相关,能为多货币对冲或共振入场提供量化依据。

把统计分布塞进下单与参数里

EURUSD 的上升波段均值约 280 点、下降波段均值约 220 点,这组数不是拿来写报告的,而是直接改你的止盈止损距离:多头目标可以贴着 280 点附近摆,空头则参考 220 点,而不是拍脑袋给个整数。 指标周期也别再用 14、20、50 这种出厂默认值糊弄。量化出来的 EURUSD 趋势平均持续 15 天,那 15 周期均线就比标准 20 周期更贴市;快线压在 8–10 天、慢线放在 16–20 天,ADX 用 14 天跟踪趋势,Parabolic SAR 设 step=0.02、maximum=0.2,整体和识别出的趋势特征对得上,指标表现可能更稳。 持仓时间也能用分布来管。按趋势典型持续时间开发退出逻辑:到了给定天数还没打到目标,就自动平仓,避免浮盈被反转吞掉。更实用的做法是分三批——小利润先走一部分锁确定性,到平均趋势价位走第二部分,剩下挂跟踪止损赌大行情。 自适应系统才是量化分析的落点。高波动期平均趋势值可能涨 40%,系统侦测到就该自动放大盈利目标;若统计显示上涨趋势比下跌更长更陡,就给两类趋势套不同进出场规则。外汇和贵金属杠杆高,这类参数切换只是降低盲猜概率,不保证胜率。

把多周期统计交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到不同时间框架下的趋势持续天数与幅度分布,你专注决策就好。

常见问题

参数较小时算法会捕捉大量短期趋势,噪声增多;参数较大时只保留长期走势,适合波段与持仓周期更长的策略,需按交易风格调整。
可以,小布盯盘的品种页内置了多周期趋势特征的分布视图,省去自己写 Python 拉数据的步骤,但外汇贵金属波动高风险,统计仅作概率参考。
不同时间框架下降噪程度和极值定义差异很大,D1 适合长期策略,M15 适合日内,混用会导致止盈与风控周期错配。
针对特定时段和货币对做假设检验,计算趋势延续比例与平均幅度,数据不支持的命题就别放进策略里。
滑动窗口逻辑直观且只需局部遍历,在历史数据回看中计算开销明显低于全局优化类算法,具体提速依数据量而定。