您应当知道的 MQL5 向导技术(第 61 部分):结合 ADX 和 CCI 形态进行监督学习
概述
我们继续考察如何把跟踪市场不同层面的指标与机器学习配对,来构建一个交易系统。至于接下来的文章,我们会考察平均方向性指数(ADX)振荡器与商品通道指数(CCI)的配对。ADX 主要作为一个趋势确认指标,而 CCI 则是一个动量指标。当我们在之前的文章中考察单个指标形态时,曾接触过这两个属性。回顾一下,趋势确认衡量的是给定价格趋势如何强劲;而强度指的是适合入场。动量指标则从另一方面衡量价格变化率。价格在给定方向上变化越快,遭遇不利波动的可能性就越小。

商品通道指数(CCI)
我们正在测试我们的网络,并利用 Python 取指标信号作为输入。这是因为目前 Python 相较于 MQL5 提供了显著的性能优势。然而,正如我在最近的文章中提到的,MQL5 通过使用 OpenCL,能够达到、甚至接近这样的性能。不过,这需要配备一块 GPU,才能享受 OpenCL 的速度。至于目前,当两种语言在相似的 CPU 上使用时,Python 显然出类拔萃,故我们据其测试和开发我们的模型。
MetaTrader 已构建了一个 Python 模块,不仅允许将经纪商价格数据加载到 Python,还允许自 Python 下单交易。更多相关内容可以在这里和这里找到。因此,我们利用部分模块登录经纪商,并将价格数据拉入 Python,用于我们的监督学习 MLP。一旦我们得到价格数据,随后我们就要定义指标函数。如果我们从 CCI 开始,我们可如下实现:
def CCI(df, period=14): """ Calculate Commodity Channel Index (CCI) :param df: pandas DataFrame with columns ['high', 'low', 'close'] :param period: lookback period (default 20) :return: Series with CCI values """ # Calculate Typical Price typical_price = (df['high'] + df['low'] + df['close']) / 3 # Calculate Simple Moving Average of Typical Price sma = typical_price.rolling(window=period).mean() # Calculate Mean Deviation mean_deviation = typical_price.rolling(window=period).apply( lambda x: np.mean(np.abs(x - np.mean(x))), raw=True) # Calculate CCI df['cci'] = (typical_price - sma) / (0.015 * mean_deviation) return df[['cci']]
CCI 是一个振荡指标,即衡量资产价格偏离统计平均值的程度。这对交易者识别超买或超卖条件非常有帮助。我们上述实现的函数采用 pandas 数据帧,包含“最高价”、“最低价”、和“收盘价”列,并有一个计算 CCI 值的回望周期(默认为 14)。“典型价格”计算设定为最高价、最低价、和收盘价的平均值,代表/覆盖平均周期大部分的价格动作。它是这三个主要价格在每个时间帧下的算术均值,有助于把价格数据简化为一个代表性数值。这可辅助降低日内波动带来的噪音。这一步是基础,因为 CCI 基于距这一典型价格的偏离,同时用这三个(最高价、最低价、收盘价)帮助确保价格走势的平衡。因此,需要确保通过 MetaTrader 5 模块检索经纪商价格数据的 Pandas 数据帧包含所有这些列,因为缺了这些数值会导致错误。
简单移动平均线(SMA)平滑了指定周期内的典型价格,建立一条基线。这是计算指定输入周期(默认 14)的 SMA,如此它就能像一个参考来行事。这很重要,因为短期价格波动会被平滑,从而提供一个具有代表性的“正常”价格水平。滚动函数需要足够的数据点,至少是输入周期的平方,才能计算一条有效的 SMA。如果数据集太短,那么初始 NaN 值或许需要特殊处理(经由像是 dropna() 函数,等等)
平均偏差随后衡量这些代表性典型价格与其 SMA 绝对偏差的平均值,从而捕捉价格波动性。对于每个窗口,计算每个典型价格与窗口均值之间的绝对差值,然后取其平均值。这是根本,因为平均偏差会放大价格波动性,而波动性对于 CCI 的伸缩至关重要,而 CCI 又深刻反映资产典型价格摇摆。这也确保了不同资产之间的比较。lambda 函数针对大数据集计算密集,这就是为何使用向量化替代、或像 ta-lib 这样的函数库是个好主意。在进行中,仍然需要导入 NumPy。
CCI 公式随后将上述所有分量合并,计算出一个数值,然后按常数 0.015 进行缩放,以实现归一化。0.015 常数是一个标准的缩放因子,旨在保持 CCI 值在 ±100 范围。不过这并非总是能达成,不过这就是目标。这是 CCI 公式的核心,因为它将原始价格偏差转化为标准化的振荡器。高于 +100 的数值则表示超买,低于 -100 则表示超卖。按该公式,如果平均偏差为零,应当注意除零错误。虽然罕见,但价格横盘时这种场景是可能的。然后根据需要,可在分母中加入一个小的 ε 值(例如 1e-10),以缓解这一点。
return 语句返回一个仅包含 “cci” 列的数据帧。如果调试或额外分析需要额外的列(例如 “typical_price”),则可修改该语句,包含所需列。
平均方向性指数(ADX)
如上所述,ADX 衡量趋势强度,无论方向如何。这是通过使用主要由两个缓冲区(+DI 和 -DI)组成的方向走势索引做到的。该函数与上述 CCI 类似,按“最高价”、“最低价”、“收盘价” 列的 pandas 数据帧、及回望周期(默认 14)来计算 ADX、+DI 和 -DI 的数值。其 Python 的实现如下:
def ADX(df, period=14): """ Calculate Average Directional Index (ADX) :param df: pandas DataFrame with columns ['high', 'low', 'close'] :param period: lookback period (default 14) :return: DataFrame with ADX, +DI, -DI columns """ # Calculate +DM, -DM, and True Range df['up_move'] = df['high'] - df['high'].shift(1) df['down_move'] = df['low'].shift(1) - df['low'] df['+dm'] = np.where( (df['up_move'] > df['down_move']) & (df['up_move'] > 0), df['up_move'], 0.0 ) df['-dm'] = np.where( (df['down_move'] > df['up_move']) & (df['down_move'] > 0), df['down_move'], 0.0 ) # Calculate True Range df['tr'] = np.maximum( df['high'] - df['low'], np.maximum( abs(df['high'] - df['close'].shift(1)), abs(df['low'] - df['close'].shift(1)) )) # Smooth the values using Wilder's smoothing method (EMA with alpha=1/period) df['+dm_smoothed'] = df['+dm'].ewm(alpha=1/period, adjust=False).mean() df['-dm_smoothed'] = df['-dm'].ewm(alpha=1/period, adjust=False).mean() df['tr_smoothed'] = df['tr'].ewm(alpha=1/period, adjust=False).mean() # Calculate +DI and -DI df['+di'] = 100 * (df['+dm_smoothed'] / df['tr_smoothed']) df['-di'] = 100 * (df['-dm_smoothed'] / df['tr_smoothed']) # Calculate DX df['dx'] = 100 * (abs(df['+di'] - df['-di']) / (df['+di'] + df['-di'])) # Calculate ADX df['adx'] = df['dx'].ewm(alpha=1/period, adjust=False).mean() # Return the relevant columns return df[['adx', '+di', '-di']]
两个计算方向性走势的缓冲区(+DM, -DM),放入价格向上和向下走势的大小,以供评估方向性强度。它们是从连续最高价走势(上升移动)与相反方向(连续最低点之差,下降移动)之间的差值推导而来。这很重要,因为这些构成了+DM 和 -DM 模块,进而设定方向性动量。通过使用平移值,我们确保跨周期比较。当 +DM 超过向下移动、且为正数值时,假定其为上升移动;否则,+DM 为 0。类似地,当 -DM 超过向上移动,也给其一个向下移动的数值,令其变为正数值。这有助于过滤掉非主导、或负值走势。
NumPy 的 where() 函数对于向量化运算非常有效,因此您应该确保 NumPy 被导入。验证上行和下行的计算正确性也很重要,以避免误判走势。使用 shift(1) 在第一行引入了 NaN,这在下游计算、或返回结果时需要处理。从始至终的根本,要确保“最高价”和“最低价”列是数字。
真实范围 TR 衡量价格波动率,有助于参考缺口和日内范围。取三个值中最高的一个来执行计算;最高价-最低价区间、绝对最高价至先前收盘价的范围,以及绝对最低价至先前收盘价的范围。这有助于参考价格缺口和波动性。测量资产波动率非常重要,因为它会当作归一化 +DI 和 -DI 的分母。这意味着 ADX 体现了相对于价格走势的趋势强度。NumPy 的 maximum 函数确保选取最大范围。自 shift(1) 处理 NaN 也应被关注。
怀尔德(Wilder)平滑法针对 +DM、-DM 和 TR,应用了特殊 alpha 的指数移动平均。alpha 是 1/period,adjust 参数的 False 意味着更多权重会赋予近期数据,这模仿了怀尔德的原创方法。平滑有助于降低噪声,令指标数据集在趋势分析中更加可靠。ewm 函数高效,但对 alpha 参数敏感。
方向性指标依据真实范围归一化已平滑走势,以便比较多头与空头的强度。100 的比例有助于用百分比来表达它们。这很重要,因为 +DI 和 -DI 以数字标示多头和空头趋势相对于波动率的程度。因此,+DI 和 -DI 之间的交叉处是潜在趋势变化的信号。增加一个小的 ε 值,就能起到除零保护作用。100 的缩放因子是标准的,是为了轻松解读。
DX 缓冲区判定 +DI 与 -DI 之间的伸缩绝对差值,再除以它们的合计。这是方向性走势的相对强度大小的根基。DX 是 ADX 的重要中间步骤,任务是捕捉趋势的“烈度”(无论是多头亦或空头)。应当小心处理 +di + -di 为零的情况,避免除零错误。可以返回零,或者跳过计算。
最后,ADX 取方向性指数平均值,来估算整体趋势强度。DX 值的平滑,若采用怀尔德 EMA 计算 ADX,倾向于反映覆盖长久运行的趋势强度。这是最终的指标输出,高于 25 的数值标记强劲趋势,低于 20 的则暗示市场疲软或范围振荡。出于连贯性,重点要确保跨所有平滑步骤的 alpha 值一致。
return 语句生成一个包含 “adx”、“+di” 和 “-di” 列的数据帧,其为 ADX 指标的完整数据缓冲区集合。这为交易者提供了趋势分析所需的相关量值,附加了 “dx” 或 “tr” 中间列,能用来调试或定制指标。
特征
我们将这两个函数的读数并到一起,创造一个多维信号数组,本质上结合了 ADX(针对趋势强度)和 CCI(针对动量),识别特定市场条件,譬如趋势初启、逆转、或超买/超卖状态。自这两个指标合并后产生的信号,就是我们所说的更广泛层面上的“特征”。回想一下,在前四篇文章中,当我们研究 MA 和随机振荡器的指标配对时,我们得到 5 个主要数据集。这些分别是特征、状态、动作、奖励、和编码。此处的这些特征与我们当时得到的等同。
这两个指标的配对所采用的前提,就是自它们的配对生成的 10 个特征形态。当然,数量可能更多,但就我们的目的,这就足够了。我们为每个形态分配了一个函数。每个函数会返回一个 NumPy 数组,每列代表一个条件(维度),其中 1 表示条件已满足,0 表示不满足。这些函数将取 pandas 数据帧作为输入。这些输入被标记为 adx_df(“adx”、“+di”、“-di” 列);cci_df(“cci” 列);可选 price_df(“high”、“low”、“close” 列)。
我们利用 Python 实现这些函数,以便加快测试流程,但也需要实现类似的 MQL5 版本,从而可部署/运用在我们的最终智能系统。MQL5 内置的 CCI 和 ADX 中已具备指标点处理,意味当智能系统用到它们时,这些都不是问题。回顾一下,对于 Python 来说,应当验证 adx_df 和 cci_df,以便确保所需的列存在,NaN 值也需一同处理,或舍弃、或填充它值,以避免比较错误。移位操作如 shift(1) 会固有在第一行引入 NaN。因此,将第一行设置为 0 是管理这一点的标准方法。理想情况下,对于大数据集应当引入矢量化运算,因为所用的 NumPy where() 函数和 astype(int) 或许不够给力。
所有特征都是依据 EURUSD 的 2020 年 1 月 1 日至 2024 年 1 月 1 日的日线时间帧数据进行测试/训练。前向漫游测试区间为 2024.01.01 至 2025.01.01。只有特征-2、3 和 4 能够通过前向漫游测试,故此它们的结果会在各自的描述中分享。
特征-0
该形态基于 ADX > 25 且 CCI 在 ±100 交叉。它为突破动量、或趋势开端提供确认。25 水平对 ADX 具有重要意义,因此每当它与其交叉,且 CCI 与关键水平 100 交叉时,新趋势来临的概率很高。这两个指标的运用有助于过滤噪声。在寻找趋势时,等待 ADX 测试 25 水平始终很重要。任何低于 20 的都应避开。在以趋势闻名的资产,这是一种高概率的设定,比如像一些股票指数,等等。
自这些信号生成的特征向量是一个 6-维向量。我们的 Python 和 MQL5 实现如下:
def feature_0(adx_df, cci_df): """ Creates a 3D signal array with the following dimensions: 1. ADX > 25 crossover (1 when crosses above 25, else 0) 2. CCI > +100 crossover (1 when crosses above +100, else 0) 3. CCI < -100 crossover (1 when crosses below -100, else 0) """ # Initialize empty array with 3 dimensions and same length as input feature = np.zeros((len(adx_df), 6)) # Dimension 1: ADX > 25 crossover feature[:, 0] = (adx_df['adx'] > 25).astype(int) feature[:, 1] = (adx_df['adx'].shift(1) <= 25).astype(int) # Dimension 2: CCI > +100 crossover feature[:, 2] = (cci_df['cci'] > 100).astype(int) feature[:, 3] = (cci_df['cci'].shift(1) <= 100).astype(int) # Dimension 3: CCI < -100 crossover feature[:, 4] = (cci_df['cci'] < -100).astype(int) feature[:, 5] = (cci_df['cci'].shift(1) >= -100).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
if(Index == 0) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > 25 ? 1.0f : 0.0f); _features[1] = (_adx[1] <= 25 ? 1.0f : 0.0f); _features[2] = (_cci[0] > 100 ? 1.0f : 0.0f); _features[3] = (_cci[1] <= 100 ? 1.0f : 0.0f); _features[4] = (_cci[0] < -100 ? 1.0f : 0.0f); _features[5] = (_cci[1] >= -100 ? 1.0f : 0.0f); } }
我们已将该形态的 MLP 输入向量分解为其关键成分信号。这些是:ADX 之前低于 25,随后 ADX 高于 25,CCI 之前低于 +100,CCI 现在高于 +100,CCI 之前高于 -100,CCI 目前低于 -100。在这种设定下,显然不会所有状况能同时为真。它允许我们自定义所有价格数据点,替代围绕典型的形态逻辑来将它们分组。
传统上,看涨设定是当 ADX 从 25 下方穿过,并收于 25 上方,随后 CCI 也从 +100 下方穿过,并收于 +100 上方。同样,看跌形态是 ADX 再次穿过 25 关卡,并收于上方,但 CCI 从上方穿过 -100 关卡,并收于其下方。如果我们生成严格的向量,即仅测试这些“真实”的看涨或看跌设定,那么我们的输入向量将是大小为 3,且在庞大的测试数据中变异更小。我们选择的 6-维输入数据选项捕捉这些传统量值,也有“连续”数据,否则这些数据会被更多“离散/传统”设置所忽略。
特征-1
该形态围绕 ADX > 25 且 CCI 从对立面穿过 ±50 关卡。它是在已建立趋势中的再次入场动量。鉴于 ADX 确认趋势完整性,CCI 检测短期逆势后的复原,这对于回调后持续交易非常理想。这种形态适合顺势交易者,他们看重回踩后顺势入场。CCI 在穿过零轴也是一个重要提示,不应操之过急。对于顺势交易者,若正在寻求保护收益,可在该信号处设置尾随停止。我们的 Python 和 MQL5 实现如下:
def feature_1(adx_df, cci_df): """ Creates a modified 3D signal array with: 1. ADX > 25 (1 when above 25, else 0) 2. CCI crosses from below 0 to above +50 (1 when condition met, else 0) 3. CCI crosses from above 0 to below -50 (1 when condition met, else 0) """ # Initialize empty array with 3 dimensions feature = np.zeros((len(adx_df), 5)) # Dimension 1: ADX above 25 (continuous, not just crossover) feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: CCI crosses from <0 to >+50 feature[:, 1] = (cci_df['cci'] > 50).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) < 0).astype(int) # Dimension 3: CCI crosses from >0 to <-50 feature[:, 3] = (cci_df['cci'] < -50).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) > 0).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
else if(Index == 1) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > 25 ? 1.0f : 0.0f); _features[1] = (_cci[0] > 50 ? 1.0f : 0.0f); _features[2] = (_cci[1] < 0 ? 1.0f : 0.0f); _features[3] = (_cci[0] < -50 ? 1.0f : 0.0f); _features[4] = (_cci[1] > 0 ? 1.0f : 0.0f); } }
我们的函数生成一个 5-维二进制输出向量,其成分为:ADX 是否高于 25 关卡,之前的 CCI 是否高于 0;当前 CCI 是否低于或等于 -50;之前的 CCI 是否低于 0;当前的 CCI 是否高于或等于 50。传统上,看涨设定是 ADX 高于 25,而 CCI 之前低于零,但现在高于 50。看跌设定是,如果 ADX 再次高于 25,而 CCI 之前高于 0,但现在低于或等于 -50。上述有关行情更多朝着延续/回归移动,而非断断续续的观点,也适用于此。
特征-2
该形态基于 ADX > 25,且价格与 CCI 提供了背离。这也是一种背离设定、或趋势内的逆转。它采用经典的动量背离形态,并以 ADX 作为趋势滤波器。这表明即使趋势仍然有效,也潜在逆转可能。该形态适合与价格动作、或支撑/阻力结合的状况,以便更好地确认。它的理想状况,是在长时间移动后形成背离。不过建议谨慎,背离往往只是早期信号,因为若正处于强劲趋势,它们当中许多会误判。我们的 Python 和 MQL5 实现如下:
def feature_2(adx_df, cci_df, price_df): """ Creates a 5D signal array with: 1. ADX > 25 (1 when above 25, else 0) 2. Lower low (1 when current low < previous low, else 0) 3. Higher CCI (1 when current CCI > previous CCI, else 0) 4. Higher high (1 when current high > previous high, else 0) 5. Lower CCI (1 when current CCI < previous CCI, else 0) """ # Initialize empty array with 5 dimensions feature = np.zeros((len(price_df), 5)) # Dimension 1: ADX above 25 feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: Lower low feature[:, 1] = (price_df['low'] < price_df['low'].shift(1)).astype(int) # Dimension 3: Higher CCI feature[:, 2] = (cci_df['cci'] > cci_df['cci'].shift(1)).astype(int) # Dimension 4: Higher high feature[:, 3] = (price_df['high'] > price_df['high'].shift(1)).astype(int) # Dimension 5: Lower CCI feature[:, 4] = (cci_df['cci'] < cci_df['cci'].shift(1)).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
else if(Index == 2) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2 && CopyRates(Symbol(),Period(),T, 2, _r) >= 2) { _features[0] = (_adx[0] > 25 ? 1.0f : 0.0f); _features[1] = (_r[0].low <= _r[1].low ? 1.0f : 0.0f); _features[2] = (_cci[0] > _cci[1] ? 1.0f : 0.0f); _features[3] = (_r[0].high > _r[1].high ? 1.0f : 0.0f); _features[4] = (_cci[0] < _cci[1] ? 1.0f : 0.0f); } }
我们的函数输出是一个 6-维向量,包括:ADX 是否大于 25;价格低点下移更低;CCI 上移更高;价格高点上移更高;最后,CCI 是否下移更低。按说,看涨设定的组成是由 ADX 在 25 上方、在动量上升时录得更低的最低价,如 CCI 标记。类似地,看跌设定中 ADX 仍会在 25 以上,但价格将创下更高的最高价,而 CCI 却录得下落。


特征-3
该形态结合 ADX 上升,及中立区的 CCI。它以 CCI 处于中立区,权当趋势确认。它专注于上升或下降趋势中的持续动量。当 CCI 处于中性区(0 到 +/-100)时,它往往意味着价格维稳,不会极端移动。它倾向于比超买/超卖信号更安全,假入场风险更低。在波动较小的市场中,这可取之作为“趋势是您的朋友”。结合移动平均线排列、或价格结构,它能够提供更高的安全性。我们的 Python 和 MQL5 实现如下:
def feature_3(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX rising (1 when current ADX > previous ADX, else 0) 2. CCI between 0 and +100 (1 when in range, else 0) 3. CCI between 0 and -100 (1 when in range, else 0) """ # Initialize empty array with 3 dimensions feature = np.zeros((len(adx_df), 5)) # Dimension 1: ADX rising feature[:, 0] = (adx_df['adx'] > adx_df['adx'].shift(1)).astype(int) # Dimension 2: CCI between 0 and +100 feature[:, 1] = (cci_df['cci'] > 0).astype(int) feature[:, 2] = (cci_df['cci'] < 100).astype(int) # Dimension 3: CCI between 0 and -100 feature[:, 3] = (cci_df['cci'] < 0).astype(int) feature[:, 4] = (cci_df['cci'] > -100).astype(int) # Set first row to 0 (no previous ADX value to compare) feature[0, :] = 0 return feature
else if(Index == 3) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > _adx[1] ? 1.0f : 0.0f); _features[1] = (_cci[0] > 0 ? 1.0f : 0.0f); _features[2] = (_cci[1] < 100 ? 1.0f : 0.0f); _features[3] = (_cci[0] < 0 ? 1.0f : 0.0f); _features[4] = (_cci[1] > -100 ? 1.0f : 0.0f); } }
我们的函数生成一个 5-维向量,其组成:ADX 是否增加;CCI 是否大于 0;CCI 是否低于 +100;CCI 是否低于 0;以及 CCI 是否高于 -100。传统的看涨设定是上升的 ADX,CCI 在 0 以上,但低于 +100。翻折看跌形态同样是 ADX 上升,但 CCI 低于 0,但高于 -100。该形态强调 ADX 的上升(不仅是 > 25)。此外,中性 CCI 范围倾向于针对早期趋势发展阶段,这与特征-0 的极端交叉示例不同。


特征-4
该形态采用的设定是 ADX > 25,且 CCI 从极端处复原。这是一种失败摇摆设定。它捕捉到动量陷阱,其中 CCI 突破了极限关卡,却未能继续。ADX 的加入确保了这不是盘整内的拉锯状况。该形态往往在逆转、或急剧回弹之前见到。它最好用在波动交易时段(或新闻驱动事件,像是如非农就业报告发布)。此处关键是要观察失败的摇摆日的灯芯蜡烛(尖刺柱线),获得强烈的确认。我们的 Python 和 MQL5 实现如下:
def feature_4(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX > 25 (1 when above 25, else 0) 2. CCI dips below -100 then closes above it (1 when condition met, else 0) 3. CCI breaks above +100 then closes below it (1 when condition met, else 0) """ feature = np.zeros((len(cci_df), 5)) # Dimension 1: ADX above 25 feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: CCI dips below -100 then closes above it feature[:, 1] = (cci_df['cci'].shift(1) < -100).astype(int) feature[:, 2] = (cci_df['cci'] > -100).astype(int) # Dimension 3: CCI breaks above +100 then closes below it feature[:, 3] = (cci_df['cci'].shift(1) > 100).astype(int) feature[:, 4] = (cci_df['cci'] < 100).astype(int) return feature
else if(Index == 4) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > 25 ? 1.0f : 0.0f); _features[1] = (_cci[0] < -100 ? 1.0f : 0.0f); _features[2] = (_cci[1] > -100 ? 1.0f : 0.0f); _features[3] = (_cci[0] > 100 ? 1.0f : 0.0f); _features[4] = (_cci[1] < 100 ? 1.0f : 0.0f); } }
我们的特征-4 函数为我们给出一个 5-维向量,输出 1 和 0 二进制值:ADX 低于 20;CCI 低于 -100;CCI 现高于 -100;CCI 高于 +100;CCI 现低于 +100;典型的看涨信号要搜寻动量转移,因此若 ADX 低于 20,且 CCI 从 -100 下方移至 -100 上方之时。因此,如果 ADX 再次跌破 20,是盛行趋势疲弱迹象,而 CCI 从 +100 上方收于 +100 下方,表示正面动量下落,也是翻折看跌形态。


特征-5
该形态简单地采用 ADX < 20,且 CCI 极端激增。它作为低波动性即将动量激增的前兆。它在 ADX 低迷期间观察 CCI 的爆发,有助于发现突破的早期阶段。不仅如此,它还意图帮助交易者在趋势开始前建仓。当实现该形态时,采用紧凑的停止挡往往是个好主意,因为很多尖峰可能是假动作。当该形态与其它指标结合时,譬如布林带挤压、或成交量突破,可能带来额外优势。不过,它更适合较短的时间帧(例如 M15 到 H1),因为这些对于快速动量交易更便利。我们的 Python 和 MQL5 实现如下:
def feature_5(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX < 20 (1 when below 20, else 0) - indicates weak trend 2. CCI spikes above 150 (1 when condition met, else 0) - extreme overbought 3. CCI drops below -150 (1 when condition met, else 0) - extreme oversold """ # Initialize array feature = np.zeros((len(cci_df), 5)) # Dimension 1: ADX below 20 (weak trend) feature[:, 0] = (adx_df['adx'] < 20).astype(int) # Dimension 2: CCI spikes above 150 (sudden extreme overbought) # Using 2-bar momentum to detect "sudden" spikes feature[:, 1] = (cci_df['cci'] > 150).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) < 130).astype(int) # Dimension 3: CCI drops below -150 (sudden extreme oversold) # Using 2-bar momentum to detect "sudden" drops feature[:, 3] = (cci_df['cci'] < -150).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) > -130).astype(int) return feature
else if(Index == 5) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] < 20? 1.0f : 0.0f); _features[1] = (_cci[0] > 150 ? 1.0f : 0.0f); _features[2] = (_cci[1] < 130 ? 1.0f : 0.0f); _features[3] = (_cci[0] < -150 ? 1.0f : 0.0f); _features[4] = (_cci[1] > -130 ? 1.0f : 0.0f); } }
我们的特征-5 函数生成一个 5-维向量,投喂给我们的 MLP。捕获的信号是:ADX 是否低于 20;CCI 是否高于 +150;此前 CCI 是否低于 +130;CCI 是否低于 -150;最后 CCI 是否高于 -130。它用 ADX 在 25 以上的标记来确保强劲趋势正在上演,旨在检测 CCI 从极端关卡的复原,并按惯例专注于逆转、或动量转移。
典型的看涨设定是当 ADX 高于 25 时,CCI 曾低于 +130,且现在处于 +150。类似地,看跌设定也要求 ADX 必须高于 25,CCI 现处于 -150,之前测试处于 -130。
特征-6
该特征是关于 ADX 下穿 40 以下,且 CCI 与 ±100 交叉。针对趋势耗竭的离场信号,该形态以 ADX 下跌为标记,是趋势疲软的迹象。一旦 CCI 也回到中立或对立侧,这是动量衰败的预兆。它可被视为风险降低信号,作为调整尾随停止、或止盈的标记。它也能与烛条形态结合,从而干净利索地离场,不过,按该设定入场开立新交易往往不明智。我们的 Python 和 MQL5 实现如下:
def feature_6(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX crosses below 40 (1 when crosses down, else 0) 2. CCI crosses below +100 (1 when crosses down, else 0) 3. CCI crosses above -100 (1 when crosses up, else 0) """ # Initialize array with zeros feature = np.zeros((len(cci_df), 6)) # Dimension 1: ADX crosses below 40 feature[:, 0] = (adx_df['adx'] < 40).astype(int) feature[:, 1] = (adx_df['adx'].shift(1) >= 40).astype(int) # Dimension 2: CCI crosses below +100 feature[:, 2] = (cci_df['cci'] < 100).astype(int) feature[:, 3] = (cci_df['cci'].shift(1) >= 100).astype(int) # Dimension 3: CCI crosses above -100 feature[:, 4] = (cci_df['cci'] > -100).astype(int) feature[:, 5] = (cci_df['cci'].shift(1) <= -100).astype(int) return feature
else if(Index == 6) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] < 40? 1.0f : 0.0f); _features[1] = (_adx[1] < 40? 1.0f : 0.0f); _features[2] = (_cci[0] < 100 ? 1.0f : 0.0f); _features[3] = (_cci[1] >= 100 ? 1.0f : 0.0f); _features[4] = (_cci[0] > -100 ? 1.0f : 0.0f); _features[5] = (_cci[1] <= -100 ? 1.0f : 0.0f); } }
我们的特征-6 函数输出一个 6-维向量,包括:ADX 现是否低于 40;ADX 是否此前高于 40;CCI 是否低于 100;CCI 是否此前高于 100;CCI 是否高于 -100;最后,CCI 之前是否低于 -100。看跌终结形态(等同于看涨)是指 ADX 从 40 跌至 40 下方,而 CCI 曾低于 -100,但现在已高于该关卡。相较之,看涨终结形态(等同于看跌)是指 ADX 再次从 40 下跌,CCI 也从 +100 上方跌破该关卡。它最适合作为离场、或退出警告形态,不过于此包含它,是出于测试目的,当作入场信号。
特征-7
这个涉及 ADX > 25,且 CCI 与零线交叉。一旦 CCI 与零线交叉,它就是一个趋势捕捉器。这是因为 CCI 穿越零线,权当作为动量枢轴点。由于 ADX 也在确认强度,这一设定提供了干净的趋势中段入场。当价格节节走高、或节节走低时,该形态更可作为依靠。据该形态作为信号,在多个点入场可能值得研究。应当针对时间对齐、或会话波动窗口完成回测。我们的 Python 和 MQL5 实现如下:
def feature_7(adx_df, cci_df): """ Creates Feature-7 3D signal array with: 1. ADX > 25 (1 when above 25, else 0) - trend strength 2. CCI crosses above 0 (1 when bullish crossover, else 0) 3. CCI crosses below 0 (1 when bearish crossover, else 0) """ # Initialize array with zeros feature = np.zeros((len(cci_df), 5)) # Dimension 1: ADX above 25 (continuous signal) feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: CCI crosses above 0 (bullish) feature[:, 1] = (cci_df['cci'] > 0).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) <= 0).astype(int) # Dimension 3: CCI crosses below 0 (bearish) feature[:, 3] = (cci_df['cci'] < 0).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) >= 0).astype(int) return feature
else if(Index == 7) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > 25? 1.0f : 0.0f); _features[1] = (_cci[0] > 0? 1.0f : 0.0f); _features[2] = (_cci[1] <= 0 ? 1.0f : 0.0f); _features[3] = (_cci[0] < 0 ? 1.0f : 0.0f); _features[4] = (_cci[1] >= 0 ? 1.0f : 0.0f); } }
我们的特征-7 函数也返回一个 5-维输出向量。该向量记录:ADX 是否高于 25;CCI 是否高于 0;CCI 之前是否低于或等于 0;CCI 是否低于 0;最后,CCI 之前是否高于或等于 0。其典型形态是,如果 ADX 高于 25;且 CCI 曾低于 0,但现在高于 0,则暗示看涨信号。类似地,看跌形态是 ADX 再次高于 25,而 CCI 此前高于 0,现低于 0。
特征-8
我们的第九个特征信号依赖 ADX < 20,且 CCI 自极值回调。它相当于 ADX 强度过滤器,加上 CCI 超买/超卖逆转指标。这是经典的方位逆转,经 ADX 过滤。在疲软趋势或拉锯行情中,CCI 逆转倾向表现更好。理想情况下,它应仅在 ADX 真的很低(低于 20)时使用,且不应在趋势行情中应用。与布林带或 RSI 配对,适合多指标确认。这种逆转形态对于均值回归资产可能很理想,像是大宗商品、或货币对。我们的 Python 和 MQL5 实现如下:
def feature_8(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX < 20 (1 when below 20, else 0) - weak trend 2. CCI rises from -200 to -100 (1 when condition met, else 0) - extreme oversold bounce 3. CCI falls from +200 to +100 (1 when condition met, else 0) - extreme overbought pullback """ # Initialize array with zeros feature = np.zeros((len(cci_df), 5)) # Dimension 1: ADX below 20 (weak trend) feature[:, 0] = (adx_df['adx'] < 20).astype(int) # Dimension 2: CCI rises from -200 to -100 # Using 2-bar lookback to detect the move feature[:, 1] = (cci_df['cci'] > -100).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) <= -200).astype(int) # Dimension 3: CCI falls from +200 to +100 # Using 2-bar lookback to detect the move feature[:, 3] = (cci_df['cci'] < 100).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) >= 200).astype(int) return feature
else if(Index == 8) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] < 20? 1.0f : 0.0f); _features[1] = (_cci[0] > -100? 1.0f : 0.0f); _features[2] = (_cci[1] <= -200 ? 1.0f : 0.0f); _features[3] = (_cci[0] < 100 ? 1.0f : 0.0f); _features[4] = (_cci[1] >= 200 ? 1.0f : 0.0f); } }
特征-8 的函数输出也是 5-维二进制值向量。这些,标记:ADX 是否低于 20;CCI 超过 -100;CCI 低于 -200;CCI 低于 +100;高于 +200。因此,来自这些形态的看涨信号是在 ADX 低于 20,且 CCI 此前测试 -200 后上穿 -100 时。相较之,看跌形态是 ADX 再次跌破 20,CCI 此前在 +200 以上,后跌破 +100。
特征-9
我们的最后一个特征再次采用 ADX > 25,且 CCI 延迟交叉。该形态代表对立信号抑制、或陷阱滤波器。它擅长发现与盛行趋势对立的假突破、或假动作。在这些设定下,价格往往暗示逆转,不过 CCI 通过重申盛行趋势来拒绝这一观点。它适合防范趋势陷阱。它可结合烛条确认、或假动作后成交量下降。那些因虚假先验信号而被“灼伤“、需要一款置信过滤器的交易者来说,这很不错。我们的 Python 和 MQL5 实现如下:
def feature_9(adx_df, cci_df): feature = np.zeros((len(cci_df), 7)) cci = cci_df['cci'].values # Dimension 1 feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: Below 0 then above +50 within 20 periods feature[:, 1] = (cci < 0).astype(int) feature[:, 2] = (np.roll(cci, 1) >= 0).astype(int) below_zero = (feature[:, 1]==1) & (feature[:, 2]==1) feature[:, 3] = 0 for i in np.where(below_zero)[0]: if i+20 < len(cci) and np.max(cci[i+1:i+21]) > 50: feature[:, 3] = 1 break # Dimension 3: Above 0 then below -50 within 20 periods feature[:, 4] = (cci > 0).astype(int) feature[:, 5] = (np.roll(cci, 1) <= 0).astype(int) feature[:, 6] = 0 above_zero = (feature[:, 4]==1) & (feature[:, 5]==1) for i in np.where(above_zero)[0]: if i+20 < len(cci) and np.min(cci[i+1:i+21]) < -50: feature[:, 6] = 1 break return feature
else if(Index == 9) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 21) { _features[0] = (_adx[0] > 25? 1.0f : 0.0f); _features[1] = (_cci[0] < 0? 1.0f : 0.0f); _features[2] = (_cci[1] >= 0 ? 1.0f : 0.0f); _features[3] = (_cci[ArrayMaximum(_cci,1,20)] > 50 ? 1.0f : 0.0f); _features[4] = (_cci[0] > 0? 1.0f : 0.0f); _features[5] = (_cci[1] <= 0 ? 1.0f : 0.0f); _features[6] = (_cci[ArrayMinimum(_cci,1,20)] < -50 ? 1.0f : 0.0f); } }
该形态生成一个 7-维向量,映射为:ADX 大于 25;CCI 是否低于 0;之前的 CCI 是否高于 0;之前 20 根柱线至最后一根柱线期间,CCI 是否高于 50 关卡;CCI 是否高于 0;之前的 CCI 是否低于 0;最后,在之前 20 根柱线至最后一根柱线期间,CCI 是否跌破了 -50。
来自这些形态生成的指示信号如下。为了获得看涨信号,ADX 需要高于 25,且 CCI 需要在跌破 0 之前曾尝试高于 0,且在下跌前 20 根柱内最高达至 50。同样,看跌形态是指 CCI 刚跌破 0,之前曾在 0上方,且在 0 上方前的 20 根柱线内至少曾低于 -50。该形态的测试结果未进行前向漫游测试,故此就不分享了,但所有源码都附在底部,供后续独立测试。
结束语
我们在监督学习的 MLP 中研究了 ADX 和 CCI 的联动形态,前向漫游测试结果优劣参杂。这是一次尝试,输入向量更连续、更低离散,正如我们在有关移动平均和随机振荡器的第 57 篇文章中所做的那样。尽管这或许是罪魁祸首,我们在接下来的文章中仍会坚持该方式,在于我们也要见识其它机器学习方法如何配合这些指标运作。
| 名称 | 描述 |
|---|---|
| 61_0.onnx | 针对形态-0 的 MLP: |
| 61_1.onnx | 针对形态-1 的 MLP: |
| 61_2.onnx | 针对形态-2 的 MLP: |
| 61_3.onnx | 针对形态-3 的 MLP: |
| 61_4.onnx | 针对形态-4 的 MLP: |
| 61_5.onnx | 针对形态-5 的 MLP: |
| 61_6.onnx | 针对形态-6 的 MLP: |
| 61_7.0nnx | 针对形态-7 的 MLP: |
| 61_8.onnx | 针对形态-8 的 MLP: |
| 61_9.onnx | 针对形态-9 的 MLP: |
| 61_x.mqh | 形态处理文件 |
| SignalWZ_61.mqh | 信号类文件 |
| wz_61.mq5 | 向导汇编的智能系统,展示包含文件 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17910
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
在 MQL5 中实现其他语言的实用模块(第 02 部分):构建受 Python 启发的 REQUESTS 库
价格行为分析工具开发(第二十八部分):开盘区间突破工具
新手在交易中的10个基本错误
从新手到专家:使用 MQL5 制作动画新闻标题(五)—— 事件提醒系统