您应该了解的MQL5向导技巧(第七十部分):结合指数核网络使用SAR与RVI形态
引言
在前一篇文章中,我们介绍了SAR与RVI 这一互补指标组合。我们对10种形态进行了测试,其中索引为1、2、6的三种形态未能实现前向步进。我们将这些形态从0到9进行索引编号,以便智能交易系统(EA)通过计算映射值精准调用对应的形态。例如,如果形态索引为1,则参数PatternsUsed需设置为2的1次方,即2;
如果索引为2,则为2的2次方,即4,以此类推。由于总共有10种形态,因此该参数的最大有效值为1023。0至1023之间非2的纯幂次的数值,均代表多种形态的组合,读者可以自行研究让EA同时使用多个形态。然而,基于前文的论证与测试结果,在本系列教程中我们暂不展开探讨这部分内容。
正如我们近期其中一篇文章中所承诺的,本文将通过监督学习,尝试优化上一篇中表现不佳的形态信号1、2和6,使其重新激活。在将机器学习应用于MQL5指标信号时,我们选择使用Python进行网络模型的编码与训练。这是因为即便在无GPU的环境下,Python依然能提供很高的开发与运行效率。使用Python时,我们依托MetaTrader Python模块,只需填入交易账户的登录名与密码,即可连接至经纪商的MT服务器。
一旦通过 MetaTrader 5 Python模块建立连接,我们就能获取经纪商提供的行情价格数据。Python虽有技术指标相关库,但通常需要额外安装,且格式往往较为冷门甚至不太直观。所幸,从基础原理自行实现这些指标并不算复杂。因此,我们首先在Python中实现本文的两个核心指标:SAR与RVI。
抛物转向指标(SAR)函数
SAR是一种趋势跟踪类指标,用于识别价格方向上可能的趋势反转点。它通过点位标记来标识当前主导趋势,这些点位会落在潜在止损位的一侧。当处于多头趋势时,SAR点位会出现在价格低点下方;当处于空头趋势时,SAR点位会出现在价格高点上方。我们将按如下方式编写计算SAR的函数代码:
def SAR(df: pd.DataFrame, af: float = 0.02, af_max: float = 0.2) -> pd.DataFrame: """ Calculate Parabolic SAR indicator and append it as 'SAR' column to the input DataFrame. Args: df (pd.DataFrame): DataFrame with 'high', 'low', 'close' columns af (float): Acceleration factor, default 0.02 af_max (float): Maximum acceleration factor, default 0.2 Returns: pd.DataFrame: Input DataFrame with new 'SAR' column """ if not all(col in df.columns for col in ['high', 'low', 'close']): raise ValueError("DataFrame must contain 'high', 'low', 'close' columns") if af <= 0 or af_max <= 0 or af > af_max: raise ValueError("Invalid acceleration factors") if df.empty: raise ValueError("DataFrame is empty") result_df = df.copy() result_df['SAR'] = 0.0 sar = df['close'].iloc[0] ep = df['high'].iloc[0] af_current = af trend = 1 if len(df) > 1 and df['close'].iloc[1] > df['close'].iloc[0] else -1 for i in range(1, len(df)): prev_sar = sar high, low = df['high'].iloc[i], df['low'].iloc[i] if trend > 0: sar = prev_sar + af_current * (ep - prev_sar) if low < sar: trend = -1 sar = ep ep = low af_current = af else: if high > ep: ep = high af_current = min(af_current + af, af_max) else: sar = prev_sar + af_current * (ep - prev_sar) if high > sar: trend = 1 sar = ep ep = high af_current = af else: if low < ep: ep = low af_current = min(af_current + af, af_max) result_df.loc[i, 'SAR'] = sar return result_df
我们实现的上述函数,接收一个包含最高价(high)、最低价(low)、收盘价(close)列的pandas数据帧,以及两个浮点型参数 —— 加速因子。最终输出会将计算结果作为新列,追加到输入数据帧中,列名为SAR。该函数的整体逻辑,是基于极值点(EP)与加速因子(AF),跟踪趋势方向并更新SAR值。
首先,我们创建输入数据框的副本,避免修改原始数据。这一步会将high、low列提取为NumPy数组,以提升计算效率。随后,我们用零值初始化sar数组,用于存储SAR值。这一步至关重要,既能保证数据完整性,也为SAR计算做好数据结构准备。使用.copy()可有效避免意外的副作用。在迭代计算场景下,NumPy数组的性能通常优于pandas序列。
我们将初始趋势设为多头(uptrend),即trend = 1。极值点初始化为首个最高价,将初始SAR值设置为首个最低价。加速因子从输入参数af-start开始。这一初始化设定至关重要,它通过假设初始多头趋势,为SAR计算定义了起点。将low[0]设置为初始SAR值,与SAR在多头趋势中作为止损位的作用一致 —— 它会在最低价下方,由af-start参数权重调整,使其与市场波动相匹配。
接下来,我们执行SAR公式更新。这一更新会让SAR向极值点靠拢,靠拢幅度根据加速因子缩放。这是SAR公式的核心,体现了趋势展开时价格的抛物线式偏移特性。实现时需要注意的是,加速因子(af)初始值要小,避免趋势启动后步长过于激进;同时要在高波动市场中监控数值稳定性。
完成此操作后,我们继续设置趋势反转逻辑。当处于多头趋势时,如果SAR值超过当期最低价,则判定趋势反转至空头:将SAR重置为上期极值点,且将新极值点设置为当期最低价。加速因子重置为默认初始值。
趋势反转检测是SAR用于发出入场/出场信号的核心功能。该条件确保SAR在多头趋势中始终位于价格下方。可通过调整af-increment参数,测试反转信号的灵敏度。
如果多头趋势延续,需持续更新SAR缓冲区,并递增加速因子(af-increment)。SAR值会被限制为当期值与前两期最低价中的较小值,防止其侵入价格区间。此外,一旦当期最高价超过极值点,则更新极值点,同时加速因子递增,但不超过af-max设定的上限。这一维护步骤确保SAR始终是有效的止损位,且仅随趋势同步加速。使用前两期最低价可提升稳健性,但在小周期场景下可能需要调整。
接下来,我们还将探讨SAR的空头趋势逻辑。在很多方面,空头趋势逻辑与多头逻辑对称。如果SAR值低于当期最高价,则反转至多头;如果无反转,SAR值被限制为当期值与前两期最高价中的较大值;极值点与加速因子同步更新。这一步使SAR能对称跟踪多空趋势。实现时务必保证多空逻辑对称,避免产生偏向性。可通过历史数据回测验证反转信号的准确性。
完成SAR逻辑后,我们继续执行输出赋值。将计算结果赋值给数据框的新列SAR,并返回处理后的数据帧。这一步将指标集成到数据帧中,便于后续分析与可视化。实际使用时,建议验证SAR列与价格数据的对齐性,可通过Matplotlib等绘图库可视化SAR点位。
综上所述,SAR最适合趋势性市场,可用于提示止损位、识别反转点,在震荡或横盘市场中效果不佳。参数调优对SAR并非至关重要,但部分品种可能需要调整加速因子初始值与步长(常用默认值为0.02和0.2)。通过经纪商历史数据回测验证,是有效的优化方式。SAR的主要局限在于,在快速波动市场中易产生滞后,发出大量虚假信号。
RVI函数
RVI的核心作用是跟踪趋势的强度,我们可将其直接等同于动量。其计算原理是:比较收盘价在当期价格波动区间中的相对位置,再通过移动平均对结果进行平滑处理。作为震荡指标,RVI可用于通过动量验证趋势,或识别顶底背离信号。在Python中的具体实现如下:
def RVI(df: pd.DataFrame, period: int, signal_period: int) -> pd.DataFrame: """ Calculate Relative Vigor Index (RVI) with signal line and append as 'RVI' and 'RVI_Signal' columns. Args: df (pd.DataFrame): DataFrame with 'open', 'high', 'low', 'close' columns period (int): Lookback period for RVI calculation signal_period (int): Lookback period for signal line calculation Returns: pd.DataFrame: Input DataFrame with new 'RVI' and 'RVI_Signal' columns """ # Input validation if not all(col in df.columns for col in ['open', 'high', 'low', 'close']): raise ValueError("DataFrame must contain 'open', 'high', 'low', 'close' columns") if period < 1 or signal_period < 1: raise ValueError("Period and signal period must be positive") # Create a copy to avoid modifying the input DataFrame result_df = df.copy() # Calculate price change and range close_open = df['close'] - df['open'] high_low = df['high'] - df['low'] # Calculate SMA for numerator and denominator num = close_open.rolling(window=period).mean() denom = high_low.rolling(window=period).mean() # Calculate RVI result_df['RVI'] = num / denom * 100 # Calculate signal line (SMA of RVI) result_df['RVI_Signal'] = result_df['RVI'].rolling(window=signal_period).mean() return result_df
由以上代码可见,通常来说,我们传入一个包含开盘价、最高价、最低价和收盘价列的pandas数据帧。此外,输入参数还包含一个用于信号缓冲区的SMA移动平均周期。输出结果会将经过平滑处理的RVI以及对应的RVI信号线追加为数据帧的新列。其基本逻辑:RVI通过(收盘价 − 开盘价)/(最高价 − 最低价)计算。接下来,用4周期SMA进行平滑,并根据用户自定义的SMA周期生成信号线。
如果逐行来看,函数中首先要做的是创建输入数据帧的副本。这是为了保留原始数据,避免对输入数据帧做意外的修改。这一点很重要,而使用复制函数可以轻松实现,避免产生非预期的“副作用”。
之后我们计算RVI。原始RVI的计算方式是价格变动幅度与交易区间的比值。这一步至关重要,因为它捕捉了价格相对于当日波动区间的运动活力,构成了 RVI 的计算基础。需要确保最高价与最低价不相等,以避免除零错误,不过此问题会在下一步处理。当前公式反映的是日内动量。
为处理除零错误,我们可将最高价等于最低价时可能产生除数为零情况下的无穷大值替换为NaN,可再将NaN替换为0。这对保证数值稳定性、避免后续计算出错非常重要。需要说明的是,这只是一种简易处理方式;如果零值过度扭曲RVI输出结果,读者可以考虑其他处理方案。
完成后,我们对RVI进行平滑并定义信号线。首先对原始按参数period对分子与分母进行SMA平滑,以降低噪声影响以及前面提到的零星零值干扰,从而形成RVI指标主线。在此基础上,再以用户指定period的SMA对已经平滑过的RVI进行二次平滑,以生成信号线。这一整步非常关键,平滑处理让RVI更易于解读,信号线同样有助于我们更明确地识别出交叉点。在实际实现中,通常固定使用4周期SMA来平滑原始RVI,但如果需要微调,信号线的SMA周期可调,一般可选用6至14区间的数值。
处理完成后进行结果赋值。平滑后的RVI和信号线分别作为新列加入输出数据帧。理论上讲,这使得RVI及其信号线都可以用于后续分析或可视化,例如绘制两条曲线以突出交叉信号。
RVI 作为动量指标,用于确认趋势强度。它还能及早识别背离,比如价格创出新高而RVI并未同步新高的情况。此外,RVI与自身信号线的交叉可用于辅助确认看涨或看空前景。RVI 的参数调优主要围绕信号线平滑所用的SMA周期展开:周期较短(6左右)可获得更快、更频繁的信号;周期在14左右则信号更平滑。通过历史数据测试RVI交叉与背离信号,对验证信号可靠性十分重要。RVI的主要局限在于,由于采用双重平滑,指标存在一定滞后性。在低波动市场中,也难以产生有效信号。
SAR与RVI的互补性
与本系列中我们选用的大多数指标组合一样,之所以选中这组指标,是因为二者能够相互互补。SAR是趋势跟踪指标,直接显示在价格主图上。RVI是震荡指标,在价格图表之外的独立窗口显示,主要用于衡量动量。SAR更适合用于设置止损,而RVI更擅长确认趋势或识别背离。
二者均基于Python实现,依靠pandas处理数据,NumPy则用于加速计算。SAR采用迭代式逻辑,因涉及趋势反转,整体更为复杂;而RVI使用向量化运算,逻辑相对简单,但容易出现除零问题。因此,对需要迭代计算的SAR使用NumPy数组,对向量化运算的RVI使用pandas数据帧,能够显著提升这组指标的运行效率。
读者还可以对该指标组合做进一步扩展,包括:在Python中使用Zipline等库进行回测;便于手动交易者查看的可视化,绘制SAR点位以及RVI与其信号线;完善错误处理,比如在代码中增加输入校验,检查pandas数据帧是否包含必需列、指标周期是否合法;对单个指标进行功能扩展。例如,可以为RVI增加背离检测,或改用其他平滑方法。
精选信号形态
特征1
在前一篇文章中,大部分形态都能实现一定程度的前向步进,只有索引为1、2、6的形态表现不佳。此前我们一直聚焦于表现良好的形态,研究如何用机器学习进一步提升优势。然而,在本文及后续类似文章中,我们将用机器学习改善那些初步测试中无法有效外推的形态的表现。
在Python中,与MQL5类似,每个形态都需要对应一个函数,输出true/false信号. 当形态出现时为true,未出现时为false。这些布尔值会被转换为0和1的二元信号,用于整合两个指标与价格数据构成交易策略。每个函数会生成一个两列特征数组:第0列记录多头信号;第1列记录空头信号。每一列中,0表示无信号,1表示触发信号。
正如前一篇文章所提及的,特征1的逻辑是:当价格持续运行在SAR上方/下方,且同时RVI处于上升/下降状态时,检测趋势。其核心是捕捉价格与SAR的持续对应关系,以及RVI所反映的动量。我们在Python中实现该函数的代码如下:
def feature_1(sar_df, rvi_df, price_df): """ """ feature = np.zeros((len(sar_df), 2)) feature[:, 0] = ((price_df['low'].shift(1) > sar_df['SAR'].shift(1)) & (price_df['low'] > sar_df['SAR']) & (rvi_df['RVI'] > rvi_df['RVI'].shift(1))).astype(int) feature[:, 1] = ((price_df['high'].shift(1) < sar_df['SAR'].shift(1)) & (price_df['high'] < sar_df['SAR']) & (rvi_df['RVI'] < rvi_df['RVI'].shift(1))).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
对于上述代码,我们首先做的是初始化一个全零数组,形状为[输入K线长度, 2]。末尾设置的两列,分别用于记录多头信号和空头信号。这一步非常重要,它为存储二元信号提供了干净的空间,避免出现未初始化的脏值。使用np.zeros效率更高,也能防止数值计算中出现意外结果,因为空白数组有时会被自动填充为NaN。
初始化完成后,我们开始设置多头信号的值:在第0列中,当同时满足以下条件时赋值为1:前一根K线的低点高于当前SAR,表明多头趋势持续;当前低点高于当前SAR;当前RVI高于前一根K线的RVI,表明动量在上升。只有同时满足所有条件,才能捕捉到强劲的上涨趋势,因为SAR确认趋势方向,RVI确认动量强度。编写时务必保证shift(1)数据对齐正确,缺失或错位的数据会导致严重错误。使用astype(int)可以将布尔值(true/false)转换为0/1二元数值。
多头列设置完成后,我们接着设置空头列。空头条件与多头条件完全对称,因此,同一个特征向量中几乎不会同时出现多头与空头信号(均为1)的情况。此外,我们将特征向量长度限制为2,只保留纯粹多头或纯粹空头的信号。我们之前也讨论并实现过更长的特征向量,逐点位记录各个指标的独立信号。但在这些条件下进行的测试,前向步进表现均不佳,除非使用极短的测试窗口。因此最终我们选择保留长度为2的结构,只记录多头与空头两类信号。
因此,对于第二列,我们在满足以下条件时设置空头信号:前一根K线的高点低于前一期SAR,且当前高点也低于当期SAR;同时,当前RVI低于前一期RVI,表明动量正在回落。借助SAR与RVI的互补性,所有这些条件共同定义了一段强劲的下跌趋势。与上面的多头逻辑类似,我们需要检查数据是否对齐,并处理由shift产生的NaN值。
在定义好多头与空头信号后,我们对输出数组进行调整,以适配滞后对比带来的偏移。具体做法是将前两行赋值为0,避免出现无效信号。这同时也能避免开头因数据不完整而产生虚假信号。按照惯例,在使用滞后数据时,将初始若干行置空是保证模型稳健性的常用做法。覆盖训练集与前向步进的测试结果,相关报告如下:


引入监督学习后,模型效果明显更优。未使用监督学习时,形态1(pattern-1)的图表如下:

特征2
我们测试的第三种形态同样未通过前向步进。正如前一篇文章中所探讨的,该特征形态用于捕捉潜在反转:价格突破SAR,同时RVI 显示出相应动量。它还通过收盘价纳入了价格运行方向,作为额外的趋势判断依据。在Python中的具体实现如下:
def feature_2(sar_df, rvi_df, price_df): """ """ feature = np.zeros((len(sar_df), 2)) feature[:, 0] = ((price_df['high'].shift(1) <= sar_df['SAR'].shift(1)) & (price_df['low'] >= sar_df['SAR']) & (rvi_df['RVI'] > rvi_df['RVI'].shift(1)) & (price_df['close'].shift(1) > price_df['close'])).astype(int) feature[:, 1] = ((price_df['low'].shift(1) >= sar_df['SAR'].shift(1)) & (price_df['high'] <= sar_df['SAR']) & (rvi_df['RVI'].shift(1) > rvi_df['RVI']) & (price_df['close'] > price_df['close'].shift(1))).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
这里同样先初始化一个全零数组,用于存储多头与空头信号。如前所述,这样做可以避免一开始就出现NaN值,保证函数输出结果稳定可预期。统一的初始化方式对后续数据处理至关重要。
接下来定义多头信号的触发条件。必须全部满足才会标记为1,否则为0。这也是使用全零数组更稳妥的原因,可以避免错误录入。因此,如果前一期高点处于或低于前一期SAR;且当前低点处于或高于当前SAR,意味着发生了翻转;同时RVI正在上升;收盘价下降以确认背离形态成立。这套条件用于捕捉多头反转:价格向上突破SAR,同时RVI提供动量支撑。该形态包含多项条件,必须全部成立,才能标记为有效信号(true/1)。
空头信号条件则记录在特征数组的第二列。对于看跌值,前一期低点处于或低于前一期SAR;当前高点处于或低于当前SAR;RVI正在下降;收盘价上涨以确认背离形态成立。信号形态2用于识别空头背离,保证各数据列之间对齐一致非常重要。接下来,我们将特征数组的前几行置为0,以适配shift滞后对比操作,避免因滞后数据缺失而产生无效信号。这是处理具有滞后性的时间序列特征时的标准做法。
理论上,特征2可以与前面的信号形态1结合使用,因为二者存在一定互补关系:特征1是纯粹的趋势跟踪体系,而特征2则在背离位置捕捉反转机会,二者结合可以引入波段交易逻辑,让整体系统更稳健。然而,正如之前文章所论述的,混合不同形态容易导致信号过早相互抵消。因此,这类组合系统的测试需要使用大量历史数据,且前向步进的结论必须足够可靠。对该形态的训练与测试覆盖了训练期和测试期,结果如下。再次可见,引入机器学习后,效果明显更优:


未使用监督学习时,形态2(pattern-2)的图表如下:

特征6
我们在前一篇文章提到的最初10个特征中,最后一个测试特征基于信号形态6。该形态不再仅依靠RVI动量进行确认,而是使用RVI与其信号线交叉。它专注于捕捉由SAR趋势配合RVI 交叉共同确认的强趋势信号。在Python中的具体实现如下:
def feature_6(sar_df, rvi_df, price_df): """ """ feature = np.zeros((len(sar_df), 2)) feature[:, 0] = ((price_df['low'].shift(1) > sar_df['SAR'].shift(1)) & (price_df['low'] > sar_df['SAR']) & (rvi_df['RVI'] > rvi_df['RVI_Signal']) & (rvi_df['RVI'].shift(1) < rvi_df['RVI_Signal'].shift(1))).astype(int) feature[:, 1] = ((price_df['high'].shift(1) < sar_df['SAR'].shift(1)) & (price_df['high'] < sar_df['SAR']) & (rvi_df['RVI'] < rvi_df['RVI_Signal']) & (rvi_df['RVI'].shift(1) > rvi_df['RVI_Signal'].shift(1))).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
我们依旧先创建一个指定大小的全零NumPy数组来存储输出结果。这种初始化方式非常重要,与其他函数保持一致,可以确保输出不含NaN值,同时也能维持结构稳定,方便后续处理使用。
之后,我们定义多头信号条件,记录在第0列。当满足以下全部条件时标记为1:前一根K线低点高于SAR;当前低点高于当期SAR;当前RVI位于其信号线之上;且前一期RVI位于其信号线之下。
定义好多头形态之后,我们再设置空头条件:前一期高点低于前一期SAR;当前高点同样低于当期SAR;当前RVI位于其信号线之下;同时前一期RVI 位于其信号线之上。与前两个形态一样,对该形态进行训练和测试后,得到如下测试报告。再次可见,该形态似乎实现了较为理想的前向步进。


未使用监督学习时,形态6(pattern-6)的图表如下:

网络
在测试上述筛选出的3种形态时,我们使用了一个一维卷积神经网络(1D CNN)。该网络包含三层卷积层,每层的卷积核数量与核尺寸均呈指数级递增。卷积层之后接入最大池化层,并进行展平操作。最后,通过全连接层输出最终预测结果。其Python代码如下:
class ExpConv1DNetwork(nn.Module): def __init__(self, input_length, input_channels=1, base_filters=16, base_kernel_size=3, exp_base=2): super(ExpConv1DNetwork, self).__init__() self.conv_layers = nn.ModuleList() self.pool_layers = nn.ModuleList() for i in range(3): filters = int(base_filters * (exp_base ** i)) kernel_size = int(base_kernel_size * (exp_base ** i)) | 1 self.conv_layers.append( nn.Conv1d( in_channels=input_channels if i == 0 else int(base_filters * (exp_base ** (i-1))), out_channels=filters, kernel_size=kernel_size, padding='same' ) ) # Use smaller kernel size for pooling to prevent size reduction to 0 self.pool_layers.append(nn.MaxPool1d(kernel_size=2, ceil_mode=True)) self.flatten_size = self._get_flatten_size(input_length, input_channels, base_filters, exp_base) self.flatten = nn.Flatten() self.dense1 = nn.Linear(self.flatten_size, 128) self.relu = nn.ReLU() self.dropout = nn.Dropout(0.5) self.dense2 = nn.Linear(128, 1) def _get_flatten_size(self, input_length, input_channels, base_filters, exp_base): current_length = input_length current_channels = input_channels for i in range(3): current_channels = int(base_filters * (exp_base ** i)) # Update length after pooling current_length = (current_length + 1) // 2 # Ceiling division for ceil_mode=True return current_channels * current_length def forward(self, x): for conv, pool in zip(self.conv_layers, self.pool_layers): x = self.relu(conv(x)) x = pool(x) x = self.flatten(x) x = self.relu(self.dense1(x)) x = self.dropout(x) x = self.dense2(x) return x
该网络要求输入为一维格式,形状为:批量大小、输入通道数、序列长度。网络还可通过基础卷积核数量、基础核大小、指数基数等参数进一步自定义。模型输出为单个标量,训练后取值范围在0至1之间,其中,0代表空头预测,1代表多头预测。逐行详解此类的代码是很有必要的,因为它直接决定了我们改进后信号形态的整体表现;但受限于文章篇幅,这部分内容暂时搁置,后续同类文章中我们会再详细展开。
结论
我们探讨了基于GBP/CHF货币对仅2年的有限数据窗口下,为原本无法通过前向步进的信号形态加入监督学习后,所带来的潜在改进效果。本次测试依旧在极为严格的限制条件下进行,但结果显示,相比上一篇文章中的表现,模型效果出现了积极提升。这一结果令人鼓舞。但一如既往地,读者在采纳本文所展示的任何思路之前,仍需自行开展独立、更全面的严谨验证。
| 名称 | 描述 |
|---|---|
| WZ-70.mq5 | 头文件,指示向导组装过程中使用的文件 |
| SignalWZ_70.mqh | 自定义信号类文件,供MQL5向导使用 |
| 70_1.onnx | 形态1导出的网络模型 |
| 70_2.onnx | 形态2导出的网络模型 |
| 70_6.onnx | 形态6导出的网络模型 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18433
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
机器学习交易系统中的隐马尔可夫模型
新手在交易中的10个基本错误
基于生物地理学的优化算法(BBO)