在网上看到有不少朋友常上传指标希望能直接在指标内多加警告提示功能, 下面的流程让你在有限的编程能力也能自己快速完成.
下面例子以 MT4 内建的 Stochastic 指标 (也就是我们熟知的 KD 指标) 修改为例.
1. 先把指标放在图表上观察
2. 把原始指标代码先另存为同名再加 _Alert, 这个例子也就是 Stochastic_Alert.mq4, 这个就成为以后有带提示版本的新指标.
3. 在原来代码里的 input 那几行下多加以下 input 参数
input bool AlertPopup = true;
input bool Sound = true;
input bool Email = false;
input bool Mobile = false;
第一个 AlertPopup 是提示窗口, 默认 true 是设开启, 第二是有提示声音, 默认 true 是设开启, 第三个电邮通知, 默认 false 是设"不"开启, 如需开启功能在指标参数设定改为 true 即可, 第四个 Mobile 是电脑 MT4 与手机间通知, 默认 false 是设"不"开启.
4. 在 int OnInit(void) 前多个参数:
datetime dtLastTime = 0;
5. 观察指标图表上那两条线的颜色, 一条是淡绿色, 一条是红色, 在 int OnInit(void) 函数代码里找到有 DRAW_LINE 的部分, 这是画线的初始化的地方:
SetIndexStyle(0,DRAW_LINE);
SetIndexBuffer(0, ExtMainBuffer);
SetIndexStyle(1,DRAW_LINE);
SetIndexBuffer(1, ExtSignalBuffer);
可以看到有两个 ExtMainBuffer 和 ExtSignalBuffer 都是指向画线的参数, 一个指标编号是 0, 另一个是指标编号是 1, 再对照代码最开始的指标颜色设定
#property indicator_color1 LightSeaGreen
#property indicator_color2 Red
这个是 MQL4 语言设计不好的地方, 在 #property 里的指标编号设定是从 1 开始, 但其他程序里是从 0 开始, 不管如何, 对应关系还是照依序的顺序
所以 ExtMainBuffer 是指淡绿色线, ExtSignalBuffer 是指红色线
6. 每个使用者都有自己偏好的提示条件, 这里以最简单的以两条线金叉与死叉来作提示为范例
在图上的金叉往上, 也就是淡绿线高于红线, 条件的逻辑就是:
淡绿线在这个 k 棒大于红线值, 但在前一根 k 棒小于红色线
死叉就是相反, 淡绿线在这个 k 棒小于红线值, 但在前一根 k 棒大于红色线
7. 在原有指标代码里的 OnCalculate(...) 函数 (MT4 build 600 版本后) 或 start() 里的下方位置多加以下的代码, 把判断逻辑写在 if 内红色里, 0 是代表最新 k 棒, 1 是前一根, 需要最即时的提醒就用 0 和 1, 不过因为最新 k 棒的价格还在变动, 可能现在金叉后价格又变动但提示已经发出, 优点是达到即时提示的目的, 还是由人工来判断是否交易. 偏好稳定型通知的朋友可以把 0 和 1 的地方各改为 1 和 2, 也就是比对前一根 k 棒和前前 1 根 k 棒, 因为前一根已经收完, 指标值就稳定了.
把提示信息的标题语写在下面蓝色字的部分, 这个范例除了标题语外还会顺便显示当前商品周期和价格, 这样方便当有几个图表会发出信息时易于辨认, 提示的信息应该是像 KD 金叉: EURUSD_M15 1.32426
因为电邮通知可以多带点信息在文本, 就多放了通知时当前价格和交易商服务器时间, 想再多放些信息就在那里多写.
下面有多了个 dtLastTime!=Time[0] 的判断, 目的是防止这根 k 棒在条件符合后又被持续变化的价格触发而发出通知, 一旦条件第一次符合后就立刻让 dtLastTime = Time[0]; 也就是等于这根最新 k 棒的开盘时间, 除非下根 k 棒出现让 Time[0] 改变, 不然就挡住在这根 k 棒走完前不要再发出通知, 至于下根 k 棒出现后, 条件也改变了, 自然也就不会再马上发出通知.
还有一个小细节, 这个 dtLastTime = Time[0]; 要立刻写在条件符合后马上执行, 因为这里的通知有几个是 "非同步" 处理如发警告声和电邮和MT4间通知 (只有提示窗口是即时反应的), 通知处理反应比较慢, 可是价格变动又不断触发条件成立又要作新的通知, 如果不是把这个等于放在条件符合后马上执行, 让 if (dtLastTime!=Time[0]... 来挡住速度太快的价格跳动触发条件符合, 很容易收到重复的通知, 这些都是通知型设计的小经验谈, 目的是在调和 MT4 不同程序模块的异步处理.
/// UP
if (dtLastTime!=Time[0] && ExtMainBuffer[0]>ExtSignalBuffer[0] && ExtMainBuffer[1])
{
dtLastTime = Time[0];
strAlertMessage = StringConcatenate("KD 金叉: ",Symbol(),"_",PeriodToString(Period())," ",DoubleToStr(Close[0],Digits));
if (AlertPopup)
Alert(strAlertMessage);
if (Sound)
PlaySound("alert.wav");
if (Email)
SendMail(strAlertMessage,
"最新价格 "+DoubleToStr(Close[0],Digits)+
"\n服务器时间: " + TimeToStr(TimeCurrent(),TIME_DATE|TIME_SECONDS));
if (Mobile)
SendNotification(strAlertMessage);
}
/// DOWN
if (dtLastTime!=Time[0] && ExtMainBuffer[0] ExtMainBuffer[1]>ExtSignalBuffer[1])
{
dtLastTime = Time[0];
strAlertMessage = StringConcatenate("KD 死叉: ",Symbol(),"_",PeriodToString(Period())," ",DoubleToStr(Close[0],Digits));
if (AlertPopup)
Alert(strAlertMessage);
if (Sound)
PlaySound("alert.wav");
if (Email)
SendMail(strAlertMessage,
"最新价格 "+DoubleToStr(Close[0],Digits)+
"\n服务器时间: " + TimeToStr(TimeCurrent(),TIME_DATE|TIME_SECONDS));
if (Mobile)
SendNotification(strAlertMessage);
}
8. 在原来指标代码最下方多加个函数, 这只是个在提示信息里多提示图表当前周期的简单换算函数:
string PeriodToString (int imin)
{
string strprd;
switch (imin)
{
case (1):
strprd="M1";
break;
case (2):
strprd="M2";
break;
case (3):
strprd="M3";
break;
case (5):
strprd="M5";
break;
case (15):
strprd="M15";
break;
case (30):
strprd="M30";
break;
case (60):
strprd="H1";
break;
case (60*4):
strprd="H4";
break;
case (60*24):
strprd="D1";
break;
case (60*24*7):
strprd="W1";
break;
}
return (strprd);
}
9. 最后把修改完的指标代码完整附于下, 红色的部分是添加上去的提示功能, 修改加入部分在原有代码里也相当独立, 也不会干扰到原有功能, 这算是一个最快达成让现有指标具有提示的方法了.
//+------------------------------------------------------------------+
//| Stochastic.mq4 |
//| Copyright 2005-2014, MetaQuotes Software Corp. |
//| http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "2005-2014, MetaQuotes Software Corp."
#property link "http://www.mql4.com"
#property description "Stochastic Oscillator"
#property strict
#property indicator_separate_window
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_buffers 2
#property indicator_color1 LightSeaGreen
#property indicator_color2 Red
#property indicator_level1 20.0
#property indicator_level2 80.0
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT
//--- input parameters
input int InpKPeriod=5; // K Period
input int InpDPeriod=3; // D Period
input int InpSlowing=3; // Slowing
input bool AlertPopup = true;
input bool Sound = true;
input bool Email = false;
input bool Mobile = false;
//--- buffers
double ExtMainBuffer[];
double ExtSignalBuffer[];
double ExtHighesBuffer[];
double ExtLowesBuffer[];
//---
int draw_begin1=0;
int draw_begin2=0;
datetime dtLastTime = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit(void)
{
string short_name;
//--- 2 additional buffers are used for counting.
IndicatorBuffers(4);
SetIndexBuffer(2, ExtHighesBuffer);
SetIndexBuffer(3, ExtLowesBuffer);
//--- indicator lines
SetIndexStyle(0,DRAW_LINE);
SetIndexBuffer(0, ExtMainBuffer);
SetIndexStyle(1,DRAW_LINE);
SetIndexBuffer(1, ExtSignalBuffer);
//--- name for DataWindow and indicator subwindow label
short_name="Sto("+IntegerToString(InpKPeriod)+","+IntegerToString(InpDPeriod)+","+IntegerToString(InpSlowing)+")";
IndicatorShortName(short_name);
SetIndexLabel(0,short_name);
SetIndexLabel(1,"Signal");
//---
draw_begin1=InpKPeriod+InpSlowing;
draw_begin2=draw_begin1+InpDPeriod;
SetIndexDrawBegin(0,draw_begin1);
SetIndexDrawBegin(1,draw_begin2);
//--- initialization done
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Stochastic oscillator |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
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[])
{
int i,k,pos;
//--- check for bars count
if(rates_total<=InpKPeriod+InpDPeriod+InpSlowing)
return(0);
//--- counting from 0 to rates_total
ArraySetAsSeries(ExtMainBuffer,false);
ArraySetAsSeries(ExtSignalBuffer,false);
ArraySetAsSeries(ExtHighesBuffer,false);
ArraySetAsSeries(ExtLowesBuffer,false);
ArraySetAsSeries(low,false);
ArraySetAsSeries(high,false);
ArraySetAsSeries(close,false);
//---
pos=InpKPeriod-1;
if(pos+1
pos=prev_calculated-2;
else
{
for(i=0; i
{
ExtLowesBuffer=0.0;
ExtHighesBuffer=0.0;
}
}
//--- calculate HighesBuffer[] and ExtHighesBuffer[]
for(i=pos; i
{
double dmin=1000000.0;
double dmax=-1000000.0;
for(k=i-InpKPeriod+1; k<=i; k++)
{
if(dmin>low[k])
dmin=low[k];
if(dmax
dmax=high[k];
}
ExtLowesBuffer=dmin;
ExtHighesBuffer=dmax;
}
//--- %K line
pos=InpKPeriod-1+InpSlowing-1;
if(pos+1
pos=prev_calculated-2;
else
{
for(i=0; i
ExtMainBuffer=0.0;
}
//--- main cycle
for(i=pos; i
{
double sumlow=0.0;
double sumhigh=0.0;
for(k=(i-InpSlowing+1); k<=i; k++)
{
sumlow +=(close[k]-ExtLowesBuffer[k]);
sumhigh+=(ExtHighesBuffer[k]-ExtLowesBuffer[k]);
}
if(sumhigh==0.0)
ExtMainBuffer=100.0;
else
ExtMainBuffer=sumlow/sumhigh*100.0;
}
//--- signal
pos=InpDPeriod-1;
if(pos+1
pos=prev_calculated-2;
else
{
for(i=0; i
ExtSignalBuffer=0.0;
}
for(i=pos; i
{
double sum=0.0;
for(k=0; k
sum+=ExtMainBuffer[i-k];
ExtSignalBuffer=sum/InpDPeriod;
}
string strAlertMessage;
/// UP
if (dtLastTime!=Time[0] && ExtMainBuffer[0]>ExtSignalBuffer[0] && ExtMainBuffer[1]
{
dtLastTime = Time[0];
strAlertMessage = StringConcatenate("KD 金叉: ",Symbol(),"_",PeriodToString(Period())," ",DoubleToStr(Close[0],Digits));
if (AlertPopup)
Alert(strAlertMessage);
if (Sound)
PlaySound("alert.wav");
if (Email)
SendMail(strAlertMessage,
"最新价格 "+DoubleToStr(Close[0],Digits)+
"\n服务器时间: " + TimeToStr(TimeCurrent(),TIME_DATE|TIME_SECONDS));
if (Mobile)
SendNotification(strAlertMessage);
}
/// DOWN
if (dtLastTime!=Time[0] && ExtMainBuffer[0]ExtSignalBuffer[1])
{
dtLastTime = Time[0];
strAlertMessage = StringConcatenate("KD 死叉: ",Symbol(),"_",PeriodToString(Period())," ",DoubleToStr(Close[0],Digits));
if (AlertPopup)
Alert(strAlertMessage);
if (Sound)
PlaySound("alert.wav");
if (Email)
SendMail(strAlertMessage,
"最新价格 "+DoubleToStr(Close[0],Digits)+
"\n服务器时间: " + TimeToStr(TimeCurrent(),TIME_DATE|TIME_SECONDS));
if (Mobile)
SendNotification(strAlertMessage);
}
//--- OnCalculate done. Return new prev_calculated.
return(rates_total);
}
//+------------------------------------------------------------------+
string PeriodToString (int imin)
{
string strprd;
switch (imin)
{
case (1):
strprd="M1";
break;
case (2):
strprd="M2";
break;
case (3):
strprd="M3";
break;
case (5):
strprd="M5";
break;
case (15):
strprd="M15";
break;
case (30):
strprd="M30";
break;
case (60):
strprd="H1";
break;
case (60*4):
strprd="H4";
break;
case (60*24):
strprd="D1";
break;
case (60*24*7):
strprd="W1";
break;
}
return (strprd);
}
|