
如何将聪明资金概念(SMC)与 RSI 指标结合到 EA 中
概述
在快节奏的外汇交易世界中,拥有一个可靠高效的交易系统对于成功至关重要。一般来说,交易有很多术语、概念和策略。有时,这往往会让人喘不过气来,尤其是对于那些仍在努力在交易行业站稳脚跟的新交易员来说。 聪明资金概念(SMC,Smart Money Concept)是外汇交易中的热门概念之一,但对于新交易者或任何人来说,在交易中使用聪明资金概念有时都很困难。
为了解决这个问题,应该有一个强大的工具,根据市场结构和价格走势自动做出交易决策。解决方案是将聪明资金概念(结构突破)与流行的相对强弱指数(RSI)指标相结合。这种组合通过利用价格行为和动量分析提供了战略优势,提高了交易进场和退场的准确性,旨在优化交易表现。
EA 交易示例的思路
这个 EA 交易示例的思路和功能是,EA 将检测波段低点和波段高点,因为一些聪明资金概念会利用波段。RSI指标仍将使用传统的方式,即 70 水平用于超买市场,30 水平用于超卖市场,周期数为 8。当市场价格高于之前检测到的高点时,这将表明结构向上突破。同样,当市场价格低于之前检测到的低点时,这将表明下行结构的突破。
现在,让我们开发 EA 交易示例
该 EA 的目标是根据市场条件和 RSI 水平开启买入和卖出订单。具体来说,它:
- 识别市场中的波段高点和波段低点。
- 检查市场价格是否高于前一个波段高点(卖出信号)或低于前一个波段低点(买入信号)。
- 然后用 RSI 水平确认信号。
因此,基本上 EA 交易会搜索结构的突破或之前波动(高/低)的突破,然后如果 RSI 值在指定的设置范围内,则会执行市场订单(买入/卖出)。
代码分解
1.属性和包含文件
#property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade/Trade.mqh>
这些代码行定义了 EA 的属性,并且包含执行交易所需的交易库。
2.全局变量和输入参数
long MagicNumber = 76543; double lotsize = 0.01; input int RSIperiod = 8; input int RSIlevels = 70; int stopLoss = 200; int takeProfit = 500; bool closeSignal = false;
- MagicNumber:EA 交易的唯一标识符。
- Lotsize:每笔交易的手数大小。
- RSIperiod:用于计算 RSI 的周期数。
- RSIlevels:RSI 临界值水平。
- stopLoss 和 takeProfit:以点数为单位的止损和止盈水平。
- CloseSignal:根据与当前打开仓位相反的信号标记关闭仓位。
3.RSI 变量
int handle; double buffer[]; MqlTick currentTick; CTrade trade; datetime openTimeBuy = 0; datetime openTimeSell = 0;
- Handle:RSI 指标的句柄。
- Buffer:用于存储 RSI 值的数组。
- currentTick:存储当前市场价格的结构。
- Trade:这是交易操作的对象。
- openTimeBuy 和 openTimeSell:最后买卖信号和操作的时间戳。
4.初始化函数
int OnInit() { if (RSIperiod <= 1) { Alert("RSI period <= 1"); return INIT_PARAMETERS_INCORRECT; } if (RSIlevels >= 100 || RSIlevels <= 50) { Alert("RSI level >= 100 or <= 50"); return INIT_PARAMETERS_INCORRECT; } trade.SetExpertMagicNumber(MagicNumber); handle = iRSI(_Symbol, PERIOD_CURRENT, RSIperiod, PRICE_CLOSE); if (handle == INVALID_HANDLE) { Alert("Failed to create indicator handle"); return INIT_FAILED; } ArraySetAsSeries(buffer, true); return INIT_SUCCEEDED; }
- 验证 RSI 周期和水平。
- 设置交易识别的幻数。
- 创建 RSI 指标句柄。
- 将缓冲区设置为按时间倒序存储 RSI 值的序列。
5.去初始化函数
void OnDeinit(const int reason) { if (handle != INVALID_HANDLE) { IndicatorRelease(handle); } }
删除 EA 时释放 RSI 指标句柄。
6.OnTick 函数
众所周知,OnTick 函数包含检测信号和执行交易的核心逻辑。
void OnTick() { static bool isNewBar = false; int newbar = iBars(_Symbol, _Period); static int prevBars = newbar; if (prevBars == newbar) { isNewBar = false; } else if (prevBars != newbar) { isNewBar = true; prevBars = newbar; }
首先,由于 OnTick 函数在每个分时报价上运行,我们需要确保在检测信号或执行交易时,每个柱形都执行一次。为此,我们声明了一个静态布尔变量 isNewBar。我们首先将其设置为 false,然后声明一个 int 型变量 newBar,并将其赋值给函数 iBars,这样我们就可以跟踪每个蜡烛柱。
- static bool isNewBar:跟踪是否形成了新的柱形(烛形)。
- int newbar = iBars(_Symbol, _Period):获取图表上的当前柱形数。
- static int prevBars = newbar:初始化前一个柱形计数。
- if-else 块检查柱形计数是否发生变化,表明出现了新的柱形。如果形成了新的柱形,"isNewBar" 就会设置为 "true",否则就是 "false"。
const int length = 10; int right_index, left_index; int curr_bar = length; bool isSwingHigh = true, isSwingLow = true; static double swing_H = -1.0, swing_L = -1.0;
然后,我们需要设置变量来检测波段高点和波段低点:
- const int length = 10:定义检测波段高点和波段低点的范围。
- int right_index, left_index:当前柱形右侧和左侧柱形的索引。
- int curr_bar = length:设置当前柱形索引。
- bool isSwingHigh = true, isSwingLow = true:这些标志可用于确定柱形是波段高点还是波段低点。
- static double swing_H = -1.0, swing_L = -1.0:存储最新检测到的波段最高值和波段最低值。
double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);
变量类型为双精度型变量,变量 "Ask" 用于获取当前市场的卖出价格,变量 "Bid" 用于获取当前市场的买入价格:
- double Ask:获取当前卖出价格
- double Bid:获取当前买入价格
- NormalizeDouble:将价格四舍五入到正确的小数位数。
if (isNewBar) { for (int a = 1; a <= length; a++) { right_index = curr_bar - a; left_index = curr_bar + a; if ((high(curr_bar) <= high(right_index)) || (high(curr_bar) < high(left_index))) { isSwingHigh = false; } if ((low(curr_bar) >= low(right_index)) || (low(curr_bar) > low(left_index))) { isSwingLow = false; } } if (isSwingHigh) { swing_H = high(curr_bar); Print("We do have a swing high at: ", curr_bar, " H: ", high(curr_bar)); drawswing(TimeToString(time(curr_bar)), time(curr_bar), high(curr_bar), 32, clrBlue, -1); } if (isSwingLow) { swing_L = low(curr_bar); Print("We do have a swing low at: ", curr_bar, " L: ", low(curr_bar)); drawswing(TimeToString(time(curr_bar)), time(curr_bar), low(curr_bar), 32, clrRed, +1); } }
然后,我们继续搜索波段高点和波段低点,但在此之前,如果检测到新的柱形,函数会在定义的 'length' 长度范围内检查周围的柱形,以确定是否存在波段高点或波段低点。变量 length 用于定义搜索波动的蜡烛图范围,对于范围内的每个柱形,函数都会将当前柱形的高点/低点与相邻柱形进行比较。
如果当前柱形的高点不高于周围的柱形,那么它就不是波段高点。如果当前柱形的低点不低于周围的柱形,那么它就不是波段低点。如果满足了所有这些条件,那么就可以确认波段高点或波段低点,然后存储相应的值,然后使用 'drawswing' 函数(绘制波段高点或波段低点)在图表上绘制标记。
int values = CopyBuffer(handle, 0, 0, 2, buffer); if (values != 2) { Print("Failed to get indicator values"); return; } Comment("Buffer[0]: ", buffer[0], "\nBuffer[1]: ", buffer[1]);
- copyBuffer(handle, 0, 0, 2, buffer):将最新的 RSI 值复制到缓冲区。
- 如果函数无法获取 RSI 值,则退出。
- RSI 值将作为注释显示在图表上。
int cntBuy = 0, cntSell = 0; if (!countOpenPositions(cntBuy, cntSell)) { return; }
'countOpenPositions(cntBuy, cntSell) 这个函数计算未平仓的买入和卖出仓位的数量,如果函数失败, 'OnTick' 退出。
if (swing_H > 0 && Ask > swing_H && buffer[0] >= 70) { Print("Sell Signal: Market is above previous high and RSI >= 70"); int swing_H_index = 0; for (int i = 0; i <= length * 2 + 1000; i++) { if (high(i) == swing_H) { swing_H_index = i; break; } } drawBreakLevels(TimeToString(time(0)), time(swing_H_index), high(swing_H_index), time(0), high(swing_H_index), clrBlue, -1); if (cntSell == 0) { double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); double sl = Bid + stopLoss * _Point; double tp = Bid - takeProfit * _Point; trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, lotsize, currentTick.bid, sl, tp, "RSI EA"); } swing_H = -1.0; return; }
正如我之前所解释的,我们将检查卖出信号。其逻辑是,卖出价必须高于前一个波动高点。此外,RSI 值必须大于或等于 70 水平。如果条件得到满足,它就会在图表上标出波段高点并画出突破水平线。如果没有未平仓的卖出仓位,它就会开启一个卖出仓位,这个卖出交易将以计算出的止损和止盈水平开启,然后会重置波段高点值。
if (swing_L > 0 && Bid < swing_L && buffer[0] <= 30) { Print("Buy Signal: Market is below previous low and RSI <= 30"); int swing_L_index = 0; for (int i = 0; i <= length * 2 + 1000; i++) { if (low(i) == swing_L) { swing_L_index = i; break; } } drawBreakLevels(TimeToString(time(0)), time(swing_L_index), low(swing_L_index), time(0), low(swing_L_index), clrRed, +1); if (cntBuy == 0) { double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); double sl = Ask - stopLoss * _Point; double tp = Ask + takeProfit * _Point; trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, lotsize, currentTick.ask, sl, tp, "RSI EA"); } swing_L = -1.0; return; } }
对于买入信号,使用相反的逻辑,如果买入价低于前一波段低点,且 RSI 值小于或等于 30 水平,那么技术上买入逻辑成立,条件满足。当满足条件时,它会标记出波动的低点,然后在图表上画出一个突破水平线。 如果没有未平仓的买入仓位,它就会根据计算出的止损和止盈水平开启新的买入交易,最后会重置波动低点值。
我们想要完成的目标如下:
_ 符合所有条件的卖出交易:
_ 满足所有条件的买入交易:
OnTick 函数的总结:
EA 交易会在每个市场分时报价上执行以下关键操作
- 检测新柱形:它首先会检查上一个分时报价之后是否有新的蜡烛柱形成,然后会跟踪所有蜡烛柱。
- 识别波动高点和波动低点:然后,当市场价格高于波段高点或低于波段低点时,它就会确定市场的波段点,作为以后的参考水平。
- 获取 RSI 值:获取最新的 RSI 值,用于信号确认。
- 计数开启的仓位:跟踪当前未平仓的买入和卖出仓位。
- 生成交易信号:使用波段点,即波段高点、波段低点和 RSI 水平来生成买入或卖出信号。
- 执行交易:如果条件全部满足,则根据生成的信号开启新仓位。
最高价、最低价和时间值的自定义函数
double high(int index){ return (iHigh(_Symbol, _Period, index)); } double low(int index){ return (iLow(_Symbol, _Period, index)); } datetime time(int index){ return (iTime(_Symbol, _Period, index)); }
函数 high(int index) 返回在指定索引处柱形的最高价。它将 index 作为 "int" 类型的参数。内置的 MQL5 函数 iHigh(_symbol,_Period,index) 用于获取当前交易品种和周期在索引处的最高价格。接着是 low(int index),该函数返回指定索引处的最低价,它还将 index 作为参数,其类型为 int,然后是 iLow(_symbol,_Period,index)。它也是 MQL5 的内置函数,用于获取当前交易品种在 index 柱的低价。最后是 time (int index) 函数,它返回指定索引柱形的时间,iTime(_symbol, _Period, index) 也是 MQL5 的内置函数,用于获取当前交易品种和周期在 index 处的时间,iTime 返回的数据类型为 datetime 类型。
绘制波段点的函数
void drawswing(string objName, datetime time, double price, int arrCode, color clr, int direction){ if(ObjectFind(0, objName) < 0){ ObjectCreate(0, objName, OBJ_ARROW, 0, time, price); ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode); ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 10); if(direction > 0){ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);} if(direction < 0){ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);} string text = ""; string Descr = objName + text; ObjectCreate(0, Descr, OBJ_TEXT, 0, time, price); ObjectSetInteger(0, Descr, OBJPROP_COLOR, clr); ObjectSetInteger(0, Descr, OBJPROP_FONTSIZE, 10); if(direction > 0){ ObjectSetString(0, Descr, OBJPROP_TEXT," "+text); ObjectSetInteger(0, Descr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); } if(direction < 0){ ObjectSetString(0, Descr, OBJPROP_TEXT," "+text); ObjectSetInteger(0, Descr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); } } ChartRedraw(0); }
该函数为图表上的波动点(高点和低点)创建可视化标记。它需要以下参数:
- ObjName:是要创建对象的名称。
- Time:发生或检测到波段的时间。
- Price:形成波段的价格。
- ArrCode:用于可视化表示的箭头代码。
- Clr:箭头的颜色。
- Direction:波动方向(高点为正,低点为负)。
功能
1.对象创建:
- ObjectFind(0, objName) < 0:检查给定名称的对象是否已经存在。
- ObjectCreate(0, objName, OBJ-ARROW, 0, time, price):按指定的时间和价格创建箭头对象。
- ObjectSetInteger(0, objName, OBJPROP-ARROWCODE, arrCode):设置箭头代码。
- ObjectSetInteger(0, objName, OBJPROP-COLOR, clr):设置箭头颜色。
- ObjectSetInteger(0, objName, OBJPROP-FONTSIZE, 10):设置字体大小。
2.方向处理:
- 根据方向设置锚点位置。
- OBJPROP-ANCHOR:设置箭头锚点的位置。
3.创建文本对象:
- 创建与箭头相关联的文本对象,以显示附加信息。
- 根据方向为文本设置颜色、字体大小和锚点。
4.图表更新:
- ChartRedraw(0):重绘图表以反映变化
绘制断点水平线的函数
void drawBreakLevels(string objName, datetime time1, double price1, datetime time2, double price2, color clr, int direction){ if(ObjectFind(0, objName) < 0){ ObjectCreate(0, objName, OBJ_ARROWED_LINE, 0, time1, price1, time2, price2); ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1); ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1); ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2); ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2); ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); string text = "Break"; string Descr = objName + text; ObjectCreate(0, Descr, OBJ_TEXT, 0, time2, price2); ObjectSetInteger(0, Descr, OBJPROP_COLOR, clr); ObjectSetInteger(0, Descr, OBJPROP_FONTSIZE, 10); if(direction > 0){ ObjectSetString(0, Descr, OBJPROP_TEXT,text+" "); ObjectSetInteger(0, Descr, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER); } if(direction < 0){ ObjectSetString(0, Descr, OBJPROP_TEXT,text+" "); ObjectSetInteger(0, Descr, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER); } } ChartRedraw(0); }
该函数用于创建价格水平突破的可视化表示,这些价格水平是之前在图表上检测到的波动点,它具有以下参数:
- objName:要创建对象的名称。
- time1, time2:开始时间是指波段形成的时间,结束时间是指发生突破的时间。
- price1, price2:起始价是出现波段高点或波段低点的价格,price2 是突破波段高点或波段低点的价格。
- Clr:箭头的颜色。
- Direction:锚定文本的方向。
功能
1.对象创建:
- ObjectFind(0, objName) < 0:检查给定名称的对象是否已经存在。
- ObjectCreate(0, objName, OBJ_ARROWED_LINE, 0, time1, price1, time2, price2):在指定的时间和价格处创建箭头对象。
- 设置箭头线起点和终点的时间和价格。
- ObjectSetInteger(0, objName, OBJPROP-COLOR, clr):设置线的颜色。
- ObjectSetInteger(0, objName, OBJPROP-WIDTH, 2):设置线宽。
2.创建文本对象:
- 创建与该线相关联的文本对象,以显示附加信息。
- 根据方向设置文本的颜色、字体大小和锚点。
3.图表更新:
- chartRedraw(0):重绘图表以反映变化。
结论
总之,自定义函数提供了便捷的访问方式,简化了对柱形的最高值、最低值和时间值的访问,从而提升了交易体验。通过将 RSI 指标与 SMC 概念相结合,我们可以将指标可视化,并确认 EA 是否遵循了指令。该函数在图表上绘制箭头线时,会标记重要的点,也就是波段点(高点和低点)。此外,我们还可以观察到突破水平。我们还有动态更新功能,可确保图表实时更新最新的标记和指标,帮助进行可视化分析和决策。
通过本综合指南,我们可以全面了解如何将 SMC 概念与 RSI 指标整合到任何 EA 交易系统的结构和功能中。通过循序渐进的讲解,读者现在应该可以清楚地掌握 SMC_RSI EA 交易系统的操作,从初始化变量到根据计算信号执行交易。无论您是经验丰富的交易者还是初学者,这篇全面的指南都能让您掌握必要的知识,在交易中有效地利用和定制这一强大的工具。
下面是回溯测试结果,可以说 EA 本身还需要某种优化才能获得更高的利润率,下面是回溯测试结果:
下面我们可以看到净值曲线的可视化表示,我只测试了12个月,所以不能准确地说,如果测试可能持续12年,它的表现会如何。
参考
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15030


