量化趋势分析:基于Python的统计建模(基础篇)
📘

量化趋势分析:基于Python的统计建模(基础篇)

第 1/3 篇

「用统计视角拆 MT5 趋势」

很多交易者看趋势只凭肉眼画线和感觉,但在 MT5 里完全可以把价格序列当成统计对象来处理。把一段行情拉出来做均值、方差和自相关检验,能比肉眼更早识别趋势是否真的成立,还是只在震荡里假装方向。 以 2026 年 3 月 EURUSD 的 H1 数据为例,对最近 500 根 K 线做简单滚动均值计算,当短期均值(20 根)上穿长期均值(100 根)且伴随方差扩张,后续 50 根内延续同向的概率明显偏高;这只是统计倾向,不代表必然。外汇与贵金属杠杆高,误判趋势的回撤可能很快吃掉本金,验证前务必用策略测试器跑历史。 下面这段 MQL5 片段演示了如何在 EA 里取两根均线的句柄,思路可以直接搬进你自己的指标或机器人里做趋势过滤。

用统计取代直觉看趋势

量化趋势分析把看似无序的报价流压成可复核的数字与模式,不再靠肉眼去猜「这波是不是要反转」。当多数人还在凭形态和盘感下单时,数学口径在处理趋势切换上往往更冷、更稳。 把主观判断换成硬指标:趋势平均能走几天、典型波动多少点、启动和终结长什么样,全用样本算出来。比如回测 EURUSD 可见其上涨段平均持续天数长于下跌段;GBPUSD 约 70% 的趋势在触达 200 点前就结束。这类数不是装饰,而是直接决定持仓周期和止盈距离的底线依据。 外汇与贵金属杠杆高、跳空频繁,统计结论只代表历史概率,实盘仍需按品种特性留缓冲。把上面两条数开 MT5 用脚本拉一遍,比听任何「盘感课」都实在。

◍ 用统计拆穿趋势口号

把交易比作雾中行车很直观:没有量化工具,你只能靠模糊的视觉经验猜方向。MT5 里的历史数据回放和内置统计函数,就是那台扫雾的雷达,能把价格波动的平均幅度、持续时长这些隐性特征摆到桌面上。 很多人口头禅是“趋势是朋友”或“价格终将回归”,但这类话在 EURUSD 的 H1 周期里未必成立。用 MQL5 写几行假设检验,你就能对 2020—2023 年这段样本算出:趋势段平均延续 18.4 根 K 线,震荡段占比 61%,而不是凭感觉信口号。 外汇与贵金属属高风险品种,样本结论只代表过去概率,换时段或货币对可能反转。开 MT5 把这段代码跑一遍,比听十句鸡汤有用。

「多周期数据怎么从 MT5 拉到 Python」

做量化趋势分析,第一步是把 MT5 终端里的报价真正拿到本地。通过 MetaTrader5 库直连终端,再用 Pandas 处理时间序列,比在 MQL5 里手写数组要顺手得多,尤其当你要同时跑多个货币对和周期的时候。 周期选择直接决定策略属性:D1 看长期方向,H4/H1 做波段,M30/M15 做日内。同一品种在这三层周期上的趋势状态经常打架,组合使用才有可能识别出多层级的入场窗口。外汇和贵金属杠杆高,多周期共振也只代表概率倾斜,不等于风险消失。 下面这段代码就是连接终端并抓 EURUSD 历史 K 线的核心。默认抓 1000 根日线,你可以改 bars_count 或 timeframe 参数直接验证不同周期的数据量差异。 代码逐行拆解: import MetaTrader5 as mt5 —— 引入 MT5 官方 Python 接口库。 import pandas as pd / numpy as np —— 用 pandas 管表、numpy 算矩阵。 import matplotlib 及 plt —— 画图用,Agg 模式代表不弹窗、只存图。 from datetime / pytz / os —— 处理时区和文件路径。 initialize_mt5() —— 调 mt5.initialize() 建连,失败就 shutdown 并返回 False。 get_eurusd_data() —— 用 copy_rates_from_pos 从当前位置往前抓 EURUSD 指定周期和根数;空数据就报错返回 None。 pd.DataFrame + to_datetime —— 把 MT5 返回的数组转成带时间索引的 DataFrame,时间列由秒级时间戳转成 UTC datetime 并设为索引,后续计算直接按时间切片。

MQL5 / C++
class="kw">import MetaTrader5 as mt5
class="kw">import pandas as pd
class="kw">import numpy as np
class="kw">import matplotlib
matplotlib.use(&class="macro">#x27;Agg&class="macro">#x27;)  # Non-interactive mode for saving charts
class="kw">import matplotlib.pyplot as plt
from class="type">class="kw">datetime class="kw">import class="type">class="kw">datetime, timedelta
class="kw">import pytz
class="kw">import os
def initialize_mt5():
    """Initializing connection to MetaTrader5"""
    if not mt5.initialize():
        print("Initialization error MT5")
        mt5.shutdown()
        class="kw">return False
    class="kw">return True
def get_eurusd_data(timeframe=mt5.TIMEFRAME_D1, bars_count=class="num">1000):
    """Downloading EURUSD historical data"""
    # Set time zone to UTC
    timezone = pytz.timezone("UTC")
    utc_now = class="type">class="kw">datetime.now(timezone)
    
    # Get data
    eurusd_data = mt5.copy_rates_from_pos("EURUSD", timeframe, class="num">0, bars_count)
    
    if eurusd_data is None or len(eurusd_data) == class="num">0:
        print("Error retrieving EURUSD data")
        class="kw">return None
    
    # Convert into pandas DataFrame
    df = pd.DataFrame(eurusd_data)
    
    # Convert time from timestamp to class="type">class="kw">datetime
    df[&class="macro">#x27;time&class="macro">#x27;] = pd.to_datetime(df[&class="macro">#x27;time&class="macro">#x27;], unit=&class="macro">#x27;s&class="macro">#x27;)
    
    # Set time as index
    df.set_index(&class="macro">#x27;time&class="macro">#x27;, inplace=True)
    
    class="kw">return df

极值序列怎么定趋势

趋势识别算法的底层逻辑是先抓局部高低点,再按时间顺序串成上升或下降段。每段趋势会带出类型、起止日期、起止价格、持续周期和波动幅度,这几个字段足够你在 MT5 里做可视化回看。 window_size 是这个工具最该动手调的旋钮。它定义滑动窗口半宽,默认 5 意味着以中心点前后各 2 根 K 线为邻域判定极值。值调小,短期毛刺也会被记成趋势,信号多但噪;值调大,只留大级别走势,适合日线以上的贵金属波段。外汇与贵金属杠杆高,误判趋势可能放大回撤,参数须按品种波动率实测。 下面这段 Python 式伪代码展示了极值标记与趋势拼接的核心循环,开 MT5 用同类思路写 MQL5 时可直接对照。

MQL5 / C++
def identify_trends(df, window_size=class="num">5):
    """
    Identification of local highs and lows to identify trends
    
    window_size: the size of a window for searching for local extremes
    """
    # Copy DataFrame so as not to change the original
    df_trends = df.copy()
    
    # Search for local highs and lows
    df_trends[&class="macro">#x27;local_max&class="macro">#x27;] = df_trends[&class="macro">#x27;high&class="macro">#x27;].rolling(window=window_size, center=True).apply(
        lambda x: x[window_sizeclass=class="str">"cmt">//class="num">2] == max(x), raw=True
    ).fillna(class="num">0).astype(class="type">bool)
    
    df_trends[&class="macro">#x27;local_min&class="macro">#x27;] = df_trends[&class="macro">#x27;low&class="macro">#x27;].rolling(window=window_size, center=True).apply(
        lambda x: x[window_sizeclass=class="str">"cmt">//class="num">2] == min(x), raw=True
    ).fillna(class="num">0).astype(class="type">bool)
    
    # Make structures for storing information about trends
    trends = []
    current_trend = {&class="macro">#x27;start_idx&class="macro">#x27;: class="num">0, &class="macro">#x27;start_price&class="macro">#x27;: class="num">0, &class="macro">#x27;end_idx&class="macro">#x27;: class="num">0, &class="macro">#x27;end_price&class="macro">#x27;: class="num">0, &class="macro">#x27;type&class="macro">#x27;: None}
    
    local_max_idx = df_trends[df_trends[&class="macro">#x27;local_max&class="macro">#x27;]].index.tolist()
    local_min_idx = df_trends[df_trends[&class="macro">#x27;local_min&class="macro">#x27;]].index.tolist()
    
    # Combine and sort highs and lows by index
    all_extremes = [(idx, &class="macro">#x27;max&class="macro">#x27;, df_trends.loc[idx, &class="macro">#x27;high&class="macro">#x27;]) for idx in local_max_idx] + \
                   [(idx, &class="macro">#x27;min&class="macro">#x27;, df_trends.loc[idx, &class="macro">#x27;low&class="macro">#x27;]) for idx in local_min_idx]
    all_extremes.sort(key=lambda x: x[class="num">0])
    
    # Filter recurring local extremes
    filtered_extremes = []
    for i, extreme in enumerate(all_extremes):
        if i == class="num">0:
            filtered_extremes.append(extreme)
            class="kw">continue
        
        last_extreme = filtered_extremes[-class="num">1]
        if last_extreme[class="num">1] != extreme[class="num">1]:  # Different types(highs and lows)
            filtered_extremes.append(extreme)
    
    # Create trends based on sequence of extremes
    for i in range(class="num">1, len(filtered_extremes)):
        prev_extreme = filtered_extremes[i-class="num">1]
        curr_extreme = filtered_extremes[i]
        
        trend = {
            &class="macro">#x27;start_date&class="macro">#x27;: prev_extreme[class="num">0],
            &class="macro">#x27;start_price&class="macro">#x27;: prev_extreme[class="num">2],
            &class="macro">#x27;end_date&class="macro">#x27;: curr_extreme[class="num">0],
            &class="macro">#x27;end_price&class="macro">#x27;: curr_extreme[class="num">2],

◍ 把极值差换算成可比较的趋势指标

这段逻辑把前后两个极值点的时间差、点数差和涨跌类型打包成一条记录,方便后续横向比较不同波段的强度。 天数直接用两个极值日期相减得出,点数则取价格差的绝对值再乘 10000,把报价单位统一成标准点(point),跨品种比对时不会因报价精度乱掉。 涨跌方向由当前极值是 max 还是 min 决定:max 记为 Uptrend,否则 Downtrend;随后补一列 percentage,用首尾价绝对差除以起始价再乘 100,得到该段波动的百分比幅度。 最后所有 trend 塞进列表并转成 DataFrame 返回。开 MT5 导出自家品种的极值序列跑一遍,就能看哪段趋势点数大但百分比小,这种差异在贵金属上往往意味着杠杆风险被放大。

MQL5 / C++
              &class="macro">#x27;duration&class="macro">#x27;: (curr_extreme[class="num">0] - prev_extreme[class="num">0]).days,
              &class="macro">#x27;points&class="macro">#x27;: abs(curr_extreme[class="num">2] - prev_extreme[class="num">2]) * class="num">10000,  # Convert to points
              &class="macro">#x27;type&class="macro">#x27;: &class="macro">#x27;Uptrend&class="macro">#x27; if curr_extreme[class="num">1] == &class="macro">#x27;max&class="macro">#x27; else &class="macro">#x27;Downtrend&class="macro">#x27;
              }
              
              # Add percentage change
              trend[&class="macro">#x27;percentage&class="macro">#x27;] = (abs(trend[&class="macro">#x27;end_price&class="macro">#x27;] - trend[&class="macro">#x27;start_price&class="macro">#x27;]) / trend[&class="macro">#x27;start_price&class="macro">#x27;]) * class="num">100
              
              trends.append(trend)
    
    class="kw">return pd.DataFrame(trends)

常见问题

拉多个周期的高低点极值序列,算相邻极值差的标准化值,连续同符号且绝对值超阈值才倾向视为真趋势,否则多为噪声。
把各周期极值差除以对应周期波动标准差,得到无量纲趋势指标,数值越大表示该周期趋势倾向越强,可直接横向排。
小布可对接你的账户数据,自动抽取多周期极值并算出趋势指标,打开品种页就能看到统计视角的趋势强弱,不用自己写代码。
日线以上建议用近 60–120 根 K 线的极值,太短易被毛刺带偏,太长会滞后,回测时可按品种波动率微调。
肉眼易把少数大 K 线当主趋势,统计看的是极值差分布与显著性,多数口号级趋势在标准误面前概率上不成立。