量化趋势分析:基于Python的统计建模(基础篇)
「用统计视角拆 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 并设为索引,后续计算直接按时间切片。
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 时可直接对照。
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 导出自家品种的极值序列跑一遍,就能看哪段趋势点数大但百分比小,这种差异在贵金属上往往意味着杠杆风险被放大。
&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)