1189 lines
60 KiB
MQL5
1189 lines
60 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| ExpCodePatternFinder.mq5 |
|
|
//| Copyright 2026, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2026, MetaQuotes Ltd."
|
|
#property link "https://www.mql5.com"
|
|
#property version "1.00"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Включаемые файлы |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Перечисления |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Структуры |
|
|
//+------------------------------------------------------------------+
|
|
struct SPatternStep // Структура одного бара паттерна
|
|
{
|
|
int code; // Код Лиховидова
|
|
datetime time; // Время бара
|
|
double open; // Цена открытия
|
|
double high; // Цена High
|
|
double low; // Цена Low
|
|
double close; // Цена Close
|
|
double open_base; // Цена открытия базовой точки
|
|
datetime time_base; // Время базовой точки
|
|
double rel_pos; // Позиция относительно базовой точки (Open[i] - open_base) / ATR
|
|
};
|
|
|
|
struct SPattern // Структура паттерна
|
|
{
|
|
SPatternStep steps[]; // Глубина паттерна
|
|
ENUM_POSITION_TYPE type; // POSITION_TYPE_BUY (Low ZZ) или POSITION_TYPE_SELL (High ZZ)
|
|
datetime time; // Время экстремума
|
|
//--- Устанавливает глубину паттерна
|
|
bool SetSteps(const int size){ return(ArrayResize(steps,size)==size); }
|
|
};
|
|
|
|
struct SCandleParts // Структура кода Лиховидова
|
|
{
|
|
int body; // Градация тела (0-3)
|
|
int us; // Верхняя тень (0-3)
|
|
int ls; // Нижняя тень (0-3)
|
|
ENUM_POSITION_TYPE direction; // Направление (BUY/SELL)
|
|
};
|
|
|
|
struct SMatchResult // Структура результатов поиска
|
|
{
|
|
int best_idx; // Индекс лучшего паттерна
|
|
string signal; // Текст сигнала
|
|
int cnt_buy; // Количество совпадений BUY
|
|
int cnt_sell; // Количество совпадений SELL
|
|
double avg_buy; // Среднее сходство BUY
|
|
double avg_sell; // Среднее сходство SELL
|
|
|
|
//--- Конструктор
|
|
SMatchResult() : best_idx(WRONG_VALUE), signal("UNCERTAIN"), cnt_buy(0), cnt_sell(0), avg_buy(0), avg_sell(0) {}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Макроподстановки |
|
|
//+------------------------------------------------------------------+
|
|
#define ATTEMPTS_MAX 10 // Максимальное количество попыток получения базы паттернов
|
|
#define MAX_POS_DIST 0.5 // 0.5 ATR - максимальный предел смещения (0% сходства вертикальной позиции)
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Входные параметры |
|
|
//+------------------------------------------------------------------+
|
|
//--- ZigZag
|
|
sinput string InpParamZZ = "---"; // --- ZigZag Parameters ---
|
|
input int InpZZDepth = 12; // ZZ Depth
|
|
input int InpZZDeviation = 5; // ZZ Deviation
|
|
input int InpZZBackstep = 3; // ZZ Back Step
|
|
|
|
//--- Candle Code
|
|
sinput string InpParamCC = "---"; // --- Candle Code Parameters ---
|
|
input int InpCCBBLength = 55; // Candle Code BB Length (Thresholds)
|
|
input double InpCCBBNumDevs = 0.3; // Candle Code BB Deviations
|
|
input int InpCCAvgLength = 9; // Candle Code Average Length (SMA)
|
|
|
|
//--- ATR
|
|
sinput string InpParamATR = "---"; // --- ATR Parameters ---
|
|
input int InpATRPeriod = 14; // ATR Period
|
|
|
|
//--- Параметры поиска паттернов
|
|
sinput string InpParamPattSrc= "---"; // --- Pattern search Parameters ---
|
|
input int InpPatternDepth= 5; /* Pattern Depth (Bars) */ // Размер паттерна в барах
|
|
input uint InpMaxGradeDiff= 1; /* Maximum difference in gradations (0-3) */ // Максимальная разница по градациям тела/теней для совпадения (0-3)
|
|
input double InpMinCandleSim= 50.0; /* Candles similarity (0-100%) */ // Сходство свечей (0-100%)
|
|
input double InpMinPosSim = 50.0; /* Relative position similarity (0-100%) */ // Сходство относительного расположения (0-100%)
|
|
input double InpTotalSim = 65.0; /* Final pattern similarity threshold (0-100%) */ // Итоговый порог похожести паттерна (0-100%)
|
|
|
|
//--- Параметры окна паттерна
|
|
sinput string InpParamPattWnd= "---"; // --- Pattern window Parameters ---
|
|
input int InpWndX = 30; /* Pattern Window X offset */ // Смещение окна по оси X
|
|
input int InpWndY = 30; /* Pattern Window Y offset */ // Смещение окна по оси Y
|
|
input int InpWndW = 500; /* Pattern Window Width */ // Ширина окна паттерна
|
|
input int InpWndH = 250; /* Pattern Window Height */ // Высота окна паттерна
|
|
|
|
//--- Параметры текста статистики
|
|
sinput string InpParamStatsTxt= "---"; // --- Statistics text Parameters ---
|
|
input int InpStatsX = 10; /* Statistics X offset */ // Смещение текста по оси X
|
|
input int InpStatsY = 20; /* Statistics Y offset */ // Смещение текста по оси Y
|
|
input int InpStatsStep = 15; /* Statistics Line Step */ // Вертикальный шаг между строками
|
|
input bool InpStatsBack = true; /* Use background for statistics */ // Использовать подложку для текста статистики
|
|
|
|
//--- Параметры ручного режима
|
|
sinput string InpParamManMode= "---"; // --- Manual mode Parameters ---
|
|
input bool InpManualMode = true; /* Manual bar selection (Ctrl + Click) */ // Включить ручной выбор бара
|
|
input color InpAnalysColor = clrGray; /* Analysis line color */ // Цвет линии выбранного бара
|
|
|
|
//--- Параметры советника
|
|
sinput string InpParamExp = "---"; // --- Expert Parameters ---
|
|
input ulong InpMagic = 123; /* Magic Number */ // Идентификатор эксперта
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Глобальные переменные |
|
|
//+------------------------------------------------------------------+
|
|
int handle_zz; // Хэндл ZigZag
|
|
int handle_cc; // Хэндл Candle Code
|
|
int handle_atr; // Хэндл ATR
|
|
int zz_depth; // ZZ Depth
|
|
int zz_dev; // ZZ Deviation
|
|
int zz_back; // ZZ Back Step
|
|
int cc_bb_len; // Candle Code BB Length (Thresholds)
|
|
int cc_avg_len; // Candle Code Average Length (SMA)
|
|
int atr_period; // ATR Period
|
|
|
|
int ExtPatternDepth; // Размер паттерна в барах
|
|
uint ExtMaxGradeDiff; // Максимальная разница по градациям тела/теней для совпадения (0-3)
|
|
double ExtMinCandleSim; // Сходство свечей (0-100%)
|
|
double ExtMinPosSim; // Сходство относительного расположения (0-100%)
|
|
double ExtTotalSim; // Итоговый порог похожести паттерна (0-100%)
|
|
|
|
int attempts_count; // Счётчик попыток сбора базы паттернов
|
|
|
|
SPattern patterns[]; // Массив паттернов
|
|
SMatchResult match_result; // Структура результатов поиска
|
|
bool base_is_ready; // Флаг готовности базы паттернов
|
|
|
|
string prefix; // Префикс имён объектов
|
|
int start_search_index; // Бар начала поиска
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
//--- create timer
|
|
EventSetTimer(60);
|
|
|
|
//--- Инициализируем массив паттернов
|
|
ArrayFree(patterns);
|
|
|
|
//--- Устанавливаем и корректируем входные параметры индикаторов
|
|
//--- ZigZag
|
|
zz_depth=(InpZZDepth <1 ? 12 : InpZZDepth);
|
|
zz_dev=(InpZZDeviation <1 ? 5 : InpZZDeviation);
|
|
zz_back=(InpZZBackstep <1 ? 3 : InpZZBackstep);
|
|
|
|
//--- Candle Code
|
|
cc_bb_len=(InpCCBBLength <1 ? 55 : InpCCBBLength);
|
|
cc_avg_len=(InpCCAvgLength<1 ? 9 : InpCCAvgLength);
|
|
|
|
//--- ATR
|
|
atr_period=(InpATRPeriod<1 ? 14 : InpATRPeriod);
|
|
|
|
//--- Создаём хэндлы индикаторов
|
|
//--- ATR
|
|
handle_atr=iATR(_Symbol,_Period,atr_period);
|
|
if(handle_atr==INVALID_HANDLE)
|
|
{
|
|
PrintFormat("%s: Failed to create iATR(%d) handle",__FUNCTION__,atr_period);
|
|
return INIT_FAILED;
|
|
}
|
|
//--- ZigZag \MQL5\Indicators\Examples\ZigZag.mq5
|
|
handle_zz=iCustom(_Symbol,_Period,"Examples\\ZigZag",zz_depth,zz_dev,zz_back);
|
|
if(handle_zz==INVALID_HANDLE)
|
|
{
|
|
PrintFormat("%s: Failed to create ZigZag(%d,%d,%d) handle",__FUNCTION__,zz_depth,zz_dev,zz_back);
|
|
return INIT_FAILED;
|
|
}
|
|
//--- CandleCode
|
|
handle_cc=iCustom(_Symbol,_Period,"Shared Projects\\Article-21319-MQL5-CandleCode-Indicator\\CandleCode",cc_bb_len,InpCCBBNumDevs,cc_avg_len);
|
|
if(handle_cc==INVALID_HANDLE)
|
|
{
|
|
PrintFormat("%s: Failed to create CandleCode(%d,%.1f,%d) handle",__FUNCTION__,cc_bb_len,InpCCBBNumDevs,cc_avg_len);
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
//--- Инициализируем флаг готовности и количество попыток получения базы паттернов
|
|
base_is_ready=false;
|
|
attempts_count=0;
|
|
|
|
//--- Корректируем введённые значения поиска паттернов
|
|
ExtPatternDepth=(InpPatternDepth<0 ? 5 : InpPatternDepth);
|
|
ExtMinCandleSim=(InpMinCandleSim<0 ? 80.0 : InpMinCandleSim>100 ? 100.0 : InpMinCandleSim);
|
|
ExtMinPosSim=(InpMinPosSim<0 ? 80.0 : InpMinPosSim>100 ? 100.0 : InpMinPosSim);
|
|
ExtMaxGradeDiff=int(InpMaxGradeDiff>3 ? 3 : InpMaxGradeDiff);
|
|
ExtTotalSim=(InpTotalSim<0 ? 85.0 : InpTotalSim>100 ? 100.0 : InpTotalSim);
|
|
start_search_index=1;
|
|
|
|
prefix=MQLInfoString(MQL_PROGRAM_NAME)+(string)InpMagic;
|
|
|
|
//--- Пытаемся сформировать базу паттернов сразу при инициализации
|
|
base_is_ready=CreatePatternBase(ATTEMPTS_MAX,ExtPatternDepth,cc_bb_len,attempts_count,patterns);
|
|
|
|
//--- Если база готова - ищем паттерны и выводим статистику
|
|
if(base_is_ready)
|
|
{
|
|
int total=ArraySize(patterns);
|
|
PrintFormat("%s: The pattern database has been successfully created. Total patterns: %d",__FUNCTION__,total);
|
|
|
|
FindAndDisplayMatches(ExtPatternDepth,ExtTotalSim,ExtMinCandleSim,ExtMinPosSim,ExtMaxGradeDiff,patterns,match_result,prefix+"_BestMatchWindow",
|
|
InpWndX, InpWndY, InpWndW, InpWndH,InpStatsX, InpStatsY, InpStatsStep,InpStatsBack,start_search_index,InpAnalysColor);
|
|
}
|
|
|
|
//--- Всё успешно
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
//--- destroy timer
|
|
EventKillTimer();
|
|
|
|
//--- Освобождаем массив паттернов
|
|
ArrayFree(patterns);
|
|
|
|
//--- Удаляем графические объекты
|
|
ObjectsDeleteAll(ChartID(),prefix);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
//--- Если база паттернов ещё не готова - пытаемся создать
|
|
if(!base_is_ready)
|
|
{
|
|
if(CreatePatternBase(ATTEMPTS_MAX,ExtPatternDepth,cc_bb_len,attempts_count,patterns))
|
|
{
|
|
base_is_ready=true;
|
|
int total=ArraySize(patterns);
|
|
PrintFormat("%s: The pattern database has been successfully created. Total patterns: %d",__FUNCTION__,total);
|
|
}
|
|
}
|
|
|
|
//--- Если база всё еще не готова - выходим до следующего тика
|
|
if(!base_is_ready)
|
|
return;
|
|
|
|
//--- Поиск совпадений при появлении нового бара
|
|
if(IsNewBar())
|
|
{
|
|
//--- Если работа в автоматическом режиме, устанавливаем индекс на прошлый бар (с индексом 1)
|
|
if(!InpManualMode)
|
|
{
|
|
//--- Выполняем поиск и визуализацию результатов, начиная от первого бара
|
|
PrintFormat("%s: New Bar. Automatic search for matches from index 1",__FUNCTION__);
|
|
start_search_index=1;
|
|
FindAndDisplayMatches(ExtPatternDepth,ExtTotalSim,ExtMinCandleSim,ExtMinPosSim,ExtMaxGradeDiff,patterns,match_result,prefix+"_BestMatchWindow",
|
|
InpWndX,InpWndY,InpWndW,InpWndH,InpStatsX,InpStatsY,InpStatsStep,InpStatsBack,start_search_index,InpAnalysColor);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Timer function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer()
|
|
{
|
|
//---
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Trade function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTrade()
|
|
{
|
|
//---
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| TradeTransaction function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTradeTransaction(const MqlTradeTransaction& trans,
|
|
const MqlTradeRequest& request,
|
|
const MqlTradeResult& result)
|
|
{
|
|
//---
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| ChartEvent function |
|
|
//+------------------------------------------------------------------+
|
|
void OnChartEvent(const int32_t id,
|
|
const long &lparam,
|
|
const double &dparam,
|
|
const string &sparam)
|
|
{
|
|
//--- Если режим автоматический - уходим
|
|
if(!InpManualMode)
|
|
return;
|
|
|
|
//--- Обработка клика по графику в ручном режиме
|
|
if(id==CHARTEVENT_CLICK)
|
|
{
|
|
ChartClickHandler(lparam,dparam,start_search_index);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает флаг открытия нового бара |
|
|
//+------------------------------------------------------------------+
|
|
bool IsNewBar(void)
|
|
{
|
|
static datetime lastbar=0;
|
|
datetime bar=iTime(_Symbol,_Period,0);
|
|
if(bar==lastbar || bar==0)
|
|
return false;
|
|
lastbar=bar;
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает данные цен с указанного бара в заданном количестве |
|
|
//+------------------------------------------------------------------+
|
|
bool GetPriceData(const int index,const int count,MqlRates &array[])
|
|
{
|
|
ResetLastError();
|
|
if(CopyRates(_Symbol,_Period,index,count,array)!=count)
|
|
{
|
|
PrintFormat("%s: CopyRates(%d,%d) failed. Error %d",__FUNCTION__,index,count,GetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает данные индикатора ATR |
|
|
//| с указанного бара в заданном количестве |
|
|
//+------------------------------------------------------------------+
|
|
bool GetATRData(const int index,const int count,double &array[])
|
|
{
|
|
ResetLastError();
|
|
if(CopyBuffer(handle_atr,0,index,count,array)!=count)
|
|
{
|
|
PrintFormat("%s: CopyBuffer(%d,%d) failed. Error %d",__FUNCTION__,index,count,GetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает данные ZigZag в заданном количестве |
|
|
//| с указанного буфера и бара |
|
|
//+------------------------------------------------------------------+
|
|
bool GetZZData(const int buffer,const int index,const int count,double &array[])
|
|
{
|
|
if(buffer>2)
|
|
{
|
|
PrintFormat("%s: Error: Invalid buffer number (%d). Must be 0 - 2",__FUNCTION__,buffer);
|
|
return false;
|
|
}
|
|
ResetLastError();
|
|
if(CopyBuffer(handle_zz,buffer,index,count,array)!=count)
|
|
{
|
|
PrintFormat("%s: CopyBuffer(%d,%d) failed. Error %d",__FUNCTION__,index,count,GetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает данные индикатора Candle Code |
|
|
//| с указанного бара в заданном количестве |
|
|
//+------------------------------------------------------------------+
|
|
bool GetCandleCodeData(const int index,const int count,double &array[])
|
|
{
|
|
ResetLastError();
|
|
if(CopyBuffer(handle_cc,0,index,count,array)!=count)
|
|
{
|
|
PrintFormat("%s: CopyBuffer(%d,%d) failed. Error %d",__FUNCTION__,index,count,GetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Сбор паттернов по всей истории |
|
|
//+------------------------------------------------------------------+
|
|
bool CollectHistoryData(const int pattern_size,const int limit,SPattern &array[])
|
|
{
|
|
//--- Проверяем наличие необходимой истории
|
|
int bars_total=iBars(_Symbol,_Period);
|
|
if(bars_total<limit+pattern_size+2)
|
|
return false;
|
|
|
|
//--- Очищаем массив паттернов
|
|
if(ArraySize(array)>0)
|
|
ArrayFree(array);
|
|
|
|
//--- Массивы для получения данных
|
|
MqlRates rates_hist[];
|
|
double atr_hist[],zz0_hist[],zz1_hist[],zz2_hist[],cc_hist[];
|
|
|
|
//--- Массивы как таймсерии
|
|
ArraySetAsSeries(rates_hist,true);
|
|
ArraySetAsSeries(atr_hist,true);
|
|
ArraySetAsSeries(zz0_hist,true);
|
|
ArraySetAsSeries(zz1_hist,true);
|
|
ArraySetAsSeries(zz2_hist,true);
|
|
ArraySetAsSeries(cc_hist,true);
|
|
|
|
//--- Получаем исторические данные цен и индикаторов
|
|
if(!GetPriceData(0,bars_total,rates_hist))
|
|
return false;
|
|
if(!GetATRData(0,bars_total,atr_hist))
|
|
return false;
|
|
if(!GetZZData(0,0,bars_total,zz0_hist))
|
|
return false;
|
|
if(!GetZZData(1,0,bars_total,zz1_hist))
|
|
return false;
|
|
if(!GetZZData(2,0,bars_total,zz2_hist))
|
|
return false;
|
|
if(!GetCandleCodeData(0,bars_total,cc_hist))
|
|
return false;
|
|
|
|
bool is_first_found=false; // Флаг для пропуска текущего излома ZZ
|
|
|
|
//--- Проходим по всей истории
|
|
for(int i=0; i<bars_total-limit-pattern_size; i++)
|
|
{
|
|
//--- Если в основном буфере ZZ пусто - это точно не излом
|
|
if(zz0_hist[i] == 0 || zz0_hist[i] == EMPTY_VALUE)
|
|
continue;
|
|
|
|
int current_type=-1;
|
|
|
|
//--- Оределяем тип
|
|
//--- Если значение в основном буфере ZZ пришло из буфера вершин (1)
|
|
if(zz1_hist[i] != 0 && zz1_hist[i] != EMPTY_VALUE && MathAbs(zz1_hist[i] - zz0_hist[i]) < _Point)
|
|
current_type=POSITION_TYPE_SELL;
|
|
|
|
//--- Если значение в основном буфере ZZ пришло из буфера впадин (2)
|
|
else if(zz2_hist[i] != 0 && zz2_hist[i] != EMPTY_VALUE && MathAbs(zz2_hist[i] - zz0_hist[i]) < _Point)
|
|
current_type=POSITION_TYPE_BUY;
|
|
|
|
//--- Тип не определён - идём далее
|
|
if(current_type==-1)
|
|
continue;
|
|
|
|
//--- Если это самое первое не пустое значение ZZ,
|
|
//--- пропускаем его, так как это перерисовывающаяся крайняя "нога"
|
|
if(!is_first_found)
|
|
{
|
|
is_first_found=true;
|
|
continue;
|
|
}
|
|
|
|
//--- Инициализируем новый паттерн
|
|
SPattern pattern;
|
|
if(!pattern.SetSteps(pattern_size))
|
|
return false;
|
|
|
|
//--- Устанавливаем базовые данные паттерна
|
|
pattern.type=(ENUM_POSITION_TYPE)current_type;
|
|
pattern.time=rates_hist[i].time;
|
|
|
|
//--- Подготавливаем базовые точки для расчёта относительной позиции
|
|
double open_base=rates_hist[i].open;
|
|
double atr_base=atr_hist[i];
|
|
if(atr_base==0)
|
|
continue;
|
|
|
|
//--- Собираем данные по каждой свече, входящей в паттерн
|
|
for(int j=0; j<pattern_size; j++)
|
|
{
|
|
int idx=i+j;
|
|
//--- Сохраняем код Лиховидова и время бара
|
|
pattern.steps[j].code=(int)cc_hist[idx];
|
|
pattern.steps[j].time=rates_hist[idx].time;
|
|
//--- Сохраняем цены OHLC текущей свечи шага
|
|
pattern.steps[j].open=rates_hist[idx].open;
|
|
pattern.steps[j].high=rates_hist[idx].high;
|
|
pattern.steps[j].low=rates_hist[idx].low;
|
|
pattern.steps[j].close=rates_hist[idx].close;
|
|
//--- Сохраняем данные базовой точки
|
|
pattern.steps[j].open_base=open_base;
|
|
pattern.steps[j].time_base=rates_hist[i].time;
|
|
pattern.steps[j].rel_pos=(rates_hist[idx].open-open_base)/atr_base;
|
|
}
|
|
|
|
//--- Увеличиваем размер массива паттернов
|
|
int size=ArraySize(array);
|
|
if(ArrayResize(array,size+1)!=size+1)
|
|
return false;
|
|
//--- Записываем паттерн в массив
|
|
array[size]=pattern;
|
|
}
|
|
|
|
//--- Всё успешно
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Декомпозиция кода Лиховидова в унифицированную структуру |
|
|
//+------------------------------------------------------------------+
|
|
void DecomposeCode(const int code,SCandleParts &code_struct)
|
|
{
|
|
//--- Определение направления свечи (64-127 бычья, 0-63 медвежья)
|
|
bool bullish=(code>=64);
|
|
code_struct.direction=(bullish ? POSITION_TYPE_BUY : POSITION_TYPE_SELL);
|
|
|
|
//--- Извлечение геометрических составляющих (градации 0-3)
|
|
int temp=code-(bullish ? 64 : 0);
|
|
int raw_body=temp/16; // Тело
|
|
int raw_us=(temp%16)/4; // Верхняя тень
|
|
int raw_ls=temp%4; // Нижняя тень
|
|
|
|
//--- Приведение к единому формату (0 - минимум, 3 - максимум)
|
|
//--- Согласно спецификации индикатора, инверсия шкал различается для каждого элемента
|
|
|
|
//--- Тело: инвертировано только у медвежьих свечей (0 - максимум)
|
|
code_struct.body=(bullish ? raw_body : 3-raw_body);
|
|
|
|
//--- Верхняя тень: в исходном коде всегда имеет прямую шкалу (0 - минимум)
|
|
code_struct.us=raw_us;
|
|
|
|
//--- Нижняя тень: в исходном коде всегда имеет обратную шкалу (3 - минимум)
|
|
code_struct.ls=3-raw_ls;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Сравнивает две свечи по форме и расположению |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateStepSimilarity(const SPatternStep &base_step, // Данные эталонной свечи из истории
|
|
const SPatternStep &curr_step, // Данные текущей свечи с рынка
|
|
const double min_candle_sim, // Минимально допустимый процент сходства формы
|
|
const double min_pos_sim, // Минимально допустимый процент сходства позиции
|
|
const int max_grade_diff) // Максимально допустимая разница по градациям тела/теней
|
|
{
|
|
//--- Разбираем оба кода на физические параметры свечей
|
|
SCandleParts b,c;
|
|
DecomposeCode(base_step.code,b);
|
|
DecomposeCode(curr_step.code,c);
|
|
|
|
//--- Сравниваем цвет. Если направление не совпало - свечи разные, возвращаем 0
|
|
if(b.direction!=c.direction)
|
|
return 0.0;
|
|
|
|
//--- Настраиваемый фильтр: если различие по телу или теням больше max_grade_diff - свечи считаются непохожими
|
|
if(fabs(b.body-c.body)>max_grade_diff || fabs(b.us-c.us)>max_grade_diff || fabs(b.ls-c.ls)>max_grade_diff)
|
|
return 0.0;
|
|
|
|
//--- Вычисляем, насколько сильно различаются размеры тел и теней обеих свечей
|
|
int diff_total=fabs(b.body-c.body)+fabs(b.us-c.us)+fabs(b.ls-c.ls);
|
|
double s_candle=100.0*(1.0-(double)diff_total/9.0);
|
|
|
|
//--- Вычисляем, насколько сильно отличаются вертикальные позиции свечей относительно базовой свечи
|
|
double diff_pos=fabs(base_step.rel_pos-curr_step.rel_pos);
|
|
double s_pos=fmax(0.0,100.0*(1.0-diff_pos/MAX_POS_DIST));
|
|
|
|
//--- Возвращаем взвешенную оценку сходства: баланс между формой свечи (50%) и её положением в ATR (50%)
|
|
return(s_candle*0.5+s_pos*0.5);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает паттерн, сформированный на текущих рыночных данных |
|
|
//+------------------------------------------------------------------+
|
|
bool GetCurrentPattern(SPattern &pattern,const int start_index,const int depth)
|
|
{
|
|
//--- Устанавливаем размер (глубину) данных паттерна
|
|
if(!pattern.SetSteps(depth))
|
|
return false;
|
|
|
|
//--- Получаем базовые данные (цену и волатильность) для точки отсчёта
|
|
MqlRates r_base[];
|
|
double atr_val[];
|
|
if(!GetPriceData(start_index,1,r_base))
|
|
return false;
|
|
if(!GetATRData(start_index,1,atr_val))
|
|
return false;
|
|
|
|
//--- Сохраняем значения для расчёта относительного расположения свечей в паттерне
|
|
double open_base=r_base[0].open;
|
|
double atr_base=atr_val[0];
|
|
|
|
//--- Проверка на нулевую волатильность
|
|
if(atr_base==0)
|
|
return false;
|
|
|
|
//--- Собираем данные по каждой свече паттерна
|
|
for(int j=0; j<depth; j++)
|
|
{
|
|
int idx=start_index+j;
|
|
MqlRates rates[];
|
|
double ccode[];
|
|
|
|
//--- Запрашиваем цену и CandleCode для каждого бара паттерна
|
|
if(!GetPriceData(idx,1,rates))
|
|
return false;
|
|
if(!GetCandleCodeData(idx,1,ccode))
|
|
return false;
|
|
|
|
//--- Записываем свойства свечи в структуру
|
|
pattern.steps[j].code=(int)ccode[0];
|
|
pattern.steps[j].time=rates[0].time;
|
|
|
|
//--- Сохраняем цены OHLC текущей свечи
|
|
pattern.steps[j].open=rates[0].open;
|
|
pattern.steps[j].high=rates[0].high;
|
|
pattern.steps[j].low=rates[0].low;
|
|
pattern.steps[j].close=rates[0].close;
|
|
|
|
//--- Вычисляем положение относительно базовой точки в единицах ATR
|
|
pattern.steps[j].rel_pos=(rates[0].open-open_base)/atr_base;
|
|
|
|
//--- Записываем данные базовой точки
|
|
pattern.steps[j].open_base=open_base;
|
|
pattern.steps[j].time_base=r_base[0].time;
|
|
}
|
|
|
|
//--- Паттерн успешно получен
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Сравнивает два паттерна и возвращает процент сходства |
|
|
//+------------------------------------------------------------------+
|
|
double ComparePatterns(const SPattern &base,const SPattern &curr,const double min_candle_sim,const double min_pos_sim,const int max_grade_diff)
|
|
{
|
|
double summ=0;
|
|
int depth=ArraySize(base.steps);
|
|
|
|
//--- Проверка на соответствие размеров
|
|
if(depth!=ArraySize(curr.steps) || depth==0)
|
|
return 0.0;
|
|
|
|
//--- Сравнение каждой свечи в паттерне
|
|
for(int i=0; i<depth; i++)
|
|
{
|
|
//--- Сравниваем каждую последующую свечу исторического паттерна со свечой текущего
|
|
double res=CalculateStepSimilarity(base.steps[i],curr.steps[i],min_candle_sim,min_pos_sim,max_grade_diff);
|
|
|
|
//--- Если хотя бы одна свеча не прошла локальные фильтры - паттерны не похожи (очень жёсткий фильтр)
|
|
// if(res==0)
|
|
// return 0.0;
|
|
|
|
//--- Накапливаем общую сумму процентов сходства всех свечей в паттерне
|
|
summ+=res;
|
|
}
|
|
|
|
//--- Возвращаем среднее арифметическое сходство всей фигуры
|
|
return(summ/depth);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Возвращает текстовое описание паттерна |
|
|
//+------------------------------------------------------------------+
|
|
string PatternDescription(const SPattern &pattern)
|
|
{
|
|
int depth=ArraySize(pattern.steps);
|
|
if(depth==0)
|
|
return "Empty pattern";
|
|
|
|
//--- Получаем точность котировок текущего символа
|
|
int dg=_Digits;
|
|
|
|
string desc=StringFormat("Pattern %s (%s):\n",
|
|
(pattern.type==POSITION_TYPE_BUY ? "BUY" : "SELL"),
|
|
TimeToString(pattern.time));
|
|
|
|
//--- Собираем данные по каждому бару паттерна
|
|
for(int i=0; i<depth; i++)
|
|
{
|
|
SCandleParts p;
|
|
//--- Разбираем оригинальный код Лиховидова
|
|
DecomposeCode(pattern.steps[i].code,p);
|
|
|
|
//--- Формируем строку описания свойств текущего в цикле бара
|
|
desc+=StringFormat("Bar %d: %5s [O:%.*f H:%.*f L:%.*f C:%.*f] [Code:%3d] [B:%d US:%d LS:%d] Pos:%5.2f ATR\n",
|
|
i,
|
|
(p.direction==POSITION_TYPE_BUY ? "Bull" : "Bear"),
|
|
dg,pattern.steps[i].open,
|
|
dg,pattern.steps[i].high,
|
|
dg,pattern.steps[i].low,
|
|
dg,pattern.steps[i].close,
|
|
pattern.steps[i].code,
|
|
p.body,
|
|
p.us,
|
|
p.ls,
|
|
pattern.steps[i].rel_pos);
|
|
}
|
|
|
|
//--- Возвращаем созданное описание
|
|
return desc;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Выводит описание паттерна в журнал |
|
|
//+------------------------------------------------------------------+
|
|
void PatternPrint(const SPattern &pattern)
|
|
{
|
|
Print(PatternDescription(pattern));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Создаёт графическую метку на указанном графике |
|
|
//+------------------------------------------------------------------+
|
|
bool CreateMarker(const long chart_id,const datetime time,const double price,const ENUM_POSITION_TYPE type)
|
|
{
|
|
string name=prefix+"_ZZ_Marker";
|
|
|
|
//--- Если объекта нет - создаём его
|
|
if(ObjectFind(chart_id,name)<0)
|
|
{
|
|
if(!ObjectCreate(chart_id,name,OBJ_ARROW,0,time,price))
|
|
return false;
|
|
}
|
|
|
|
//--- Обновляем свойства объекта (позицию и визуализацию)
|
|
ObjectSetDouble( chart_id,name,OBJPROP_PRICE,price);
|
|
ObjectSetInteger(chart_id,name,OBJPROP_TIME,time);
|
|
ObjectSetInteger(chart_id,name,OBJPROP_ARROWCODE,82);
|
|
ObjectSetInteger(chart_id,name,OBJPROP_WIDTH,1);
|
|
ObjectSetInteger(chart_id,name,OBJPROP_COLOR, (type==POSITION_TYPE_SELL ? clrRed : clrDodgerBlue));
|
|
ObjectSetInteger(chart_id,name,OBJPROP_ANCHOR,(type==POSITION_TYPE_SELL ? ANCHOR_BOTTOM : ANCHOR_TOP));
|
|
ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
|
|
|
|
//--- Всё успешно
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Создает объекты текстового блока статистики |
|
|
//+------------------------------------------------------------------+
|
|
bool CreateStatisticsObjects(const long chart_id, // Идентификатор графика
|
|
const int count, // Количество строк (объектов)
|
|
const int x, // Координата X
|
|
const int y, // Координата Y первой строки
|
|
const int step, // Шаг по вертикали
|
|
const bool use_back) // Флаг использования подложки
|
|
{
|
|
//--- Проверка валидности идентификатора графика
|
|
if(chart_id<=WRONG_VALUE)
|
|
return false;
|
|
|
|
string back_name=prefix+"_Stats_Back";
|
|
//---Если нужна подложка (фон)
|
|
if(use_back)
|
|
{
|
|
if(ObjectFind(chart_id,back_name)<0)
|
|
{
|
|
if(!ObjectCreate(chart_id,back_name,OBJ_RECTANGLE_LABEL,0,0,0))
|
|
{
|
|
PrintFormat("%s: Error. ObjectCreate(%s,OBJ_RECTANGLE_LABEL) failed",__FUNCTION__,back_name);
|
|
return false;
|
|
}
|
|
|
|
//--- Устанавливаем свойства созданного объекта
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_BGCOLOR,clrBisque);
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_BORDER_TYPE,BORDER_FLAT);
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_SELECTABLE,false);
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_HIDDEN,true);
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_BACK,false);
|
|
}
|
|
//--- Обновляем размеры и позицию
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_XDISTANCE,x-5);
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_YDISTANCE,y-5);
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_XSIZE,180);
|
|
ObjectSetInteger(chart_id,back_name,OBJPROP_YSIZE,count*step+10);
|
|
}
|
|
else
|
|
{
|
|
//--- Если подложка не нужна, но объект существует - удаляем его
|
|
if(ObjectFind(chart_id,back_name)>=0)
|
|
ObjectDelete(chart_id,back_name);
|
|
}
|
|
|
|
//--- Цикл создания объектов строк
|
|
for(int i=0; i<count; i++)
|
|
{
|
|
string obj_name=StringFormat("%s_Stats_Line_%d",prefix,i);
|
|
if(ObjectFind(chart_id,obj_name)<0)
|
|
{
|
|
if(!ObjectCreate(chart_id,obj_name,OBJ_LABEL,0,0,0))
|
|
{
|
|
PrintFormat("%s: Error. ObjectCreate(%s,OBJ_LABEL) failed",__FUNCTION__,obj_name);
|
|
return false;
|
|
}
|
|
|
|
ObjectSetInteger(chart_id,obj_name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(chart_id,obj_name,OBJPROP_FONTSIZE,8);
|
|
ObjectSetString( chart_id,obj_name,OBJPROP_FONT,"Calibri");
|
|
ObjectSetInteger(chart_id,obj_name,OBJPROP_SELECTABLE,false);
|
|
ObjectSetInteger(chart_id,obj_name,OBJPROP_HIDDEN,true);
|
|
ObjectSetInteger(chart_id,obj_name,OBJPROP_BACK,false);
|
|
}
|
|
|
|
ObjectSetInteger(chart_id,obj_name,OBJPROP_XDISTANCE,x);
|
|
ObjectSetInteger(chart_id,obj_name,OBJPROP_YDISTANCE,y+(i*step));
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Выводит статистику на график и дублирует в журнал |
|
|
//+------------------------------------------------------------------+
|
|
void DisplayStatistics(const long chart_id, // Идентификатор графика
|
|
const string dominant_signal, // Текст доминирующего сигнала
|
|
const int count_buy, // Количество совпадений BUY
|
|
const int count_sell, // Количество совпадений SELL
|
|
const double avg_buy, // Средний процент сходства BUY
|
|
const double avg_sell, // Средний процент сходства SELL
|
|
const SPattern &pattern, // Структура лучшего паттерна
|
|
const int best_idx, // Индекс лучшего совпадения
|
|
const int total_patterns, // Общее количество паттернов в базе
|
|
const int x, // Координата X текстового блока
|
|
const int y, // Координата Y первой строки
|
|
const int step, // Шаг между строками
|
|
const bool use_back) // Флаг использования подложки
|
|
{
|
|
//--- Проверка валидности идентификатора графика
|
|
if(chart_id<=WRONG_VALUE)
|
|
return;
|
|
|
|
//--- Формируем единую многострочную строку статистики
|
|
string stats_text=StringFormat("====== PATTERN STATISTICS ======\n"+
|
|
"Symbol: %s, Timeframe: %s\n"+
|
|
"Snapshot Time: %s\n"+
|
|
"Total Patterns in Base: %d\n"+
|
|
"Best Match Index: %d\n"+
|
|
"Pattern Time: %s\n"+
|
|
"===============================\n"+
|
|
"Dominant: %s\n"+
|
|
"Total Matches: %d (BUY: %d, SELL: %d)\n"+
|
|
"Average Similarity BUY: %.2f%%\n"+
|
|
"Average Similarity SELL: %.2f%%",
|
|
_Symbol,
|
|
StringSubstr(EnumToString(_Period),7),
|
|
TimeToString(TimeCurrent()),
|
|
total_patterns,
|
|
best_idx,
|
|
TimeToString(pattern.time),
|
|
dominant_signal,
|
|
count_buy+count_sell,
|
|
count_buy,
|
|
count_sell,
|
|
avg_buy,
|
|
avg_sell);
|
|
|
|
//--- Разбиваем строку на массив подстрок по разделителю \n
|
|
string lines[];
|
|
ushort sep=(ushort)'\n';
|
|
int total_lines=StringSplit(stats_text,sep,lines);
|
|
|
|
//--- Создаем или обновляем объекты блока статистики. При ошибке - выходим
|
|
if(!CreateStatisticsObjects(chart_id,total_lines,x,y,step,use_back))
|
|
return;
|
|
|
|
//--- Выводим текст в созданные объекты
|
|
for(int i=0; i<total_lines; i++)
|
|
{
|
|
string name=prefix+StringFormat("_Stats_Line_%d",i);
|
|
ObjectSetString(chart_id,name,OBJPROP_TEXT,lines[i]);
|
|
|
|
//--- Определяем цвет строки в зависимости от содержимого
|
|
color clr=(i==0 || i==6 ? clrGray :
|
|
StringFind(lines[i],"BUY") >=0 ? clrBlue :
|
|
StringFind(lines[i],"SELL")>=0 ? clrDarkRed : clrDarkSlateGray);
|
|
ObjectSetInteger(chart_id,name,OBJPROP_COLOR,clr);
|
|
}
|
|
|
|
//--- Дублируем информацию в основной журнал и перерисовываем указанный график
|
|
Print(stats_text);
|
|
ChartRedraw(chart_id);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Выводит окно чарта с центровкой на историческом паттерне |
|
|
//+------------------------------------------------------------------+
|
|
long ShowPatternWindow(string name,int x,int y,int width,int height,const SPattern &pattern)
|
|
{
|
|
//--- Вычисляем актуальный индекс бара по времени из структуры
|
|
int idx=iBarShift(_Symbol,_Period,pattern.time);
|
|
//--- Если бар не найден в доступной истории - выходим
|
|
if(idx==WRONG_VALUE)
|
|
{
|
|
PrintFormat("%s: Bar with time %s not found in available history",__FUNCTION__,TimeToString(pattern.time));
|
|
return WRONG_VALUE;
|
|
}
|
|
|
|
//--- Проверяем наличие объекта окна, создаём, если его нет
|
|
if(ObjectFind(0,name)<0)
|
|
{
|
|
if(!ObjectCreate(ChartID(),name,OBJ_CHART,0,0,0))
|
|
{
|
|
PrintFormat("%s: ObjectCreate(\"%s\",OBJ_CHART) failed",__FUNCTION__,name);
|
|
return WRONG_VALUE;
|
|
}
|
|
|
|
//--- Устанавливаем свойства окна объекта-графика
|
|
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
|
|
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
|
|
ObjectSetInteger(0,name,OBJPROP_XSIZE,width);
|
|
ObjectSetInteger(0,name,OBJPROP_YSIZE,height);
|
|
}
|
|
|
|
//--- Управление объектом графиком через его идентификатор
|
|
long id=ObjectGetInteger(ChartID(),name,OBJPROP_CHART_ID);
|
|
if(id>WRONG_VALUE)
|
|
{
|
|
//--- Синхронизируем символ и период графика
|
|
if(ChartSymbol(id)!=_Symbol || ChartPeriod(id)!=_Period)
|
|
ChartSetSymbolPeriod(id,_Symbol,_Period);
|
|
|
|
//--- Свойства графика объекта
|
|
ChartSetInteger(id,CHART_AUTOSCROLL,false);
|
|
ChartSetInteger(id,CHART_SHIFT,false);
|
|
ChartSetInteger(id,CHART_SCALE,ChartGetInteger(ChartID(),CHART_SCALE));
|
|
|
|
//--- Устанавливаем графическую метку на изломе ZZ
|
|
double price=(pattern.type==POSITION_TYPE_SELL) ? pattern.steps[0].high : pattern.steps[0].low;
|
|
CreateMarker(id,pattern.time,price,pattern.type);
|
|
|
|
//--- Получаем количество видимых баров для позиции метки излома ZZ
|
|
int visible_bars=(int)ChartGetInteger(id,CHART_VISIBLE_BARS);
|
|
//--- Если статистика выводится на подложку, то позиция излома ZZ находится во второй трети окна
|
|
int center=visible_bars/(InpStatsBack ? 3 : 2);
|
|
|
|
//--- Рассчитываем итоговое смещение для центровки излома ZZ относительно конца истории
|
|
int offset=idx-center;
|
|
if(offset<0)
|
|
offset=0;
|
|
|
|
//--- Смещаем график от конца истории на рассчитанное смещение
|
|
ChartNavigate(id,CHART_END,-offset);
|
|
|
|
//--- Перерисовываем график объекта
|
|
ChartRedraw(id);
|
|
}
|
|
//--- Возвращаем идентификатор графика объекта
|
|
return id;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Создает или перемещает вертикальную линию начала анализа |
|
|
//+------------------------------------------------------------------+
|
|
void CreateAnalysisLine(const datetime time,const color clr)
|
|
{
|
|
//--- Формируем имя объекта
|
|
string name=prefix+"_AnalysisLine";
|
|
|
|
//--- Если линии нет - создаем её
|
|
if(ObjectFind(0,name)<0)
|
|
{
|
|
if(!ObjectCreate(0,name,OBJ_VLINE,0,time,0))
|
|
{
|
|
PrintFormat("%s: ObjectCreate(%s,OBJ_VLINE) failed",__FUNCTION__,name);
|
|
return;
|
|
}
|
|
//--- Устанавливаем свойства линии
|
|
ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_DOT);
|
|
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
|
|
ObjectSetInteger(0,name,OBJPROP_RAY,true);
|
|
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
|
|
ObjectSetInteger(0,name,OBJPROP_BACK,false);
|
|
}
|
|
|
|
//--- Обновляем положение, цвет и тултип
|
|
ObjectSetInteger(0,name,OBJPROP_TIME,time);
|
|
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
|
|
string tooltip=StringFormat("The beginning of the search for\nZZ patterns (%s)",TimeToString(time));
|
|
ObjectSetString(0,name,OBJPROP_TOOLTIP,tooltip);
|
|
|
|
//--- Перерисовываем основной график
|
|
ChartRedraw(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Формирует базу паттернов из истории |
|
|
//+------------------------------------------------------------------+
|
|
bool CreatePatternBase(const int attempts_limit, // Максимальное количество попыток сбора
|
|
const int pattern_depth, // Глубина паттерна в барах
|
|
const int hist_limit, // Ограничение глубины поиска по истории
|
|
int &attempts_cnt, // Счётчик выполненных попыток
|
|
SPattern &base_array[]) // Массив для хранения собранных паттернов
|
|
{
|
|
static bool warned=false; // было ли сообщение в журнал
|
|
|
|
//--- Если лимит попыток исчерпан - выходим
|
|
if(attempts_cnt>=attempts_limit)
|
|
{
|
|
if(!warned)
|
|
{
|
|
PrintFormat("%s: Error. Maximum attempts (%d) reached. Check data",__FUNCTION__,attempts_limit);
|
|
warned=true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--- Увеличиваем счётчик и пробуем собрать базу
|
|
attempts_cnt++;
|
|
PrintFormat("%s: Attempt %d to collect pattern base...",__FUNCTION__,attempts_cnt);
|
|
|
|
//--- Если данные не получены - очищаем массив и ждем следующей попытки
|
|
if(!CollectHistoryData(pattern_depth,hist_limit,base_array))
|
|
{
|
|
ArrayFree(base_array);
|
|
return false;
|
|
}
|
|
|
|
//--- База успешно сформирована
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Осуществляет поиск совпадений текущего рынка с базой паттернов |
|
|
//+------------------------------------------------------------------+
|
|
void FindMatches(const int pattern_depth, // Глубина паттерна в барах
|
|
const double total_sim, // Порог сходства паттерна (0-100%)
|
|
const SPattern&base_array[], // База исторических паттернов
|
|
SMatchResult &result, // Структура для записи результатов
|
|
const int start_idx, // Индекс бара для снятия слепка
|
|
const datetime max_time, // Время отсечки будущего
|
|
const double min_candle_sim, // Сходство свечей (0-100%)
|
|
const double min_pos_sim, // Сходство относительного расположения (0-100%)
|
|
const int max_grade_diff) // Максимальная разница по градациям
|
|
{
|
|
//--- Если передан пустой массив - выходим
|
|
int total=ArraySize(base_array);
|
|
if(total==0)
|
|
{
|
|
PrintFormat("%s: Error. Empty array passed",__FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
//--- Обнуляем структуру перед заполнением
|
|
ZeroMemory(result);
|
|
result.best_idx=WRONG_VALUE;
|
|
|
|
//--- Получаем текущий паттерн ("слепок" рынка) от указанного индекса
|
|
SPattern curr_pattern;
|
|
if(!GetCurrentPattern(curr_pattern,start_idx,pattern_depth))
|
|
return;
|
|
|
|
double max_res=0;
|
|
double sum_buy=0, sum_sell=0;
|
|
|
|
//--- Проходим по всей базе паттернов
|
|
for(int i=0; i<total; i++)
|
|
{
|
|
//--- Пропускаем паттерны, которые "еще в будущем" относительно точки анализа
|
|
if(base_array[i].time>=max_time)
|
|
continue;
|
|
|
|
//--- Сравниваем текущий паттерн с историческим образцом
|
|
double res=ComparePatterns(base_array[i],curr_pattern,min_candle_sim,min_pos_sim,max_grade_diff);
|
|
|
|
//--- Если сходство ниже порога - идём далее
|
|
if(res<total_sim)
|
|
continue;
|
|
|
|
//--- Классифицируем совпадение по типу эталона
|
|
//--- Бычий исторический паттерн
|
|
if(base_array[i].type==POSITION_TYPE_BUY)
|
|
{
|
|
sum_buy+=res;
|
|
result.cnt_buy++;
|
|
}
|
|
//--- Медвежий исторический паттерн
|
|
else if(base_array[i].type==POSITION_TYPE_SELL)
|
|
{
|
|
sum_sell+=res;
|
|
result.cnt_sell++;
|
|
}
|
|
|
|
//--- Фиксируем лучший результат
|
|
if(res>max_res)
|
|
{
|
|
max_res=res;
|
|
result.best_idx=i;
|
|
}
|
|
}
|
|
|
|
//--- Рассчитываем итоговую статистику по найденным совпадениям
|
|
int total_matches=result.cnt_buy+result.cnt_sell;
|
|
if(total_matches>0)
|
|
{
|
|
result.avg_buy =(result.cnt_buy > 0 ? sum_buy /result.cnt_buy : 0);
|
|
result.avg_sell=(result.cnt_sell> 0 ? sum_sell/result.cnt_sell : 0);
|
|
|
|
//--- Формируем текст доминирующего сигнала
|
|
result.signal=(result.cnt_buy>result.cnt_sell) ? "PREDICT BUY" : (result.cnt_sell>result.cnt_buy) ? "PREDICT SELL" : "UNCERTAIN";
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Выполняет поиск совпадений и выводит результаты визуально |
|
|
//+------------------------------------------------------------------+
|
|
void FindAndDisplayMatches(const int pattern_depth, // Глубина паттерна в барах
|
|
const double total_sim, // Порог сходства паттерна (0-100%)
|
|
const double min_candle_sim, // Сходство свечей (0-100%)
|
|
const double min_pos_sim, // Минимально допустимый процент сходства позиции
|
|
const int max_grade_diff, // Максимальная разница по градациям
|
|
const SPattern &base_array[], // База исторических паттернов
|
|
SMatchResult &match_res, // Структура для записи результатов поиска
|
|
const string wnd_name, // Имя вложенного окна чарта
|
|
const int wnd_x, // Координата X окна паттерна
|
|
const int wnd_y, // Координата Y окна паттерна
|
|
const int wnd_w, // Ширина окна паттерна
|
|
const int wnd_h, // Высота окна паттерна
|
|
const int stats_x, // Координата X блока статистики
|
|
const int stats_y, // Координата Y первой строки
|
|
const int stats_step, // Вертикальный шаг между строками
|
|
const bool use_back, // Флаг использования подложки
|
|
const int start_idx, // Индекс бара начала поиска
|
|
const color line_clr) // Цвет линии анализа (ДОБАВЛЕНО)
|
|
{
|
|
//--- Определяем время выбранного бара для фильтрации паттернов справа от времени бара
|
|
ResetLastError();
|
|
datetime max_time=iTime(_Symbol,_Period,start_idx);
|
|
int err=GetLastError();
|
|
if(max_time==0 && err!=0)
|
|
{
|
|
PrintFormat("%s: iTime(%s,%s,%d) failed. Error %d",
|
|
__FUNCTION__,_Symbol,StringSubstr(EnumToString(_Period),7),start_idx,err);
|
|
return;
|
|
}
|
|
|
|
//--- Выполняем поиск совпадений с учетом временной отсечки
|
|
FindMatches(pattern_depth,total_sim,base_array,match_res,start_idx,max_time,min_candle_sim,min_pos_sim,max_grade_diff);
|
|
|
|
//--- Рисуем вертикальную линию начала анализа на основном графике
|
|
CreateAnalysisLine(max_time,line_clr);
|
|
|
|
//--- Если найдено подходящее совпадение - визуализируем его
|
|
if(match_res.best_idx>WRONG_VALUE)
|
|
{
|
|
//--- Создаем/обновляем вложенное окно чарта
|
|
long id=ShowPatternWindow(prefix+wnd_name,wnd_x,wnd_y,wnd_w,wnd_h,base_array[match_res.best_idx]);
|
|
|
|
//--- Если идентификатор окна получен - выводим в него блок статистики
|
|
if(id>WRONG_VALUE)
|
|
DisplayStatistics(id,match_res.signal,
|
|
match_res.cnt_buy,
|
|
match_res.cnt_sell,
|
|
match_res.avg_buy,
|
|
match_res.avg_sell,
|
|
base_array[match_res.best_idx],
|
|
match_res.best_idx,
|
|
base_array.Size(),
|
|
stats_x,
|
|
stats_y,
|
|
stats_step,
|
|
use_back);
|
|
}
|
|
//--- Если совпадений нет - выведем в лог сообщение
|
|
else
|
|
PrintFormat("%s: No matches found for the configuration at %s",__FUNCTION__,TimeToString(max_time));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Обрабатывает выбор бара на графике для ручного поиска |
|
|
//+------------------------------------------------------------------+
|
|
void ChartClickHandler(const long lparam,const double dparam,int &start_idx)
|
|
{
|
|
//--- Проверяем, зажата ли клавиша Ctrl в момент клика
|
|
if(TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)<0)
|
|
{
|
|
datetime click_time=0;
|
|
double click_price=0;
|
|
int sub_window=0;
|
|
|
|
//--- Преобразуем координаты клика во время и цену
|
|
ResetLastError();
|
|
if(!ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sub_window,click_time,click_price))
|
|
{
|
|
PrintFormat("%s: ChartXYToTimePrice() failed. Error %d",__FUNCTION__,GetLastError());
|
|
return;
|
|
}
|
|
//--- Находим индекс бара по времени клика
|
|
int bar_idx=iBarShift(_Symbol,_Period,click_time);
|
|
if(bar_idx==WRONG_VALUE)
|
|
{
|
|
PrintFormat("%s: Error. iBarShift() failed for time %s",__FUNCTION__,TimeToString(click_time));
|
|
return;
|
|
}
|
|
//--- Записываем новый индекс в переменную, переданную по ссылке
|
|
start_idx=bar_idx;
|
|
|
|
//--- Отобразим в журнале событие
|
|
PrintFormat("%s: Manual mode. Analysis bar changed to: %d (%s)",
|
|
__FUNCTION__,start_idx,TimeToString(click_time));
|
|
|
|
//--- Запускаем поиск и визуализацию от выбранного бара на графике
|
|
FindAndDisplayMatches(ExtPatternDepth,ExtTotalSim,ExtMinCandleSim,ExtMinPosSim,ExtMaxGradeDiff,patterns,match_result,prefix+"BestMatchWindow",
|
|
InpWndX,InpWndY,InpWndW,InpWndH,InpStatsX,InpStatsY,InpStatsStep,InpStatsBack,start_idx,InpAnalysColor);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
|