
Теория графов: Алгоритм Дейкстры в трейдинге
Введение
В этой статье мы рассмотрим реализацию алгоритма Дейкстры — фундаментальной концепции теории графов, известной своей эффективностью при решении задач нахождения кратчайшего пути. Этот традиционно применяемый в маршрутизации и оптимизации сетей алгоритм мы переделаем для финансовых рынков, моделируя движение цен в виде взвешенного графа. Здесь узлы представляют уровни цен или временные интервалы, а ребра отражают стоимость (или вероятность) перехода между ними.
Мы оценим применимость метода Дейкстры для прогнозирования следующего вероятного массива данных о цене, эффективно определяющего «кратчайший путь», по которому цена может пройти от своей текущей позиции до будущего значения. Рассматривая динамику рынка как граф, мы будем определять наиболее вероятную траекторию, чтобы оптимизировать торговые решения на основе минимального сопротивления или издержек.
Теория графов предоставляет мощную основу для анализа сложных рыночных структур, а алгоритм Дейкстры предлагает систематический способ навигации по ним. Интерпретируя движения цен как ребра с весами, такими как волатильность, мы можем вычислить оптимальный путь, минимизирующий риск или максимизирующий эффективность.
Прогнозируемый ценовой массив по сути действует как кратчайшее расстояние от текущей цены до будущих уровней, предлагая трейдерам метод прогнозирования тенденций на основе данных. Данный подход объединяет алгоритмическую торговлю и вычислительную математику, демонстрируя, как классические графовые алгоритмы могут выявлять скрытые возможности в финансовых временных рядах данных.
Фундаментальный подход Дейкстры
Терминология | Наша трактовка: |
---|---|
Граф — набор узлов и ребер. | Граф — структура графика, образованная максимумами и минимумами колебаний. Каждая точка колебания становится узлом, а каждый ценовой путь между ними становится ребром. |
Вес — стоимость перемещения от одного узла до другого. | Вес — стоимость (или усилие), необходимые для перемещения цены между двумя точками колебания. Это может быть абсолютное ценовое расстояние. |
Исходный узел — начальная точка алгоритма. | Исходный узел — самая последняя действительная точка колебания (последний максимум/минимум, через который цена не совершила пробой). Это наша отправная точка для вычисления кратчайших путей. |
Посещенный набор — узлы, обработку которых мы завершили. | Посещенный набор — все точки колебания, которые алгоритм уже оценил и не будет пересматривать. В терминах трейдинга это точки колебания, уже пробитые ценой или в направлении которых ведется торговля. |
Таблица расстояний — отслеживает кратчайшее расстояние до каждого узла. | Таблица расстояний — картирование каждого узла с его кратчайшим значением «стоимости достижения» от исходного узла. В трейдинге она сообщает, насколько дёшево (или легко) цена может перейти от текущей точки к любой другой точке колебания. |
Пошаговый процесс:
1. Инициализация:
- Установим расстояние до исходного узла равным 0.
- Установим расстояние до всех других узлов равным бесконечности.
- Создадим очередь приоритетов (или min-heap (минимальная куча), чтобы всегда выбирать узел с наименьшим известным расстоянием.
2. Посетим ближайший непосещенный узел:
- Начнем с исходного узла.
- Для каждого соседа вычислим:
new_distance = distance_to_current + edge_weight
Если значение `new_distance` меньше ранее известного расстояния, обновим его.
3. Отметим текущий узел как посещенный:
- После его посещения мы к нему больше не возвращаемся.
4. Повтор:
- Продолжим посещать следующий ближайший непосещенный узел.
- Повторяем, пока не будут посещены все узлы или не будет найден кратчайший путь.
пока остаются непосещенные узлы: выберем узел с наименьшим предполагаемым расстоянием для каждого соседа: если новый путь к соседу короче: обновим кратчайшее расстояние отметим текущий узел как посещенный
Начинаем
//+------------------------------------------------------------------+ //| Dijkstars Algo.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict #include <Trade/Trade.mqh> CTrade trade;
Начнем с включения файла `trade.mqh`, который предоставляет нам доступ к встроенным торговым функциям MQL5 через класс `CTrade`. Этот класс предоставляет методы для программного размещения, изменения и закрытия сделок. После включения файла создаем экземпляр `CTrade` с именем `trade`, который мы будем использовать в советнике для отправки торговых команд, таких как `Buy()`, `Sell()` и `PositionOpen()`. Эта конфигурация необходима для автоматизации исполнения ордеров в советнике.
// Input Parameters input int TakeProfit = 1000; input int StopLoss = 385; input double In_Lot = 0.01; input int LeftBars = 3; input int RightBars = 3; input int MaxSwings = 50; input double Lots = 0.1; input double PointBuffer = 10; input int Slippage = 5; ENUM_TIMEFRAMES TimeFrame;
В этом разделе мы определяем входные параметры советника, позволяя трейдеру настраивать ключевые параметры непосредственно из интерфейса советника. Параметры «TakeProfit» и «StopLoss» задают цель и риск в пунктах, а «In_Lot» и «Lots» определяют размер сделки. Параметры «LeftBars» и «RighBars» используются для определения максимумов и минимумов колебаний путем сравнения баров с соседями. «MaxSwings» ограничивает количество отслеживаемых точек колебаний, а «pointBuffer» добавляет дополнительное расстояние к SL/TP для обеспечения безопасности. «Slippage» задаёт максимально допустимое отклонение цены при исполнении ордера, а «TimeFrame» — указывает период графика, который будет анализировать советник.
// Node Structure struct SwingPoint { int index; datetime time; double price; bool isHigh; bool visited; double distance; bool used; int previous; };
Эта структура определяет модель для SwingPoint, которая представляет собой узел в основанной на алгоритме Дейкстры торговой системе. Каждая точка колебания содержит важную информацию:
- Index — это номер бара, где обнаружено колебание.
- Time — это точная временная метка этого бара.
- Price — является наивысшим или наименьшим значением на данном колебании.
- IsHigh — указывает, является ли это максимумом колебания (true) или минимумом (false).
- Visited — помогает отслеживать, какие узлы были обработаны алгоритмом.
- Distance — сохраняет рассчитанную стоимость из исходного узла методом поиска пути по Дейкстре.
- Used — сигнализирует, что это колебание уже использовалось при принятии торговых решений.
- Previous — отслеживает предыдущий узел в цепочке кратчайшего пути.
SwingPoint swingPoints[]; //+------------------------------------------------------------------+ //| OnInit | //+------------------------------------------------------------------+ int OnInit() { Print("Dijkstra Swing EA initialized"); return INIT_SUCCEEDED; }
Здесь объявлен динамический массив `swingPoints[]` для хранения всех обнаруженных максимумов и минимумов колебаний (в виде структур `SwingPoint`) на графике. Этот массив будет заполнен и использован во всем советнике для представления узлов на графике ценового действия. В функции `OnInit()` советник просто выводит на терминал сообщение, подтверждающее, что «Dijkstra Swing EA» успешно инициализирован, и возвращает `INIT_SUCCEEDED`, сигнализируя о надлежащем запуске.
//+------------------------------------------------------------------+ //| Detect swing highs and lows | //+------------------------------------------------------------------+ void DetectSwings(int left, int right) { ArrayResize(swingPoints, 0); int totalBars = Bars(_Symbol, PERIOD_CURRENT) - right; for (int i = left; i < totalBars; i++) { bool isHigh = true, isLow = true; double high = High(i), low = Low(i); for (int j = 1; j <= left; j++) { if (High(i - j) >= high) isHigh = false; if (Low(i - j) <= low) isLow = false; } for (int j = 1; j <= right; j++) { if (High(i + j) >= high) isHigh = false; if (Low(i + j) <= low) isLow = false; } if (isHigh || isLow) { int idx = ArraySize(swingPoints); ArrayResize(swingPoints, idx + 1); swingPoints[idx].index = i; swingPoints[idx].time = Time(i); swingPoints[idx].price = isHigh ? high : low; swingPoints[idx].isHigh = isHigh; swingPoints[idx].visited = false; swingPoints[idx].distance = DBL_MAX; swingPoints[idx].previous = -1; if (idx >= MaxSwings) break; } } }
Эта функция `DetectSwings()` определяет максимумы и минимумы колебаний на ценовом графике, сравнивая каждую свечу с соседними барами. Она начинается с очистки существующего массива `SwingPoints` с помощью `ArrayResize`, обеспечивая новое обнаружение при каждом вызове. Функция проходит по каждому бару на графике, начиная с индекса `left` до `totalBars - right`, и проверяет, соответствует ли текущий бар (i) максимуму или минимуму колебания.
Чтобы определить, является ли бар максимумом колебания, проверяется, превышает ли его максимальная цена максимумы предыдущих «левых» баров и следующих «правых» баров. Аналогично, минимум колебания подтверждается, если его минимальная цена ниже, чем предыдущий и следующий соседние минимумы. Если хотя бы одно из условий выполняется, бар считается действительной точкой колебания. Такое локальное сравнение гарантирует, что только значительные пики и спады цен регистрируются как узлы колебаний.
При обнаружении колебания, оно сохраняется в массиве `SwingPoints[]` со всеми соответствующими данными: индексом, временем, ценой, является ли оно максимумом или минимумом, а также значениями по умолчанию для поиска пути (например, `visited`, `distance` и `previous`). Эта структура поддерживает последующий анализ с использованием алгоритма Дейкстры для оценки вероятностей путей между колебаниями. Цикл завершается преждевременно, если количество обнаруженных колебаний достигает значения `MaxSwings`, что позволяет избежать чрезмерного использования памяти или проблем с работой.
//+------------------------------------------------------------------+ //| Apply Dijkstra's algorithm | //+------------------------------------------------------------------+ void ApplyDijkstra() { if (ArraySize(swingPoints) == 0) return; swingPoints[0].distance = 0; for (int i = 0; i < ArraySize(swingPoints); i++) { int u = -1; double minDist = DBL_MAX; for (int j = 0; j < ArraySize(swingPoints); j++) { if (!swingPoints[j].visited && swingPoints[j].distance < minDist) { minDist = swingPoints[j].distance; u = j; } } if (u == -1) break; swingPoints[u].visited = true; for (int v = 0; v < ArraySize(swingPoints); v++) { if (!swingPoints[v].visited) { double cost = MathAbs(swingPoints[u].price - swingPoints[v].price); if (swingPoints[u].distance + cost < swingPoints[v].distance) { swingPoints[v].distance = swingPoints[u].distance + cost; swingPoints[v].previous = u; } } } } }
В этой функции мы реализуем алгоритм Дейкстры для вычисления кратчайшего пути от первой точки колебания до всех остальных точек колебания, рассматривая движение цены между максимумами и минимумами колебания как взвешенный граф. Сначала мы проверяем, существуют ли точки колебания. Если массив пуст, функция немедленно завершает работу. Затем она устанавливает «расстояние» первого узла (начального пивота) на «0», обозначая исходный узел в процессе поиска пути.
Алгоритм входит в цикл, где на каждой итерации он выбирает непосещенную точку колебания с наименьшим известным `расстоянием`. Этот узел `(u)` отмечается как посещенный и алгоритм оценивает всех его непосещенных соседей. Для каждого из этих соседей `(v)` вычисляется «стоимость» или вес для перехода от узла `u` к `v` на основе абсолютной разницы цен между ними. Если кумулятивная стоимость достижения `v` через `u` ниже текущего зарегистрированного расстояния, функция обновляет `расстояние` `v`и записывает `u` как свой `предыдущий` узел.
Этот процесс продолжается до тех пор, пока не будут посещены все доступные точки колебания, или пока не останется доступных непосещенных узлов. К концу функции каждая точка колебания содержит кратчайшую кумулятивную стоимость от источника и указатель на предыдущий узел вдоль этого оптимального пути. Эта информация позволяет советнику отслеживать наиболее эффективный путь через недавнюю структуру рынка и определять, какие точки колебания цена с наибольшей вероятностью посетит в следующий раз, формируя основу для генерации интеллектуальных торговых сигналов.
//+------------------------------------------------------------------+ //| Visualize Swing Points and Connections | //+------------------------------------------------------------------+ void VisualizeSwings() { for (int i = 0; i < ArraySize(swingPoints); i++) { string objName = "Swing_" + IntegerToString(i); ObjectDelete(0, objName); ObjectCreate(0, objName, OBJ_ARROW, 0, swingPoints[i].time, swingPoints[i].price); ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, swingPoints[i].isHigh ? 233 : 234); ObjectSetInteger(0, objName, OBJPROP_COLOR, swingPoints[i].isHigh ? clrRed : clrBlue); } for (int i = 1; i < ArraySize(swingPoints); i++) { int prev = swingPoints[i].previous; if (prev != -1) { string lineName = "Line_" + IntegerToString(i); ObjectDelete(0, lineName); ObjectCreate(0, lineName, OBJ_TREND, 0, swingPoints[prev].time, swingPoints[prev].price, swingPoints[i].time, swingPoints[i].price); ObjectSetInteger(0, lineName, OBJPROP_COLOR, clrGray); ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 1); } } }
Функция `VisualizeSwings()` отвечает за отображение обнаруженных точек колебания и связей между ними непосредственно на графе, помогая трейдерам визуально подтвердить используемые советником структуру и логику. В первом цикле `for` она выполняет перебор массива `swingPoints[]` и создает объекты-стрелки для каждого колебания. Перед созданием нового объекта она удаляет все существующие объекты с тем же именем, чтобы избежать беспорядка. Каждой стрелке присвоен определенный символ: красный для максимумов колебаний «код стрелки 233» и синий для минимумов колебаний «код стрелки 234», что позволяет визуально различать их.
Во втором цикле `for` функция рисует линии между каждой точкой колебания и соответствующим ей `предыдущим` узлом (определенным алгоритмом Дейкстры). Эти линии представляют собой кратчайшие пути соединения, используемые для оценки потенциальных путей торговли. Опять же, перед рисованием нового объекта любой существующий линейный объект с тем же именем удаляется. Линии созданы с использованием `OBJ_TREND` и нарисованы серым цветом со стандартной шириной, сохраняя чистую и ясную визуальную структуру.
Такая визуализация помогает проверить правильность принятия решений советником, позволяя увидеть, какие точки колебания определены, как они связаны и какой путь был выбран на основе алгоритма Дейкстры. Это особенно полезно во время тестирования на истории или реальной торговли, когда необходимо убедиться, что советник анализирует структуру рынка так, как задумано.
double High(int index){return (iHigh(_Symbol, _Period, index));} double Low(int index){return (iLow(_Symbol, _Period, index));} datetime Time(int index){return (iTime(_Symbol, _Period, index));}
Эти три вспомогательные функции, `High()`, `Low()` и `Time()`, представляют собой простые обертки вокруг встроенных функций MQL5: `iHigh()`, `iLow()` и `iTime()`. Они обеспечивают легкий доступ к максимальной цене, минимальной цене и времени открытия определенного бара (на основе заданного `индекса`) по текущему символу и таймфрейму. Благодаря использованию этих сокращенных функций код становится чище и более читаемым, особенно при повторном доступе к данным бара во время обнаружения или визуализации колебаний.
//+------------------------------------------------------------------+ //| Filter and mark | //+------------------------------------------------------------------+ void FilterAndMarkValidSwings(SwingPoint &points[]) { int count = ArraySize(points); if(count < 2) return; for(int i = 0; i < count; i++) { if(points[i].used) continue; bool isValid = true; double swingPrice = points[i].price; int swingIndex = points[i].index; // Scan forward in time from the swing point for(int j = swingIndex - 1; j >= 0; j--) { double high = iHigh(_Symbol, TimeFrame, j); double low = iLow(_Symbol, TimeFrame, j); // Invalidate swing high if price went higher later if(points[i].isHigh && high > swingPrice) { isValid = false; break; } // Invalidate swing low if price went lower later if(!points[i].isHigh && low < swingPrice) { isValid = false; break; } } if(isValid) { points[i].used = true; // Draw object on chart string objName = points[i].isHigh ? StringFormat("SwingHigh_%d", TimeToString(iTime(_Symbol, TimeFrame, swingIndex))) : StringFormat("SwingLow_%d", TimeToString(iTime(_Symbol, TimeFrame, swingIndex))); color swingColor = points[i].isHigh ? clrRed : clrBlue; ObjectCreate(0, objName, OBJ_HLINE, 0, 0, swingPrice); ObjectSetInteger(0, objName, OBJPROP_COLOR, swingColor); ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1); } } }
Функция `FilterAndMarkValidSwings()` уточняет список точек колебания, определяя, какие из них все еще действительны и не стали недействительными из-за будущих движений цены. Она берет массив ссылок `SwingPoints` и перебирает их, пропуская те, которые уже отмечены как `used`. Для каждого потенциального колебания она предполагает, что точка действительна, а затем выполняет проверку на действительность на основе исторического ценового движения, чтобы подтвердить, вышла ли цена за пределы этого колебания после его формирования.
Чтобы определить действительность, функция сканирует прошлые бары в обратном направлении от индекса колебания. Для максимума колебания она проверяет, был ли у какой-либо будущей свечи превышающий её максимум, а для минимума колебания она проверяет, был ли у какой-либо свечи более низкий минимум. Если такое условие обнаружено, точка колебания считается недействительной, поскольку цена фактически «прошла» через это колебание. Поэтому она не отмечается и не используется в дальнейших расчетах. Если такое условие не определено, точка колебания считается действительной и помечается как `used` (использованная).
Для каждого действительного колебания функция затем рисует горизонтальную линию на графике, чтобы визуально отметить его. Линия отображается в виде пунктирной линии и окрашена в красный цвет для максимумов колебаний или в синий цвет для минимумов колебаний. Объекту присваивается имя с учетом типа колебания и времени бара, на котором он был обнаружен. Эта визуальная обратная связь помогает трейдерам мгновенно определить, какие точки колебания советник считает сильными и нетронутыми ценой, что упрощает доверие и отладку логики во время анализа или торговли.
//+------------------------------------------------------------------+ //| Cleaning up old swings | //+------------------------------------------------------------------+ void CleanOldSwingObjects(int keepBars = 100) { datetime oldestDate = iTime(_Symbol, TimeFrame, keepBars); int total = ObjectsTotal(0); for(int i = total - 1; i >= 0; i--) { string name = ObjectName(0, i); if(StringFind(name, "SwingHigh_") == 0 || StringFind(name, "SwingLow_") == 0) { datetime swingTime = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME); if(swingTime < oldestDate) { ObjectDelete(0, name); } } } }
Здесь функция отвечает за удаление устаревших, связанных с колебаниями визуальных элементов, из диаграммы для сохранения ясности и эффективности. Она определяет пороговое значение для «старых» объектов с помощью параметра `keepBars`, который извлекает временную метку бара, находящуюся количество `keepBars` свечей назад. Любые объекты swing, созданные до этой временной метки, считаются устаревшими. Затем функция просматривает все графические объекты на графике в обратном порядке и проверяет, начинаются ли их имена с «SwingHigh_» или «SwingLow_», что идентифицирует их как маркеры колебаний.
Для каждого из этих объектов swing она извлекает время создания и сравнивает его с временной отметкой отсечения (oldestDate). Если время объекта старше, он удаляется из диаграммы с помощью `objectDelete()`. Эта процедура гарантирует, что график останется незагроможденным, отображая только недавние и актуальные точки колебания. Она также помогает предотвратить ухудшение работы с течением времени, особенно при работе советника на более длительных историях или на реальных рынках, где накапливается много колебаний.
//+------------------------------------------------------------------+ //| Generate Signal & Trade | //+------------------------------------------------------------------+ void GenerateSignalAndTrade() { if (ArraySize(swingPoints) < 2) return; int last = ArraySize(swingPoints) - 1; int prev = swingPoints[last].previous; if (prev == -1) return; double entry = swingPoints[last].price; double reference = swingPoints[prev].price; double sl, tp; bool isBuy = entry > reference, isSell = entry < reference; SetSLTP(entry, reference, isBuy, sl, tp); if (PositionSelect(_Symbol)) return; if (isBuy) ExecuteTrade(ORDER_TYPE_BUY); else if(isSell) ExecuteTrade(ORDER_TYPE_SELL); }
Функция `GenerateSignalAndTrade()` отвечает за создание торгового сигнала на основе направления последнего пути, найденного алгоритмом Дейкстры через точки колебания. Сначала она обеспечивает наличие по крайней мере двух точек колебания для сравнения и что последняя точка колебания имеет допустимый «предыдущий» узел `previous`. Затем извлекает цену последнего колебания и предшествующего связанного с ним колебания с помощью ценового соотношения между ними для определения направления сделки: если последняя цена выше предыдущей, это сигнал к покупке; если ниже — к продаже.
После определения направления функция рассчитывает уровни стоп-лосса и тейк-профита с помощью функции `SetSLTP()` , которая основывает их на расстоянии между точками колебания. Перед открытием сделки проверяет, открыта ли уже позиция по символу, чтобы избежать дубликатов. Наконец, размещает сделку с помощью функции `ExecuteTrade()` , передавая соответствующий тип ордера. Такая логика гарантирует, что сделки будут совершаться только тогда, когда между допустимыми точками колебания будет выявлен четкий, структурно обоснованный направленный путь.
//+------------------------------------------------------------------+ //| Calculate SL and TP based on distance to previous node | //+------------------------------------------------------------------+ void SetSLTP(double entry, double ref, bool isBuy, double &sl, double &tp) { double distance = MathAbs(entry - ref) + PointBuffer * _Point; if (isBuy) { sl = entry - distance; tp = entry + distance; } else { sl = entry + distance; tp = entry - distance; } }
Функция `SetSLTP()` рассчитывает уровни стоп-лосса (sl) и тейк-профита (tp) для сделки на основе расстояния между текущей ценой входа и контрольной ценой (обычно предыдущей точкой колебания). Сначала она вычисляет абсолютную разницу цен между этими двумя точками и добавляет небольшой буфер (в пунктах) для безопасности. Если сделка заключается в покупке, стоп-лосс размещается под входом, а тейк-профит — над ним; для продажи стоп-лосс размещается над входом, а тейк-профит — под ним. Это обеспечивает симметричное распределение риска и прибыли относительно структуры колебания, помогая советнику следовать за движением цены с помощью значимых, основанных на структуре, уровней SL и TP.
//+------------------------------------------------------------------+ //| Execute trade with risk parameters | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE tradeType){ double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); // Convert StopLoss and TakeProfit from pips to actual price distances double sl_distance = StopLoss * point; double tp_distance = TakeProfit * point; double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance : price + sl_distance; double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance : price - tp_distance; trade.PositionOpen(_Symbol, tradeType, In_Lot, price, sl, tp, NULL); }
Функция `ExecuteTrade()` отвечает за размещение торгового ордера с предопределенными параметрами риска. Она начинается с определения текущей рыночной цены: для ордера на покупку используется цена ask, а для ордера на продажу — цена bid. Затем рассчитывает уровни стоп-лосса и тейк-профита, преобразуя входные значения (stopLoss и TakeProfit) из пунктов в фактические ценовые расстояния с помощью размера пункта символа. В зависимости от того, совершается ли сделка на покупку или продажу, она размещает стоп-лосс и тейк-профит соответствующим образом выше или ниже цены входа.
Наконец, она использует метод `PositionOpen()` класса `CTrade` для выполнения сделки с рассчитанными параметрами, включая размер лота, направление, цену входа, SL, TP и отсутствие пользовательских комментариев. Это гарантирует, что сделки следуют единому фреймворку риска независимо от направления рынка.
//+------------------------------------------------------------------+ //| OnTick | //+------------------------------------------------------------------+ void OnTick() { static datetime lastBarTime = 0; datetime currentBarTime = iTime(_Symbol, _Period, 0); if (currentBarTime != lastBarTime) { lastBarTime = currentBarTime; DetectSwings(LeftBars, RightBars); ApplyDijkstra(); VisualizeSwings(); GenerateSignalAndTrade(); FilterAndMarkValidSwings(swingPoints); CleanOldSwingObjects(); } }
Наконец, функция `OnTick()` — это основной цикл выполнения советника, который запускается на каждом новом тике. Чтобы избежать избыточной обработки, используется статическая переменная `lastBarTime` для определения того, образовался ли новый бар, путем сравнения его со временем открытия текущего бара. При обнаружении нового бара она обновляет `lastBarTime` и запускает основную логику: обнаружение новых максимумов и минимумов колебаний (DetectSwings), применение алгоритма Дейкстры для поиска наиболее эффективного пути между колебаниями (ApplyDijkstra), визуальное отображение колебаний и их связей на графике (VisualiseSwings), генерация и исполнение торговых сигналов на основе направления пути (GenerateSignalAndTrade), фильтрация недействительных точек колебаний (FilterAndMarkValidSwings) и, наконец, очистка старых объектов колебаний для поддержания порядка на графике (CleanOldSwingObjects).
Такая структура обеспечивает интеллектуальную обработку рыночной структуры советником.
Результаты тестирования на истории
Тестирование на истории оценивалось на таймфрейме 1H в течение 2-месячного окна тестирования (с 1 мая 2025 г. по 20 июня 2025 г.) со следующими входными настройками:
- TP в пунктах = 1000
- стоп-лосс = 385
- входные лоты = 0,01
- баров слева = 3
- баров справа = 3
- макс. колебание = 50
- буфер точек = 10,0
- проскальзывание = 5
Заключение
Подводя итог, мы создали полнофункциональный советник на MQL5, трактующий структуру финансового рынка с помощью алгоритма Дейкстры, применяемого к максимумам и минимумам колебаний как к узлам графа. Система обнаруживает значимые точки колебаний на каждом новом баре, отфильтровывает недействительные, которые цена уже преодолела, и рассматривает действительные колебания как вершины в алгоритме поиска пути. Затем использует ценовое расстояние в качестве весов рёбер для вычисления наиболее эффективного пути через структуру рынка, определяя наиболее вероятное направление движения цены.
На основе этого анализа советник генерирует направленные торговые сигналы и совершает сделки с надлежащим образом рассчитанными уровнями стоп-лосса и тейк-профита на основе расстояния между точками колебания. Визуальные инструменты, такие как стрелки и линии трендов, рисуются для отображения как обнаруженных точек колебаний, так и вычисленных путей, а процедуры очистки гарантируют, что график остается чистым и обновленным.
В заключение следует отметить, что данный советник выходит за рамки традиционной торговли на основе индикаторов, интегрируя графический алгоритм в анализ ценового движения, что позволяет принимать более структурированные и логичные торговые решения. Совмещая торговые входы с геометрией рыночных колебаний и гарантируя, что каждый узел используется только один раз, пока он действителен, система имитирует естественный поток цены через уровни поддержки и сопротивления. Модульная конструкция с функциями обнаружения, проверки, поиска пути, исполнения и визуализации также позволяет легко улучшать, расширять или проводить дополнительное тестирование стратегии на истории. Данный проект закладывает основу для интеллектуальной, адаптивной торговой системы, рассматривающей ценовое движение как навигационную сеть, объединяющую теорию структуры данных с поведением рынка.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18760





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования