Article-21319-MQL5-CandleCo.../ExpCodePatternFinder.mq5

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);
}
}
//+------------------------------------------------------------------+