量化趋势分析:基于Python的统计建模·进阶篇
(2/3)· 从数据获取到滑动窗口识别,把EURUSD与GBPUSD的趋势规律变成可验证的数字
◍ 滑动窗口凭什么快
把局部高点定义为窗口里比左右邻居都高的那根 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,重点比对多空均值差。若差值持续偏离历史中位数,后续价格反转概率倾向升高,但外汇/贵金属杠杆风险极高,仅作概率参考。
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 负责把三张图落盘,逻辑可直接搬到本地回测脚本里跑。
<span class="keyword">def</span> save_distribution_plots(trend_df, output_dir): <span class="class="type">class="kw">string">"""Creating and saving additional distribution charts"""</span> <span class="comment"># class="num">1. Chart of trend distribution by magnitude</span> plt.figure(figsize=(<span class="number">class="num">12</span>, <span class="number">class="num">8</span>)) 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>] 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>] 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>]], 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>], 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>]) plt.title(<span class="class="type">class="kw">string">&class="macro">#x27;Distribution of trend values(in points)&class="macro">#x27;</span>) plt.xlabel(<span class="class="type">class="kw">string">&class="macro">#x27;Trend value(points)&class="macro">#x27;</span>) plt.ylabel(<span class="class="type">class="kw">string">&class="macro">#x27;Number of trends&class="macro">#x27;</span>) plt.grid(<span class="literal">True</span>, alpha=<span class="number">class="num">0.3</span>) plt.legend() plt.tight_layout() 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>)) plt.close() <span class="comment"># class="num">2. Chart of trend distribution by duration</span> plt.figure(figsize=(<span class="number">class="num">12</span>, <span class="number">class="num">8</span>)) 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>]], 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>], 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>]) plt.title(<span class="class="type">class="kw">string">&class="macro">#x27;Distribution of trend duration(in days)&class="macro">#x27;</span>) plt.xlabel(<span class="class="type">class="kw">string">&class="macro">#x27;Trend duration(days)&class="macro">#x27;</span>) plt.ylabel(<span class="class="type">class="kw">string">&class="macro">#x27;Number of trends&class="macro">#x27;</span>) plt.grid(<span class="literal">True</span>, alpha=<span class="number">class="num">0.3</span>) plt.legend() plt.tight_layout() 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>)) plt.close() <span class="comment"># class="num">3. Chart of the ratio of duration and magnitude of trends</span> plt.figure(figsize=(<span class="number">class="num">12</span>, <span class="number">class="num">8</span>)) 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>], 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>) 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>], 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>) plt.title(<span class="class="type">class="kw">string">&class="macro">#x27;Ratio of duration and magnitude of trends&class="macro">#x27;</span>) plt.xlabel(<span class="class="type">class="kw">string">&class="macro">#x27;Trend duration(days)&class="macro">#x27;</span>) plt.ylabel(<span class="class="type">class="kw">string">&class="macro">#x27;Trend magnitude(points)&class="macro">#x27;</span>) plt.grid(<span class="literal">True</span>, alpha=<span class="number">class="num">0.3</span>) plt.legend() plt.tight_layout() plt.savefig(os.path.join(output_dir, <span class="class="type">class="kw">string">&class="macro">#x27;trend_correlation.png&class="macro">#x27;</span>)) 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,跑完看图:若橙色虚线频繁穿越实体而非贴合影线,说明极值判定阈值可能过松,外汇品种杠杆高、假突破多,需回测调参后再上模拟盘。
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%,系统侦测到就该自动放大盈利目标;若统计显示上涨趋势比下跌更长更陡,就给两类趋势套不同进出场规则。外汇和贵金属杠杆高,这类参数切换只是降低盲猜概率,不保证胜率。