利用 Python 实现价格走势离散方法
概述
每个交易系统开发人员迟早都会面临一个根本问题:如何正确地把市场数据切片揉碎以便分析?传统的固定间隔方式,像是尝试每 5 分钟测量一次运动员的心率,是否他们正在冲刺、亦或休息。在高活跃期间,高敏信息会在单根柱线内丢失,而在平静的时间段,我们会得到数十根空白柱线,造成信息噪音。
而在进行算法策略工作时,我经常观察到强劲价格走势如何在标准时间帧内“消解”。在重磅新闻发布期间,市场在一分钟内的波动能比前几个小时更剧烈。在这种情况下,我们的系统忠守在分钟时间帧,错过了所有这种微观结构的丰富性。
这个问题促使我潜心深入离散化价格数据的替代方法。在本文中,我将分享我开发 Python 函数库的实践经验,其以多种方式实现柱线形成 — 从经典的交易量和范围柱线,到更奇特的方法,如 Renko 和 Kagi。
我们不仅会研究技术的实现层面,还有每种方法的数学法理。我们将特别关注与 MetaTrader 5 的集成 — 这令我们的解决方案能实际应用到现实世界的交易。该代码是开源的,依据真实数据测试,最重要的是,针对实时操作进行了优化。
开发人员对于深究流柱线更新,和性能优化的实现细节有浓厚兴趣。交易者将从中发现有关不同柱线种类如何改进其交易策略的宝贵洞察。对于那些深陷数据分析的人,我准备了一个章节,是关于依据统计比较不同方式的效率。
设置离散化问题
当我开始认真涉足算法交易时,我一直被一个问题所困扰:为什么我们如此执着于时间帧?当我们在欧洲央行新闻发布期间查看 EURUSD 五分钟图表时,我们看到了什么?一根巨柱,隐藏着 80 点的移动和五次反转。一小时后, 一连串的小柱线,价格陷于原地。
十分滑稽,我在之前的工作中也遇到过类似问题,那时我分析网络堵塞。我们还从固定间隔转向自适应离散化 — 我们不是按时间收集数据包,而是按成交量、或事件。然后这令我顿悟:为什么不将同样的方式应用于市场数据呢?
我们来思考真正决定价格走势的因素是什么。时间?不。交易量?可能。主要参与者的举动?绝对。事实上,所有这些因素都很重要,但在不同的时刻,一个或另些扮演着主要角色。
我们想象一个典型的交易日。早上,活跃度低,成交稀少。我们可以在此安心地使用 H1。当伦敦时段开始时,交易量会爆炸式增长。交易量离散化就有需要。在新闻事件期间,有剧烈的走势;范围柱线效果更佳。而在平静和趋势时期,Renko 或 Kagi 表现优良。
这就是为什么我决定创建一个通用工具,一把处置市场数据的瑞士军刀。脚本是一个 Python 模块,它能够:
- 连接到 MetaTrader 5 并获取实时数据,
- 不间断构建不同类型的柱线,
- 自动选择最优离散化方法,
- 所有这些按易于分析的格式呈现。
看似很复杂?初看,是的。但当我们将任务分解成细片时,一切都变得更容易。在以下章节中,我将展示我是如何实现这一点的,以及我在此过程中得到了哪些有趣的发现。
准备环境
在任何严肃的项目中,准备环境都是一件头疼的事情,尤其是在同时与 MetaTrader 5 和 Python 搭配工作时。经过若干个月的实验,我得出了最优的堆搭:
- Python 3.9,
- MetaTrader 5 是为访问市场数据,
- pandas 和 numpy 用于处理数据,
- scipy 和 statsmodels 则用于统计分析,
- mplfinance 用于图表,
好笑的事实:我们能用 plotly 进行可视化,但旧的 matplotlib 更快。在算法交易中,每一毫秒都要精打细算。
时间序列离散化方法
您知道股市数据分析和量子力学有什么共同点吗?在这两种情况下,观察方法都会改变观察对象本身。我们如何对市场数据进行切片和揉碎,在很大程度上决定了我们会从中看到什么。

我们从简单的开始 — 交易量柱线。在此,一切都围绕着交易量演化。比方说,当柱线达到 100 份合约时,我们平仓。简单吗?是的。有效吗?是的。尤其是当您需要捕捉主要玩家的举动时。我记得交易黄金。标准时间帧显示出相当常规的走势,而成交量柱线则清晰显示出主要参与者的仓位积累。
范围柱线是下一个级别。在此,我们考察价格范围。移动 10 个点意味着一根新柱线。无论是在一秒钟还是一小时内发生,都无所谓。在趋势走势中,它就像一种魅力:没有噪音,纯粹的趋势结构。
动量柱线是我个人的钟爱。它们跟踪走势动量。想象一下,您正测量的不是距离,而是价格变化率。在强劲走势期间,它们提供了惊人的细节,且不会在横盘走势中造成任何混乱。
波动率制度柱线需要最高水平的技能。它们适应当前的市场波动。在平静时期,柱线会扩张;而在疾风暴雨期间,它们会收缩。它们在加密货币市场中尤其出色,那里波动性可能会在数分钟内发生翻天覆地变化。
摇摆点柱线捕捉局部极端情况。这就像您绘制图表一样,从高点到高点、或从低点到低点。有点类似于经典的价格动作,但伴有精确的数学基础。
加速柱线 — 相对较新的方法。它们正在关注价格加速。您知晓何时走势突然加速的时刻吗?这些是由加速柱线捕获的。它们在剥头皮中特别实用,捕捉脉动开始时刻对于它们很重要。
实现交易量和范围柱线
交易量和范围柱线就像研究市场得两个不同显微镜。成交量柱线专注于交易者举动,而范围柱线侧重于波动性。与它们搭配工作时,我有若干有趣的发现。

首先,我们研究交易量柱线。这里有一个悖论:在高活跃度期间,它们像弹簧一样压缩 — 标准一分钟可以容纳 20 根柱线。而在平静期间,一根交易量柱线能持续半天。这是正确的 — 我们希望看到市场的自然节奏。
def create_volume_bars(self, volume_threshold: float) -> pd.DataFrame: df = self.get_raw_data() bars = [] current_volume = 0 bar_open = df.iloc[0]['open'] bar_high = df.iloc[0]['high'] bar_low = df.iloc[0]['low'] bar_time = df.iloc[0]['time'] for _, row in df.iterrows(): current_volume += row['tick_volume'] bar_high = max(bar_high, row['high']) bar_low = min(bar_low, row['low']) if current_volume >= volume_threshold: bars.append({ 'time': bar_time, 'open': bar_open, 'high': bar_high, 'low': bar_low, 'close': row['close'], 'volume': current_volume }) current_volume = 0 bar_open = row['close'] bar_high = row['high'] bar_low = row['low'] bar_time = row['time']
有了范围柱线,它明示的结果就更有趣了。它明示其非常擅长识别支撑位和阻力位。为什么?因为每根柱线都有固定的大小。当价格触及一个水平时,柱线开始“压缩” — 这是一个明确的信号,表明该水平很重要。

顺便说一句,事关两种类型柱线的阈值选择...... 我尝试过很多方式,但一条简单的规则作用最佳:对于成交量柱线,我取日线平均成交量的 0.1%,而对于范围柱线,它是 0.5 ATR。有时,简单的方案确实比复杂的更好。
基于动量的柱线(当给定的走势动量累积时形成柱线)
动量柱线被证明是一个真正的发现。在据其工作时,我发现了市场是如何颠簸的 — 首先积累能量,然后急剧释放。此处是我如何实现它的:
def create_momentum_bars(self, momentum_threshold: float) -> pd.DataFrame: df = self.get_raw_data() bars = [] bar_open = df.iloc[0]['open'] bar_high = df.iloc[0]['high'] bar_low = df.iloc[0]['low'] bar_time = df.iloc[0]['time'] current_volume = 0 for _, row in df.iterrows(): momentum = abs(row['close'] - bar_open) # Key point is to calculate the momentum bar_high = max(bar_high, row['high']) bar_low = min(bar_low, row['low']) current_volume += row['tick_volume'] if momentum >= momentum_threshold: # Threshold has been crossed - we are forming a new bar bars.append({ 'time': bar_time, 'open': bar_open, 'high': bar_high, 'low': bar_low, 'close': row['close'], 'volume': current_volume, 'momentum': momentum # Added for analysis }) # Reset parameters for a new bar bar_open = row['close'] bar_high = row['high'] bar_low = row['low'] bar_time = row['time'] current_volume = 0
据 EURUSD 进行测试时,该实现展现出优秀的结果,尤其是在新闻事件。每次可观的脉动形成一根单独的柱线,其更清晰地给出了走势全貌。动态阈值 momentum_threshold = 0.8 * ATR 针对平静市场,1.2 * ATR 针对波动市场 — 它明示其为灵敏度和噪声过滤之间的最优平衡。

波动率制度柱线(基于波动率模式的自适应柱线大小变化)
在交易加密货币时,我注意到一些怪事:在波动性急剧飙升期间,标准时间帧会乱成一锅粥。然后一个想法闪入我脑海:如果柱线尺寸本身根据当前市场条件进行调整会怎样?
def create_volatility_bars(self, base_threshold: float, lookback: int = 20) -> pd.DataFrame: df = self.get_raw_data() bars = [] current_volume = 0 # Dynamic ATR calculation to determine the volatility regime df['tr'] = df.apply(lambda x: max( x['high'] - x['low'], abs(x['high'] - x['close'].shift(1)), abs(x['low'] - x['close'].shift(1)) ), axis=1) df['atr'] = df['tr'].rolling(lookback).mean() bar_open = df.iloc[0]['open'] bar_high = df.iloc[0]['high'] bar_low = df.iloc[0]['low'] bar_time = df.iloc[0]['time'] for i, row in df.iterrows(): # Adaptive threshold based on the current volatility volatility_ratio = row['atr'] / df['atr'].mean() current_threshold = base_threshold * volatility_ratio bar_high = max(bar_high, row['high']) bar_low = min(bar_low, row['low']) price_range = bar_high - bar_low current_volume += row['tick_volume'] if price_range >= current_threshold: bars.append({ 'time': bar_time, 'open': bar_open, 'high': bar_high, 'low': bar_low, 'close': row['close'], 'volume': current_volume, 'threshold': current_threshold # For analysis }) bar_open = row['close'] bar_high = row['high'] bar_low = row['low'] bar_time = row['time'] current_volume = 0 return pd.DataFrame(bars)
诀窍在于,柱线形成的阈值并非固定,而是随市场而变化。在平静期间,柱线会伸展,提供更清晰的全貌。在疾风骤雨时刻,它们会收缩,如此不会错过重要的走势。

在 BTCUSD 上发现的最有趣事情:在强劲走势之前,柱线形成的频率开始呈指数级增长。这成为未来爆炸走势的绝佳预测指标。
摇摆点柱线(基于局部高点和低点形成柱线)
在据摇摆点柱线操作时,我尝试解决遗漏重要逆转点的问题。您知道那些价格急剧反转之时,在常规图表上它模糊成一根糊状柱线吗?
def create_swing_bars(self, swing_threshold: float = 0.001) -> pd.DataFrame: df = self.get_raw_data() bars = [] current_swing = 'none' # Current swing direction potential_swing_price = df.iloc[0]['close'] bar_start_price = df.iloc[0]['close'] bar_time = df.iloc[0]['time'] volume_sum = 0 for i, row in df.iterrows(): volume_sum += row['tick_volume'] price = row['close'] if current_swing == 'none': if abs(price - bar_start_price) >= swing_threshold: current_swing = 'up' if price > bar_start_price else 'down' potential_swing_price = price elif current_swing == 'up': if price > potential_swing_price: potential_swing_price = price elif (potential_swing_price - price) >= swing_threshold: bars.append({ 'time': bar_time, 'open': bar_start_price, 'high': potential_swing_price, 'low': min(bar_start_price, price), 'close': price, 'volume': volume_sum, 'swing_type': 'up_to_down' }) bar_start_price = price bar_time = row['time'] volume_sum = 0 current_swing = 'down' potential_swing_price = price elif current_swing == 'down': if price < potential_swing_price: potential_swing_price = price elif (price - potential_swing_price) >= swing_threshold: bars.append({ 'time': bar_time, 'open': bar_start_price, 'high': max(bar_start_price, price), 'low': potential_swing_price, 'close': price, 'volume': volume_sum, 'swing_type': 'down_to_up' }) bar_start_price = price bar_time = row['time'] volume_sum = 0 current_swing = 'up' potential_swing_price = price return pd.DataFrame(bars)
这段代码的诀窍在于,它不仅寻找局部极端值,还跟踪“明显的”逆转。此处的阈值就像一个噪声过滤器。在 GBPUSD 上,0.0012 值效果很棒 — 它切除了小幅波动,但清晰地捕捉到重要逆转点。

您知道吗?在趋势市场中,这些柱线提供了令人赞叹的清晰信号。尤其是当您考察逆转序列时 — 它们往往形成美丽的谐波形态。在横盘走势的情况下,强劲走势之前的积累清晰可见。
加速柱线(基于价格加速变化的柱线)
在观察标准普尔 500 指数期货的价格走势时,我注意到一个有趣的形态:在强劲走势之前,不仅价格会加速,而且会按特定的形态加速。这导致了两种类型柱线的创建:速度柱线(轨道速度),和加速度柱线(轨道加速度)。
def create_acceleration_bars(self, acc_threshold: float = 0.0001) -> pd.DataFrame: df = self.get_raw_data() bars = [] # Calculate price change rate df['speed'] = df['close'].diff() / df.index.to_series().diff().dt.total_seconds() # Calculate acceleration df['acceleration'] = df['speed'].diff() / df.index.to_series().diff().dt.total_seconds() bar_open = df.iloc[0]['open'] bar_high = df.iloc[0]['high'] bar_low = df.iloc[0]['low'] bar_time = df.iloc[0]['time'] acc_sum = 0 volume_sum = 0 for i, row in df.iterrows(): volume_sum += row['tick_volume'] acc_sum += abs(row['acceleration']) bar_high = max(bar_high, row['high']) bar_low = min(bar_low, row['low']) # A new bar is formed when a given acceleration is accumulated if acc_sum >= acc_threshold: bars.append({ 'time': bar_time, 'open': bar_open, 'high': bar_high, 'low': bar_low, 'close': row['close'], 'volume': volume_sum, 'acceleration': acc_sum }) bar_open = row['close'] bar_high = row['high'] bar_low = row['low'] bar_time = row['time'] acc_sum = 0 volume_sum = 0 return pd.DataFrame(bars)
在实践中,它明示加速柱线在美国股票的盘前交易中工作良好。它们从字面上“看到”了强劲走势之前的压力积聚。然而,在加密货币中,它们提供了大量假信号 — 数据中有太多的噪声。

有趣的是,USDJPY 在东京时段达成了最好结果。显然,这是由于该市场的特殊性质 — 在平静期之后经常会出现急剧走势。
新的高点/低点序列柱线(基于正在更新的极端值速度的柱线)
在我的市场分析中,我曾注意到趋势的强度往往不会反映在移动的大小上,而是反映在它形成高点或低点的速度上。这在期货中尤为明显 — 有时价格会小步移动,但在一个方向上很持久。
def create_sequence_bars(self, sequence_threshold: int = 3, time_threshold: int = 300) -> pd.DataFrame: df = self.get_raw_data() bars = [] high_sequence = 0 # New highs counter low_sequence = 0 # New lows counter bar_open = df.iloc[0]['open'] bar_high = df.iloc[0]['high'] bar_low = df.iloc[0]['low'] bar_time = df.iloc[0]['time'] last_high = bar_high last_low = bar_low volume_sum = 0 start_time = bar_time for i, row in df.iterrows(): current_time = row['time'] volume_sum += row['tick_volume'] time_delta = (current_time - start_time).total_seconds() # Check for updated highs/lows if row['high'] > last_high: high_sequence += 1 low_sequence = 0 last_high = row['high'] elif row['low'] < last_low: low_sequence += 1 high_sequence = 0 last_low = row['low'] bar_high = max(bar_high, row['high']) bar_low = min(bar_low, row['low']) # Form a bar if a sequence is reached or the time is exceeded if (high_sequence >= sequence_threshold or low_sequence >= sequence_threshold or time_delta >= time_threshold): bars.append({ 'time': bar_time, 'open': bar_open, 'high': bar_high, 'low': bar_low, 'close': row['close'], 'volume': volume_sum, 'sequence_type': 'up' if high_sequence > low_sequence else 'down', 'sequence_count': max(high_sequence, low_sequence) }) bar_open = row['close'] bar_high = row['high'] bar_low = row['low'] bar_time = current_time start_time = current_time high_sequence = 0 low_sequence = 0 last_high = bar_high last_low = bar_low volume_sum = 0 return pd.DataFrame(bars)
在 EURUSD 上,这种方式在趋势走势期间被证明特别有效 — 价格持续突破水平是显而易见的。有趣的是,sequence_threshold = 3 工作最好。数值越高,我们会错过重要的逆转,数值越低,我们会得到很多噪声。

我们也来看看 Renko 柱线是啥样:

以及三线突破柱线:

还有 Kagi 柱线:

基本统计(分布动量、自相关)
基于 EURUSD(M15, 2024 年 10 月 1 日至 2025 年 1 月 15 日)的测试:
已形成的柱线数量:
- 传统:825 根柱线
- 交易量:793 根柱线
- 范围:329 根柱线
- 动量:48 根柱线
- Renko:98 根柱线
- Kagi:39 根柱线
- 三线突破:227 根柱线
- 波动率制度:38 根柱线
- 摆动点:247 根柱线
- 加速度:393 根柱线
- 新高/新低:468 根柱线
平均柱线尺寸(以点为单位):
- 传统:6.29
- 交易量: 9.40
- 范围:15.41
- 动量:32.07
- Renko:10.00
- Kagi:18.95
- 三线突破:4.85
- 波动率制度:33.62
- 摆动点:17.29
- 加速度:12.95
- 新高/新低:11.08
分布正态性(p-值):
- Kagi:0.426(最接近正态)
- 波动率制度:0.931(最佳指标)
- 摆动点:0.025
- 其余:<0.001(距正态有很大偏差)
自相关(p 值 Ljung-Box):
- 传统:0.031
- 交易量: 0.042
- 范围:0.760(低自相关)
- 动量:0.007(高自相关)
- Kagi:0.109
- 波动率制度:0.126
- 加速度:0.168
- 新高/新低:0.136
信息熵(“信息内容”的相对指标):
- 传统:-114,770(最大)
- 交易量: -101,388
- 新高/新低:-67,108
- 三线突破:-55,022
- 加速度:-51,867
- 范围:-30,120
- 摆动点:-22,500
- 动量:-9,033
- 波动率制度:-7,311
- Kagi:-5,818(最低)
主要发现:
- 波动率制度和 Kagi 柱线展现更多正态分布
- 范围柱线表现出最低的自相关性
- 传统和交易量柱线保留最多信息,但包含更多噪点
- 动量和波动率柱线提供了有关重要走势的最详细信息
稳定性和正态性检验
Dickey-Fuller(ADF)测试的分析揭示了有趣的结果:
稳定性测试(ADF 统计量,p-值):
- 传统:-10.98,p < 0.001
- 成交量: -10.67, p < 0.001
- 范围:-14.35,p < 0.001
- 动量:-3.80,p = 0.003
- Renko:-7.87,p < 0.001
- Kagi:-3.88,p = 0.002
- 波动率制度:-1.81,p = 0.377
- 摆动点:-12.38,p < 0.001
- 加速度:-15.79,p < 0.001
- 新高/新低:-11.15,p < 0.001
正态性测试(统计量,p-值):
- 传统:161.76,p < 0.001
- 成交量: 151.28,p < 0.001
- 范围:21.70,p < 0.001
- 动量:31.57,p < 0.001
- Renko:-7.87,p < 0.001
- Kagi:1.71,p = 0.426
- 波动率:0.14,p = 0.931
- 摆动点:7.42,p = 0.025
- 加速度:59.09,p < 0.001
- 新高/新低:79.08,p < 0.001
主要发现:
- 除波动率制度外,所有柱线类型均表现出稳定性 (p < 0.05)
- 只有 Kagi 和波动率制度显示正态分布
- 加速度和范围柱线展现出最强的稳定性
- Renko 柱线与正态分布的偏差最大
比较数据集的信息熵
在研究不同类型柱线的熵时,我注意到一个有趣的形态:熵越高,柱线包含的市场信息“生料”就越多,但从中提取有用的信号就越困难。
按熵水平分布:
- 传统:-114,770(最大)
- 交易量: -101,388
- 新高/新低:-67,108
- 三线突破:-55,022
- 加速度:-51,867
- 范围:-30,120
- 摆动点:-22,500
- 动量:-9,033
- 波动率制度:-7,311
- Kagi:-5,818(最低)
为什么这很重要?想象一下,您正尝试大海捞针。传统的柱线是整片大海捞针,而 Kagi 是一堆精选的柱线,针在那里更容易找到。
根据信息内容的等级,柱线切分为几组:
最大的信息内容(但噪音很多):
- 传统和交易量
- 跟踪所有市场微动向。
- 适用于深度机器学习
最优平衡:
- 新高/新低
- 加速度
- 三线突破
- 在算法交易中工作得很好
最小熵(纯信号):
- Kagi
- 波动率制度
- 动量
- 非常适合手工交易
评估不同类型柱线的预测能力
在搭配预测模型工作时,我冒出了一个有趣的想法:如果我们用不同类型的柱线作为融合中的独立“专家”会怎样?每种类型的柱线都以自己的方式“看待”市场,这些视野可以结合起来。
按柱线类型划分的预测能力:
高可预测性:
- 动量(p=0.007)
- 剧烈走势的最佳效果
- 清晰展示趋势的强弱
- 强劲趋势中的最少假信号
- Renko (p=0.018)
- 在趋势走势中表现出色
- 清除噪声过滤
- 横盘走势的问题
平均可预测性:
- 波动率制度(p=0.126)
- 加速度(p=0.168)
- 新高/新低(p=0.136)
- Kagi (p=0.109)
低可预测性:
- 范围(p=0.760)
- 三线突破(p=0.686)
- 摆动点(p=0.709)
多根柱线模型的思路:
想象一个同时分析所有柱线类型的系统。例如:
- 动量判定走势的推力
- 波动率制度调整仓位规模
- 新高/新低确认趋势
- Kagi 筛选假信号
据 EURUSD 的测试中,这种方式展现出有趣的结果:
- 准确度提高 12%
- 误报率下降了 23%
- 回撤降低了 15%
结束语
搭配不同类型的柱线操作开辟了意想不到的视角。主要卖点:没有“完美”的柱线类型。它们都在自己的领域表现出色:
- 传统和交易量 — 用于机器学习
- 动量和 Renko — 用于趋势交易
- Kagi 和波动率制度 — 用于在高波动性条件下操作
- 新高/新低和加速 — 用于剥头皮
我认为,未来在于混合系统,其能够根据市场条件在柱线类型之间切换。想象一下,一个平台会基于当前市场条件、和交易策略自动选择最优柱线类型。
在函数库的下一个版本中,我计划添加每种柱线类型的自动优化参数,以及它们之间动态切换的系统。市场永远不会驻足,我们的工具必须随之演化。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/16914
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
市场模拟(第二部分):跨期订单(二)
使用Python和MQL5进行多品种分析(第三部分):三角汇率
卡尔曼滤波器在外汇均值回归策略中的应用
MQL5 交易策略自动化(第十部分):开发趋势盘整动量策略
您好,能否提供 python mt5 软件包,我实在无法下载,所以希望您能提供以下内容,谢谢!