量化交易软件:在图表上快速检验交易理念

简介

第六届自动交易锦标赛终于升起帷幕。在最初的兴奋过去后,赫兹量化终于可以稍微放松一点,研究提交的交易机器人。我决定做一点调查研究,找出现代交易机器人最显著的特征,并明确什么是我们可以从它们的交易活动所期待的。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

事实证明,这相当困难。因此,我的计算不能说是毫发无差或尽善尽美,毕竟我有的只是“EA 交易”的说明和开发人员寥寥数语的注释。然而,我们仍然能够得出结论,下面是我的计算结果:锦标赛共计有 451 例参赛“EA 交易”,但只有其中的 316 例包含有意义的说明。其余“EA 交易”的说明尽是对开发人员的朋友和家人的问候、对地外文明的问候或自我赞许。

ATC 2012 最受欢迎策略:

  • 使用各种图形结构(重要价格水平、支撑位和阻力位、通道)交易– 55;

  • 价格变动分析(针对各种时间表)– 33;

  • 趋势追踪系统(我猜,这些大话隐藏了一些过度乐观的移动平均线组合,但也许是我错了):)- 31;

  • 统计价格模式 – 10;

  • 仲裁、交易品种相关分析 – 8;

  • 波动分析 – 8;

  • 神经网络 – 7;

  • 烛形分析 – 5;

  • 平均线 – 5;

  • 策略包 – 5;

  • 交易时段时间 – 4;

  • 随机数生成器 – 4;

  • 交易新闻 – 3;

  • 艾略特波浪 – 2。

当然,指标策略照惯例是最受欢迎的。很难定义每个特定指标在一个特定“EA 交易”中的角色,但估计它们使用的绝对数量是可能的:

  • 移动平均线 – 75;

  • MACD – 54;

  • 随机摆动 – 25;

  • RSI – 23;

  • 布林带 – 19;

  • 分形 – 8;

  • CCI、ATR – 各 7 个指标;

  • 峰谷、抛物线转向 – 各 6 个指标;

  • ADX – 5;

  • 动量 – 4;

  • 自定义指标(相当引人入胜 :))- 4;

  • Ichimoku、AO – 各 3 个指标;

  • ROC、WPR、StdDev、交易量 – 各 2 个指标。

数据表明了下述结论 - 大部分参赛者使用交易跟随策略和指标。或许,我在收集数据时有所疏漏,我们将见证自动交易领域中杰出人物的横空出世 - 但目前似乎是不可能的。我认为主要的问题在于,受市场吸引而来的新人被灌输的多是规则而不是知识。

例如,这些是使用 MACD 的规则,这些是信号 - 现在,最优化参数和赚钱!多动一点脑筋怎么样?多此一举!标准已经建立!为什么要推倒重来?然而,我们常常忘记,现在如此流行的指标也是像您我这样的交易人员所开发!他们也有自己的标准和权威。也许,以您的名字冠名的新指标在数十年后也会成为标准。

我愿意分享我的交易理念探寻方法,以及用于快速检验这些理念的方法。

方法说明

所有技术分析都基于一个简单的道理 - 价格反映一切。但有一个问题 - 这种说法缺乏力度。赫兹量化将目光转向图表并看到一个静态图像:价格实际上反映了所有一切。然而,赫兹量化希望知道价格在未来某个时期内将反映什么以及它的走向,这样我们就能盈利。源于价格的指标正是设计用于预测可能的未来变动。

赫兹量化从物理学得知,量值的一阶导数是速率。因此,指标计算当前价格的变化速率。我们还知道,较大的量级具有较大惯性,在无相当大的外力干涉下,可阻止速率的值大起大落。这就是我们逐渐接近趋势 - 在外力(新闻、中央银行的政策等)未影响市场的时段内当其一阶导数(速率)保持其值时的价格状态 - 概念的方式。

但是,让我们返回我们的起点 - 价格反映一切。要探寻新的理念,赫兹量化应研究同一时间间隔的价格的行为及其派生物。只有仔细研究价格图表,才能将您的交易从盲目跟风上升到真正理解的程度。

这可能不会为交易结果带来立竿见影的效果,但解答无数为什么问题的能力迟早将起到积极作用。此外,对图表和指标的视觉分析将使您找到一些价格和指标之间的全新的相关性 - 这是它们的开发者完全没有预料到的。

假设您发现一个似乎对您有利的新的相关性。下一步做什么呢?最简单的方法是编写一个“EA 交易”并用历史数据对其进行测试,确保您的设想是正确的。如果事实并非如此,赫兹量化需要选择一种常用的最优化参数的方法。最糟糕的就是,我们无法回答这个为什么问题。为什么我们的“EA 交易”最后亏损/盈利?为什么会有如此巨大的亏损?没有答案,您无法有效地实现您的想法。

可执行下述操作以看到从图表获得的相关性的结果:

  1. 创建或更改必要指标,以使其生成一个信号:-1 为卖出,1 为买入。

  2. 连接显示进入点和退出点的余额指标到图表。该指标还显示当处理信号时,余额和资产净值(点位)的变更。

  3. 分析在何种情形和状况下假设成立。

该方法具有一定的优势。

  • 首先,余额指标完全使用 OnCalculate 方法计算,该方法具有最大计算速度并自动在输入计算数组中提供历史数据。

  • 其次,添加信号至现有指标是通过向导创建“EA 交易”和创建自己的“EA 交易”的过渡步骤。

  • 再次,可以在单个图表上查看想法和最终结果。当然,该方法也有一些局限性:信号与柱的收盘价相关联、余额的计算针对固定手数、未提供使用挂单交易的选项。然而,所有这些局限性可轻松解决/改善。

实现

赫兹量化开发一个简单的信号指标以了解它是如何工作的,并评估该方法的便捷性。我很早就听说过烛形模式。所以,为什么不检查一下它们的实际工作情况?我选择“锤线”和“射击之星”反向模式分别作为买入和卖出信号。下图显示了它们的示意外观:

编辑切换为居中

添加图片注释,不超过 140 字(可选)

图 1. “锤线”和“射击之星”烛形模式

现在,我们来定义当“锤线”模式出现时的市场进入规则。

  1. 烛形的最小值应小于前五个烛形的最小值;

  2. 烛形的主体不应超过它的总高度的 50%;

  3. 烛形的上影线不应超过它的总高度的 0%;

  4. 烛形的高度不应小于前五个烛形的平均高度的 100%。

  5. 模式的收盘价应小于 10-周期移动平均线。

如果满足这些条件,我们应建立买入持仓。针对“射击之星”模式的规则是一样的。惟一的区别是我们应建立卖出持仓:

  1. 烛形的最大值应大于前五个烛形的最大值;

  2. 烛形的主体不应超过它的总高度的 50%;

  3. 烛形的下影线不应超过它的总高度的 0%;

  4. 烛形的高度不应小于前五个烛形的平均高度的 100%。

  5. 模式的收盘价应大于 10-周期移动平均线。

对于我在图形上使用的未来可进行最优化的参数,我用粗体进行了标示(如果模式显示可接受的结果)。我希望实施的限制允许我们从具有不恰当外观的信号 (pp.1-3) 以及无法视作信号的已知弱信号清除模式。

此外,我们应确定退出时机。由于提到的模式作为趋势反向信号出现,适当烛形出现时趋势存在。因此,追逐价格的移动平均线也将出现。退出信号通过穿越价格及其 10-周期移动平均线形成。

现在,是时候编写一些代码了。赫兹量化在 MQL5 向导中开发一个新的自定义指标,将其命名为 PivotCandles 并说明它的功用。让我们定义返回值以连接余额指标:

  • -1 – 卖出仓位开仓;

  • -2 – 买入仓位平仓;

  • 0 - 无信号;

  • 1 – 买入仓位开仓;

  • 2 – 卖出仓位平仓。

就像您所知道的那样,真正的程序员不会寻找简单的方法,他们寻找最简单的方法。:) 我也不例外。一边通过耳机聆听音乐,一边品尝香浓的咖啡,我完成了包含要在指标和“EA 交易”(我打算基于指标开发它的情形)中实施的类的文件。也许,甚至可以对它进行修改以用于其他烛形模式。代码本身乏新可陈。我相信代码所附的注释涵盖了任何可能的问题。

 
 

//+------------------------------------------------------------------+ //| PivotCandlesClass.mqh | //| Copyright 2012, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2012, MetaQuotes Software Corp." #property link "http://www.mql5.com" //+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ input int iMaxBodySize = 50; // Maximum candle body, % input int iMaxShadowSize = 0; // Maximum allowed candle shadow, % input int iVolatilityCandlesCount = 5; // Number of previous bars for calculation of an average volatility input int iPrevCandlesCount = 5; // Number of previous bars, for which the current bar should be an extremum input int iVolatilityPercent = 100; // Correlation of a signal candle with a previous volatility, % input int iMAPeriod = 10; // Period of a simple signal moving average //+------------------------------------------------------------------+ //| Class definition | //+------------------------------------------------------------------+ class CPivotCandlesClass { private: MqlRates m_candles[]; // Array for storing the history necessary for calculations int m_history_depth; // Array length for storing the history int m_handled_candles_count; // Number of the already processed candles double m_ma_value; // Current calculated moving average value double m_prev_ma_value; // Previous calculated moving average value bool m_is_highest; // Check if the current candle is the highest one bool m_is_lowest; // Check if the current candle is the lowest one double m_volatility; // Average volatility int m_candle_pattern; // Current recognized pattern void PrepareArrayForNewCandle(); // Prepare the array for accepting the new candle int CheckCandleSize(MqlRates &candle); // Check the candle for conformity with patterns void PrepareCalculation(); protected: int DoAnalizeNewCandle(); // Calculation function public: void CPivotCandlesClass(); void CleanupHistory(); // Clean up all calculation variables double MAValue() {return m_ma_value;} // Current value of the moving average int AnalizeNewCandle(MqlRates& candle); int AnalizeNewCandle( const datetime time, const double open, const double high, const double low, const double close, const long tick_volume, const long volume, const int spread ); }; //+------------------------------------------------------------------+ //| CPivotCandlesClass | //+------------------------------------------------------------------+ //| Class initialization | //+------------------------------------------------------------------+ void CPivotCandlesClass::CPivotCandlesClass() { // History depth should be enough for all calculations m_history_depth = (int)MathMax(MathMax( iVolatilityCandlesCount + 1, iPrevCandlesCount + 1), iMAPeriod); m_handled_candles_count = 0; m_prev_ma_value = 0; m_ma_value = 0; ArrayResize(m_candles, m_history_depth); } //+------------------------------------------------------------------+ //| CleanupHistory | //+------------------------------------------------------------------+ //| Clean up the candle buffer for recalculation | //+------------------------------------------------------------------+ void CPivotCandlesClass::CleanupHistory() { // Clean up the array ArrayFree(m_candles); ArrayResize(m_candles, m_history_depth); // Null calculation variables m_handled_candles_count = 0; m_prev_ma_value = 0; m_ma_value = 0; } //+-------------------------------------------------------------------+ //| AnalizeNewCandle | //+-------------------------------------------------------------------+ //| Preparations for analyzing the new candle and the analysis itself | //| based on candle's separate parameter values | //+-------------------------------------------------------------------+ int CPivotCandlesClass::AnalizeNewCandle( const datetime time, const double open, const double high, const double low, const double close, const long tick_volume, const long volume, const int spread ) { // Prepare the array for the new candle PrepareArrayForNewCandle(); // Fill out the current value of the candle m_candles[0].time = time; m_candles[0].open = open; m_candles[0].high = high; m_candles[0].low = low; m_candles[0].close = close; m_candles[0].tick_volume = tick_volume; m_candles[0].real_volume = volume; m_candles[0].spread = spread; // Check if there is enough data for calculation if (m_handled_candles_count < m_history_depth) return 0; else return DoAnalizeNewCandle(); } //+-------------------------------------------------------------------+ //| AnalizeNewCandle | //+-------------------------------------------------------------------+ //| Preparations for analyzing the new candle and the analysis itself | //| based on the received candle | //+-------------------------------------------------------------------+ int CPivotCandlesClass::AnalizeNewCandle(MqlRates& candle) { // Prepare the array for the new candle PrepareArrayForNewCandle(); // Add the candle m_candles[0] = candle; // Check if there is enough data for calculation if (m_handled_candles_count < m_history_depth) return 0; else return DoAnalizeNewCandle(); } //+------------------------------------------------------------------+ //| PrepareArrayForNewCandle | //+------------------------------------------------------------------+ //| Prepare the array for the new candle | //+------------------------------------------------------------------+ void CPivotCandlesClass::PrepareArrayForNewCandle() { // Shift the array by one position to write the new value there ArrayCopy(m_candles, m_candles, 1, 0, m_history_depth-1); // Increase the counter of added candles m_handled_candles_count++; } //+------------------------------------------------------------------+ //| CalcMAValue | //+------------------------------------------------------------------+ //| Calculate the current values of the Moving Average, volatility | //| and the value extremality | //+------------------------------------------------------------------+ void CPivotCandlesClass::PrepareCalculation() { // Store the previous value m_prev_ma_value = m_ma_value; m_ma_value = 0; m_is_highest = true; // check if the current candle is the highest one m_is_lowest = true; // check if the current candle is the lowest one m_volatility = 0; // average volatility double price_sum = 0; // Variable for storing the sum for (int i=0; i<m_history_depth; i++) { if (i<iMAPeriod) price_sum += m_candles[i].close; if (i>0 && i<=iVolatilityCandlesCount) m_volatility += m_candles[i].high - m_candles[i].low; if (i>0 && i<=iPrevCandlesCount) { m_is_highest = m_is_highest && (m_candles[0].high > m_candles[i].high); m_is_lowest = m_is_lowest && (m_candles[0].low < m_candles[i].low); } } m_ma_value = price_sum / iMAPeriod; m_volatility /= iVolatilityCandlesCount; m_candle_pattern = CheckCandleSize(m_candles[0]); } //+------------------------------------------------------------------+ //| CheckCandleSize | //+------------------------------------------------------------------+ //| Check if the candle sizes comply with the patterns | //| The function returns: | //| 0 - if the candle does not comply with the patterns | //| 1 - if "hammer" pattern is detected | //| -1 - if "shooting star" pattern is detected | //+------------------------------------------------------------------+ int CPivotCandlesClass::CheckCandleSize(MqlRates &candle) { double candle_height=candle.high-candle.low; // candle's full height double candle_body=MathAbs(candle.close-candle.open); // candle's body height // Check if the candle has a small body if(candle_body/candle_height*100.0>iMaxBodySize) return 0; double candle_top_shadow=candle.high-MathMax(candle.open,candle.close); // candle upper shadow height double candle_bottom_shadow=MathMin(candle.open,candle.close)-candle.low; // candle bottom shadow height // If the upper shadow is very small, that indicates the "hammer" pattern if(candle_top_shadow/candle_height*100.0<=iMaxShadowSize) return 1; // If the bottom shadow is very small, that indicates the "shooting star" pattern else if(candle_bottom_shadow/candle_height*100.0<=iMaxShadowSize) return -1; else return 0; } //+------------------------------------------------------------------+ //| DoAnalizeNewCandle | //+------------------------------------------------------------------+ //| Real analysis of compliance with the patterns | //+------------------------------------------------------------------+ int CPivotCandlesClass::DoAnalizeNewCandle() { // Prepare data for analyzing the current situation PrepareCalculation(); // Process prepared data and set the exit signal int signal = 0; /// // EXIT SIGNALS // /// // If price crosses the moving average downwards, short position is closed if(m_candles[1].close > m_prev_ma_value && m_candles[0].close < m_ma_value) signal = 2; // If price crosses the moving average upwards, long position is closed else if (m_candles[1].close < m_prev_ma_value && m_candles[0].close > m_ma_value) signal = -2; /// // ENTRY SIGNALS // /// // Check if the minimum volatility condition is met if (m_candles[0].high - m_candles[0].low >= iVolatilityPercent / 100.0 * m_volatility) { // Checks for "shooting star" pattern if (m_candle_pattern < 0 && m_is_highest && m_candles[0].close > m_ma_value) signal = -1; // Checks for "hammer" pattern else if (m_candle_pattern > 0 && m_is_lowest && m_candles[0].close < m_ma_value) signal = 1; } return signal; } //+------------------------------------------------------------------+

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值