247 lines
6.6 KiB
MQL5
247 lines
6.6 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| EMD.mq5 |
|
|
//| Copyright (c) 2012-2020, victorg, Marketeer |
|
|
//| https://www.mql5.com/en/users/marketeer |
|
|
//| https://www.mql5.com/ru/articles/7601 |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright (c) 2012-2020 victorg, Marketeer"
|
|
#property link "https://www.mql5.com/en/users/marketeer"
|
|
#property version "1.0"
|
|
#property description "Price Forecast based on Empirical Mode Decomposition (EMD)\n"
|
|
#property description "This indicator breaks down price line into multiple simple components - polynomial splines (Intrinsic Mode Functions, IMF) - and builds their superposition in near future."
|
|
|
|
#define BUF_NUM 1
|
|
|
|
#property indicator_chart_window
|
|
#property indicator_buffers BUF_NUM
|
|
#property indicator_plots BUF_NUM
|
|
|
|
#property indicator_color1 DodgerBlue
|
|
#property indicator_width1 2
|
|
#property indicator_applied_price PRICE_OPEN
|
|
|
|
|
|
#include <IndArray.mqh>
|
|
IndicatorArray buffers(BUF_NUM);
|
|
IndicatorArrayGetter getter(buffers);
|
|
|
|
|
|
#include "EMD.mqh"
|
|
|
|
|
|
input int Length = 300; // Length (bars, > 5)
|
|
input int _Offset = 0; // Offset (0..P bars)
|
|
input int Forecast = 0; // Forecast (0..N bars)
|
|
input int Reconstruction = 0; // Reconstruction (0..M IMFs)
|
|
|
|
|
|
int Offset;
|
|
const string ID = "EMDP_OFFSETTER";
|
|
|
|
|
|
int OnInit()
|
|
{
|
|
Offset = _Offset;
|
|
|
|
const datetime dt = (datetime)ObjectGetInteger(0, ID, OBJPROP_TIME, 0);
|
|
if(dt > TimeCurrent())
|
|
{
|
|
Offset = 0;
|
|
}
|
|
else if(dt > 0)
|
|
{
|
|
Offset = iBarShift(_Symbol, _Period, dt);
|
|
}
|
|
|
|
IndicatorSetString(INDICATOR_SHORTNAME, "EMDPrice (" + (string)Length + ")");
|
|
for(int i = 0; i < BUF_NUM; i++)
|
|
{
|
|
PlotIndexSetInteger(i, PLOT_DRAW_TYPE, DRAW_LINE);
|
|
PlotIndexSetInteger(i, PLOT_SHIFT, Forecast);
|
|
}
|
|
|
|
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(), true);
|
|
|
|
showOffsetter(true);
|
|
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Custom indicator iteration function |
|
|
//+------------------------------------------------------------------+
|
|
int OnCalculate(const int rates_total,
|
|
const int prev_calculated,
|
|
const int begin,
|
|
const double &price[])
|
|
{
|
|
int i, ret;
|
|
|
|
ArraySetAsSeries(price, true);
|
|
|
|
static datetime lastBar = 0;
|
|
static int barCount = 0;
|
|
|
|
if(iTime(NULL, 0, 0) == lastBar && barCount == rates_total && prev_calculated != 0) return rates_total;
|
|
|
|
for(int k = 0; k < BUF_NUM; k++)
|
|
{
|
|
buffers[k].empty();
|
|
}
|
|
|
|
lastBar = iTime(NULL, 0, 0);
|
|
barCount = rates_total;
|
|
ObjectSetInteger(0, ID, OBJPROP_TIME, 0, iTime(NULL, 0, Offset)); // advance the line to next bar
|
|
|
|
// Preparation of the input sequence
|
|
double yy[];
|
|
int n = Length;
|
|
ArrayResize(yy, n, n + Forecast);
|
|
|
|
for(i = 0; i < n; i++)
|
|
{
|
|
yy[i] = price[n - i + Offset - 1]; // we need to reverse for optional extrapolation
|
|
}
|
|
|
|
// EMD
|
|
CEMD emd;
|
|
ret = emd.decomp(yy, Forecast);
|
|
|
|
if(ret < 0) return prev_calculated;
|
|
|
|
const int N = emd.getN();
|
|
const double mean = emd.getMean();
|
|
|
|
n += Forecast;
|
|
ArrayResize(yy, n);
|
|
|
|
// Visualization
|
|
IndicatorSetString(INDICATOR_SHORTNAME, "EMDPrice (" + (string)Length + "," + (string)N + "," + (string)(float)mean + ")");
|
|
|
|
for(i = 0; i < BUF_NUM; i++)
|
|
{
|
|
PlotIndexSetInteger(i, PLOT_SHOW_DATA, i <= N + 1);
|
|
PlotIndexSetInteger(i, PLOT_LINE_WIDTH, i == 0 ? 2 : 1);
|
|
PlotIndexSetInteger(i, PLOT_LINE_STYLE, STYLE_SOLID);
|
|
}
|
|
|
|
double sum[];
|
|
ArrayResize(sum, n);
|
|
ArrayInitialize(sum, 0);
|
|
|
|
PlotIndexSetString(0, PLOT_LABEL, "Input Line");
|
|
|
|
for(i = 1; i < N; i++)
|
|
{
|
|
emd.getIMF(yy, i, true);
|
|
if(i > Reconstruction)
|
|
{
|
|
for(int j = 0; j < n; j++)
|
|
{
|
|
sum[j] += yy[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
emd.getIMF(yy, N, true);
|
|
|
|
for(int j = 0; j < n; j++)
|
|
{
|
|
sum[j] += yy[j] + mean;
|
|
if(j < Forecast && (Reconstruction <= 0 || Reconstruction > N - 1)) // completely fitted curve can not be forecasted (gives a constant)
|
|
{
|
|
sum[j] = EMPTY_VALUE;
|
|
}
|
|
}
|
|
|
|
PlotIndexSetString(0, PLOT_LABEL, "EMD"
|
|
+ (Reconstruction > 0 ?
|
|
" -" + (Reconstruction < N - 1 ? (string)Reconstruction : "R")
|
|
: ""));
|
|
|
|
buffers[0].set(Offset, sum);
|
|
|
|
return rates_total;
|
|
}
|
|
|
|
|
|
void showOffsetter(const bool selectable = false)
|
|
{
|
|
ObjectCreate(0, ID, OBJ_VLINE, ChartWindowFind(), iTime(_Symbol, _Period, Offset), 0);
|
|
ObjectSetInteger(0, ID, OBJPROP_STYLE, STYLE_DOT);
|
|
ObjectSetInteger(0, ID, OBJPROP_COLOR, ChartGetInteger(0, CHART_COLOR_GRID) & 0xFFFF);
|
|
ObjectSetInteger(0, ID, OBJPROP_BACK, true);
|
|
ObjectSetInteger(0, ID, OBJPROP_SELECTABLE, selectable);
|
|
ObjectSetInteger(0, ID, OBJPROP_ZORDER, 1000);
|
|
ObjectSetString(0, ID, OBJPROP_TEXT, "past <--> future");
|
|
}
|
|
|
|
double OnTester(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void OnDeinit(const int)
|
|
{
|
|
ObjectDelete(0, ID);
|
|
}
|
|
|
|
#define MOUSE_LEFT 1
|
|
|
|
void OnChartEvent(const int id, const long &lp, const double &dp, const string &sp)
|
|
{
|
|
static bool dragged = false;
|
|
if(id == CHARTEVENT_OBJECT_DRAG && sp == ID)
|
|
{
|
|
dragged = true;
|
|
}
|
|
else if(id == CHARTEVENT_MOUSE_MOVE)
|
|
{
|
|
const int flags = (int)StringToInteger(sp);
|
|
static int current = 0;
|
|
if((current & MOUSE_LEFT) != 0 && (flags & MOUSE_LEFT) == 0)
|
|
{
|
|
if(dragged)
|
|
{
|
|
dragged = false;
|
|
const int x = (int)lp;
|
|
const int y = (int)dp;
|
|
int window;
|
|
datetime time;
|
|
double price;
|
|
ChartXYToTimePrice(0, x, y, window, time, price);
|
|
|
|
double cursor = (double)time;
|
|
datetime newtime = (datetime)(MathRound(cursor / PeriodSeconds()) * PeriodSeconds());
|
|
|
|
adjust(newtime);
|
|
}
|
|
}
|
|
current = flags;
|
|
}
|
|
}
|
|
|
|
void adjust(const datetime dt)
|
|
{
|
|
const int PrevOffset = Offset;
|
|
|
|
if(dt + PeriodSeconds() > TimeCurrent())
|
|
{
|
|
Offset = 0;
|
|
ObjectSetInteger(0, ID, OBJPROP_TIME, 0, iTime(_Symbol, _Period, 0));
|
|
|
|
ChartRedraw();
|
|
}
|
|
else
|
|
{
|
|
Offset = iBarShift(_Symbol, _Period, dt);
|
|
}
|
|
|
|
if(PrevOffset != Offset)
|
|
{
|
|
// we need this because OnCalculate can be called before we update Offset,
|
|
// and the line is moved to its previous place before the dragging
|
|
ObjectSetInteger(0, ID, OBJPROP_TIME, 0, iTime(_Symbol, _Period, Offset));
|
|
ChartSetSymbolPeriod(0, NULL, 0);
|
|
}
|
|
}
|