forked from rosh/MQL5Book
269 lines
11 KiB
MQL5
269 lines
11 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| AutoIndicator.mqh |
| |||
//| Copyright © 2017-2020, Marketeer |
| |||
//| https://www.mql5.com/en/users/marketeer |
| |||
//+------------------------------------------------------------------+
| |||
#include "RTTI.mqh"
| |||
#include "MqlParamBuilder.mqh"
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Defines for making indicators' IDs with metadata |
| |||
//+------------------------------------------------------------------+
| |||
#define MAKE_IND(P,B,W,ID) (int)((W << 24) | ((B & 0xFF) << 16) | ((P & 0xFF) << 8) | (ID & 0xFF))
| |||
#define IND_PARAMS(X) ((X >> 8) & 0xFF)
| |||
#define IND_BUFFERS(X) ((X >> 16) & 0xFF)
| |||
#define IND_WINDOW(X) ((uchar)(X >> 24))
| |||
#define IND_ID(X) ((ENUM_INDICATOR)(X & 0xFF))
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| All indicators (types and metadata) |
| |||
//+------------------------------------------------------------------+
| |||
enum IndicatorType
| |||
{
| |||
iCustom_ = MAKE_IND(0, 0, 0, IND_CUSTOM), // {iCustom}(...)[?]
| |||
| |||
iAC_ = MAKE_IND(0, 1, 1, IND_AC), // iAC( )[1]*
| |||
iAD_volume = MAKE_IND(1, 1, 1, IND_AD), // iAD(volume)[1]*
| |||
iADX_period = MAKE_IND(1, 3, 1, IND_ADX), // iADX(period)[3]*
| |||
iADXWilder_period = MAKE_IND(1, 3, 1, IND_ADXW), // iADXWilder(period)[3]*
| |||
iAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price = MAKE_IND(8, 3, 0, IND_ALLIGATOR), // iAlligator(jawP,jawS,teethP,teethS,lipsP,lipsS,method,price)[3]
| |||
iAMA_period_fast_slow_shift_price = MAKE_IND(5, 1, 0, IND_AMA), // iAMA(period,fast,slow,shift,price)[1]
| |||
iAO_ = MAKE_IND(0, 1, 1, IND_AO), // iAO( )[1]*
| |||
iATR_period = MAKE_IND(1, 1, 1, IND_ATR), // iATR(period)[1]*
| |||
iBands_period_shift_deviation_price = MAKE_IND(4, 3, 0, IND_BANDS), // iBands(period,shift,deviation,price)[3]
| |||
iBearsPower_period = MAKE_IND(1, 1, 1, IND_BEARS), // iBearsPower(period)[1]*
| |||
iBullsPower_period = MAKE_IND(1, 1, 1, IND_BULLS), // iBullsPower(period)[1]*
| |||
iBWMFI_volume = MAKE_IND(1, 1, 1, IND_BWMFI), // iBWMFI(volume)[1]*
| |||
iCCI_period_price = MAKE_IND(2, 1, 1, IND_CCI), // iCCI(period,price)[1]*
| |||
iChaikin_fast_slow_method_volume = MAKE_IND(4, 1, 1, IND_CHAIKIN), // iChaikin(fast,slow,method,volume)[1]*
| |||
iDEMA_period_shift_price = MAKE_IND(3, 1, 0, IND_DEMA), // iDEMA(period,shift,price)[1]
| |||
iDeMarker_period = MAKE_IND(1, 1, 1, IND_DEMARKER), // iDeMarker(period)[1]*
| |||
iEnvelopes_period_shift_method_price_deviation = MAKE_IND(5, 2, 0, IND_ENVELOPES), // iEnvelopes(period,shift,method,price,deviation)[2]
| |||
iForce_period_method_volume = MAKE_IND(3, 1, 1, IND_FORCE), // iForce(period,method,volume)[1]*
| |||
iFractals_ = MAKE_IND(0, 2, 0, IND_FRACTALS), // iFractals( )[2]
| |||
iFrAMA_period_shift_price = MAKE_IND(3, 1, 0, IND_FRAMA), // iFrAMA(period,shift,price)[1]
| |||
iGator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price = MAKE_IND(8, 4, 1, IND_GATOR), // iGator(jawP,jawS,teethP,teethS,lipsP,lipsS,method,price)[4]*
| |||
iIchimoku_tenkan_kijun_senkou = MAKE_IND(3, 5, 0, IND_ICHIMOKU), // iIchimoku(tenkan,kijun,senkou)[5]
| |||
iMomentum_period_price = MAKE_IND(2, 1, 1, IND_MOMENTUM), // iMomentum(period,price)[1]*
| |||
iMFI_period_volume = MAKE_IND(2, 1, 1, IND_MFI), // iMFI(period,volume)[1]*
| |||
iMA_period_shift_method_price = MAKE_IND(4, 1, 0, IND_MA), // iMA(period,shift,method,price)[1]
| |||
iMACD_fast_slow_signal_price = MAKE_IND(4, 2, 1, IND_MACD), // iMACD(fast,slow,signal,price)[2]*
| |||
iOBV_volume = MAKE_IND(1, 1, 1, IND_OBV), // iOBV(volume)[1]*
| |||
iOsMA_fast_slow_signal_price = MAKE_IND(4, 1, 1, IND_OSMA), // iOsMA(fast,slow,signal,price)[1]*
| |||
iRSI_period_price = MAKE_IND(2, 1, 1, IND_RSI), // iRSI(period,price)[1]*
| |||
iRVI_period = MAKE_IND(1, 2, 1, IND_RVI), // iRVI(period)[2]*
| |||
iSAR_step_maximum = MAKE_IND(2, 1, 0, IND_SAR), // iSAR(step,maximum)[1]
| |||
iStdDev_period_shift_method_price = MAKE_IND(4, 1, 1, IND_STDDEV), // iStdDev(period,shift,method,price)[1]*
| |||
iStochastic_K_D_slowing_method_price = MAKE_IND(5, 2, 1, IND_STOCHASTIC), // iStochastic(K,D,slowing,method,price)[2]*
| |||
iTEMA_period_shift_price = MAKE_IND(3, 1, 0, IND_TEMA), // iTEMA(period,shift,price)[1]
| |||
iTriX_period_price = MAKE_IND(2, 1, 1, IND_TRIX), // iTriX(period,price)[1]*
| |||
iVIDyA_momentum_smooth_shift_price = MAKE_IND(4, 1, 0, IND_VIDYA), // iVIDyA(momentum,smooth,shift,price)[1]
| |||
iVolumes_volume = MAKE_IND(1, 1, 1, IND_VOLUMES), // iVolumes(volume)[1]*
| |||
iWPR_period = MAKE_IND(1, 1, 1, IND_WPR), // iWPR(period)[1]*
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Indicator builder |
| |||
//+------------------------------------------------------------------+
| |||
class AutoIndicator
| |||
{
| |||
protected:
| |||
IndicatorType type; // selected indicator type
| |||
string symbol; // optional work symbol
| |||
ENUM_TIMEFRAMES tf; // optional work timeframe
| |||
MqlParamBuilder builder; // helper object to build array of parameters
| |||
int handle; // indicator handle
| |||
string name; // custom indicator name
| |||
| |||
// helper to check parameters of built-in indicators
| |||
bool isNumerical(const int i)
| |||
{
| |||
if(builder.typeOf(i) == TYPE_STRING || builder.typeOf(i) == TYPE_BOOL)
| |||
{
| |||
return false;
| |||
}
| |||
return true;
| |||
}
| |||
| |||
// helper to check parameters of built-in indicators
| |||
bool checkForNumericals()
| |||
{
| |||
const int pnum = builder.size(); // ArraySize(params);
| |||
for(int i = 0; i < pnum; i++)
| |||
{
| |||
if(!isNumerical(i))
| |||
{
| |||
Print(EnumToString(type) + " parameter ", (i + 1), " must be a number, ",
| |||
EnumToString(builder.typeOf(i)), " given");
| |||
return false;
| |||
}
| |||
}
| |||
return true;
| |||
}
| |||
| |||
// check number and types of parameters of built-in indicators
| |||
bool checkStandard()
| |||
{
| |||
const int pnum = builder.size(); // ArraySize(params);
| |||
if(IND_PARAMS(type) != pnum)
| |||
{
| |||
Print(EnumToString(type) + " requires " + (string)IND_PARAMS(type) + " parameters, " + (string)pnum + " given");
| |||
return false;
| |||
}
| |||
| |||
// built-in indicators have numeric parameters only
| |||
return checkForNumericals();
| |||
}
| |||
| |||
// return integer constant for specific textual descrpition
| |||
int lookUpLiterals(const string &s)
| |||
{
| |||
if(s == "sma") return MODE_SMA;
| |||
else if(s == "ema") return MODE_EMA;
| |||
else if(s == "smma") return MODE_SMMA;
| |||
else if(s == "lwma") return MODE_LWMA;
| |||
| |||
else if(s == "close") return PRICE_CLOSE;
| |||
else if(s == "open") return PRICE_OPEN;
| |||
else if(s == "high") return PRICE_HIGH;
| |||
else if(s == "low") return PRICE_LOW;
| |||
else if(s == "median") return PRICE_MEDIAN;
| |||
else if(s == "typical") return PRICE_TYPICAL;
| |||
else if(s == "weighted") return PRICE_WEIGHTED;
| |||
| |||
else if(s == "lowhigh") return STO_LOWHIGH; // 0
| |||
else if(s == "closeclose") return STO_CLOSECLOSE; // 1
| |||
| |||
else if(s == "tick") return VOLUME_TICK; // 0
| |||
else if(s == "real") return VOLUME_REAL; // 1
| |||
| |||
return -1;
| |||
}
| |||
| |||
// parse comma separated list of parameters 'p1,p2,p3,...'
| |||
// into array of elements of appropriate types,
| |||
// for example, '1.0' goes to double, 123 makes int, "text" means string,
| |||
// true/false are booleans, "2021.01.01" is a datetime
| |||
int parseParameters(const string &list)
| |||
{
| |||
string sparams[];
| |||
const int n = StringSplit(list, ',', sparams);
| |||
| |||
for(int i = 0; i < n; i++)
| |||
{
| |||
// normalize the string (trim it and change to lower case)
| |||
StringTrimLeft(sparams[i]);
| |||
StringTrimRight(sparams[i]);
| |||
StringToLower(sparams[i]);
| |||
| |||
if(StringGetCharacter(sparams[i], 0) == '"'
| |||
&& StringGetCharacter(sparams[i], StringLen(sparams[i]) - 1) == '"')
| |||
{
| |||
// anything inside quotes is a string
| |||
builder << StringSubstr(sparams[i], 1, StringLen(sparams[i]) - 2);
| |||
}
| |||
else
| |||
{
| |||
string part[];
| |||
int p = StringSplit(sparams[i], '.', part);
| |||
if(p == 2) // double/float
| |||
{
| |||
builder << StringToDouble(sparams[i]);
| |||
}
| |||
else if(p == 3) // datetime
| |||
{
| |||
builder << StringToTime(sparams[i]);
| |||
}
| |||
else if(sparams[i] == "true")
| |||
{
| |||
builder << true;
| |||
}
| |||
else if(sparams[i] == "false")
| |||
{
| |||
builder << false;
| |||
}
| |||
else // int
| |||
{
| |||
int x = lookUpLiterals(sparams[i]);
| |||
if(x == -1)
| |||
{
| |||
x = (int)StringToInteger(sparams[i]);
| |||
}
| |||
builder << x;
| |||
}
| |||
}
| |||
}
| |||
| |||
if(type != iCustom_)
| |||
{
| |||
checkStandard(); // show warning for wrong types or number of parameters
| |||
}
| |||
return n;
| |||
}
| |||
| |||
// calls IndicatorCreate for prepared list of parameters
| |||
int create()
| |||
{
| |||
MqlParam p[];
| |||
// fill array 'p' with parameters acquired by builder
| |||
builder >> p;
| |||
| |||
if(type == iCustom_)
| |||
{
| |||
// insert name of custom indicator at the beginning
| |||
ArraySetAsSeries(p, true);
| |||
const int n = ArraySize(p);
| |||
ArrayResize(p, n + 1);
| |||
p[n].type = TYPE_STRING;
| |||
p[n].string_value = name;
| |||
ArraySetAsSeries(p, false);
| |||
}
| |||
| |||
return IndicatorCreate(symbol, tf, IND_ID(type), ArraySize(p), p);
| |||
}
| |||
| |||
public:
| |||
AutoIndicator(const IndicatorType t, const string custom, const string parameters,
| |||
const string s = NULL, const ENUM_TIMEFRAMES p = 0):
| |||
type(t), name(custom), symbol(s), tf(p), handle(INVALID_HANDLE)
| |||
{
| |||
if(type != iCustom_ && StringLen(name) > 0)
| |||
{
| |||
PrintFormat("Indicator name '%s' will be discarded, because of built-in type selection", name);
| |||
name = NULL;
| |||
}
| |||
| |||
if(type == iCustom_ && StringLen(name) == 0)
| |||
{
| |||
Print("Custom indicator name is missing");
| |||
}
| |||
else
| |||
{
| |||
PrintFormat("Initializing %s(%s) %s, %s",
| |||
(type == iCustom_ ? name : EnumToString(type)), parameters,
| |||
(symbol == NULL ? _Symbol : symbol), EnumToString(tf == 0 ? _Period : tf));
| |||
// parse string with indicator parameters
| |||
parseParameters(parameters);
| |||
// then create and store its handle
| |||
handle = create();
| |||
}
| |||
}
| |||
| |||
int getHandle() const
| |||
{
| |||
return handle;
| |||
}
| |||
| |||
string getName() const
| |||
{
| |||
if(type != iCustom_)
| |||
{
| |||
const string s = EnumToString(type);
| |||
const int p = StringFind(s, "_");
| |||
if(p > 0) return StringSubstr(s, 0, p);
| |||
return s;
| |||
}
| |||
return name;
| |||
}
| |||
};
| |||
//+------------------------------------------------------------------+
|