Теория графов: Алгоритм Форда—Фалкерсона для оценки ликвидности и пропускной способности рынка
Содержание
Введение
Большинство трейдеров, использующих концепции ICT, знают, как распознать ценовой дисбаланс (FVG) или ордер-блок. Они могут определить пулы ликвидности, расположенные выше равных максимумов или ниже равных минимумов. Но знание того, где находятся эти структуры, — это только половина дела. Настоящее разочарование возникает в момент принятия решения — цена находится внутри бычьего ценового дисбаланса (FVG), выше есть пул ликвидности, и сетап выглядит валидным. И все же сделка терпит неудачу. Не потому, что анализ был неверным, а потому, что у рынка просто не было достаточного структурного импульса для достижения цели. Не было возможности измерить, насколько прочным или слабым был этот путь. У трейдера не было фильтра. Он входил в сделку, ориентируясь лишь на визуальную картину, и рынок застревал где-то посередине.
Именно здесь вступает в дело алгоритм Форда—Фалкерсона. Алгоритм не предсказывает направление — это задача пулов ликвидности, ценовых дисбалансов (FVG) и ордер-блоков. Он измеряет пропускную способность пути между текущей ценой и целевым уровнем ликвидности. Каждая структура между источником и целью становится узлом в ориентированном графе. Каждому соединению присваивается оценка пропускной способности на основе объема, реакции цены, расстояния и качества структуры. Затем алгоритм Форда—Фалкерсона находит максимальный поток через эту сеть. Если путь достаточно сильный, сделка допускается к исполнению. Если есть узкое место — слабое звено в цепи — система отмечает его, и сделка пропускается. Теперь у трейдера есть количественный фильтр в рамках ICT-фреймворка. Он превращает визуально распознанный сетап в формализованное торговое решение.
Обзор системы
Система построена послойно, и каждый слой выполняет свою специфическую задачу. Первый слой сканирует график и выявляет структуру рынка — максимумы и минимумы свингов, ценовые дисбалансы (FVG), ордер-блоки и пулы ликвидности. Эти данные не отображаются визуально. Они преобразуются в узлы внутри ориентированного графа. Второй слой устанавливает связи между этими узлами и присваивает каждой связи оценку пропускной способности. Эта оценка рассчитывается на основе четырех факторов: объема тиков, силы предыдущих ценовых реакций, расстояния движения и качества типа структуры. Пулы ликвидности получают наивысший показатель. За ними следуют ордер-блоки. Далее по значимости идут ценовые дисбалансы (FVG) и свинги. После построения графа движок Форда—Фалкерсона обрабатывает его и возвращает одно число — максимальный поток от текущей цены к ближайшему целевому пулу ликвидности.

Движок отбрасывает пути с низкой пропускной способностью и возвращает одно число — способность рынка достичь ликвидности.
Система никогда не торгует только на основе потока. Направление цен имеет первостепенное значение. Цена должна находиться в ценовом дисбалансе (FVG) или корректно удерживаться от ордер-блока, а по другую сторону движения должен находиться пул ликвидности, который будет притягивать цену. Только после выполнения этих условий ICT показатель потока становится актуальным. Если максимальный поток превышает пороговое значение, сделка допускается к исполнению, и размер позиции определяется динамически — более сильный поток приводит к большему размеру позиции, более слабый поток — к меньшему. Если поток недостаточен или если на пути движения цены обнаруживается узкое место, сделка полностью пропускается. В результате получается двухэтапный процесс принятия решений: структура определяет направление, а сеть решает, достаточно ли у рынка ресурсов для реализации.

Структура устанавливает направление. Сеть определяет пропускную способность. Только когда оба условия совпадают — условия ICT выполнены И поток превышает пороговое значение — сделка исполняется.
Приступаем к работе
//+------------------------------------------------------------------+ //| Ford-Fulkerson.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/ru/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ru/users/johnhlomohang/" #property version "1.00" #include <Trade\Trade.mqh> #include <Trade\PositionInfo.mqh> //+------------------------------------------------------------------+ //| INPUT PARAMETERS | //+------------------------------------------------------------------+ // --- Structure Detection --- input group "=== Structure Detection ===" input int SwingLookback = 5; // Swing High/Low: bars each side input int FVG_MinPips = 3; // Minimum FVG size in pips input int LiquidityLookback = 50; // Bars to scan for liquidity pools input double EqualLevelTolerance = 0.0003; // Equal highs/lows tolerance (price) // --- Ford-Fulkerson Engine --- input group "=== Flow Engine ===" input double FlowThreshold = 8.0; // Minimum max-flow to qualify trade input double WeakBottleneck = 3.0; // Bottleneck below this = avoid trade // --- Multi-Timeframe --- input group "=== Multi-Timeframe ===" input bool UseMTF = true; input ENUM_TIMEFRAMES TF_Fast = PERIOD_M5; input ENUM_TIMEFRAMES TF_Mid = PERIOD_M15; input ENUM_TIMEFRAMES TF_Slow = PERIOD_H1; input ENUM_TIMEFRAMES TF_Major = PERIOD_H4; input double MTF_Weight_Fast = 0.1; input double MTF_Weight_Mid = 0.2; input double MTF_Weight_Slow = 0.3; input double MTF_Weight_Major = 0.4; // --- Risk Management --- input group "=== Risk Management ===" input double BaseRiskPercent = 1.0; // Base risk % per trade input double MaxRiskPercent = 2.0; // Maximum risk % (at peak flow) input double PeakFlowRef = 20.0; // Flow value = MaxRisk input double SL_BufferPips = 5.0; // SL buffer in pips input double TP_RR_Ratio = 2.0; // Risk:Reward ratio // --- Execution --- input group "=== Execution ===" input ulong MagicNumber = 202501; input int MaxOpenTrades = 2; input int SlippagePoints = 30; input bool EnableLong = true; input bool EnableShort = true; // --- Capacity Scoring --- input group "=== Capacity Scoring ===" input double W_Volume = 1.0; input double W_Reaction = 1.0; input double W_Distance = 1.0; input double W_Structure = 1.0;
Для начала мы определяем основные входные параметры, которые управляют каждым основным компонентом советника. Настройки обнаружения структуры определяют, как на графике идентифицируются максимумы свингов, минимумы свингов, ценовые дисбалансы (FVG) и пулы ликвидности. Входные данные движка потоков Форда—Фалкерсона задают минимальные требования к ликвидности и пропускной способности пути, необходимые для квалификации сделки и отсеивания слабых рыночных путей. Настройки для нескольких таймфреймов позволяют объединять измерения потоков с разных таймфреймов с использованием настраиваемых весов, а раздел управления рисками корректирует размер позиции на основе рассчитанного потока. Наконец, параметры исполнения и оценки контролируют размещение сделок и лимиты уровня риска. Они также устанавливают веса, используемые для расчета пропускных способностей ребер (объем, сила реакции, расстояние и качество структуры).
//+------------------------------------------------------------------+ //| ENUMERATIONS & STRUCTURES | //+------------------------------------------------------------------+ enum NodeType { NODE_SWING_HIGH, NODE_SWING_LOW, NODE_LIQUIDITY_BUY, NODE_LIQUIDITY_SELL, NODE_FVG_BULL, NODE_FVG_BEAR, NODE_OB_BULL, NODE_OB_BEAR, NODE_SOURCE, NODE_SINK }; enum SignalDir { SIG_NONE, SIG_BUY, SIG_SELL }; struct GraphNode { int id; NodeType type; datetime time; double price; double priceHigh; double priceLow; }; struct GraphEdge { int fromNode; int toNode; double capacity; double flow; }; struct SwingNode { datetime time; double price; int strength; }; struct FVGNode { double upper; double lower; bool bullish; datetime time; }; struct OBNode { double high; double low; bool bullish; datetime time; }; struct LiquidityNode { double price; bool buySide; datetime time; }; //+------------------------------------------------------------------+ //| GLOBAL VARIABLES | //+------------------------------------------------------------------+ CTrade trade; int totalNodes = 0; int totalEdges = 0; GraphNode g_nodes[]; GraphEdge g_edges[]; SwingNode g_swingHighs[], g_swingLows[]; FVGNode g_fvgs[]; OBNode g_obs[]; LiquidityNode g_liq[]; //--- Flat 1D matrices — accessed via Idx(row,col,N) double g_capFlat[]; double g_flowFlat[]; int g_matrixN = 0; // current matrix dimension double g_pointValue; double g_pipValue; int g_digits; string g_symbol; //--- Inline index helper int Idx(int r, int c, int N) { return r * N + c; } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { g_symbol = _Symbol; g_digits = (int)SymbolInfoInteger(g_symbol, SYMBOL_DIGITS); g_pointValue = SymbolInfoDouble(g_symbol, SYMBOL_POINT); g_pipValue = (g_digits == 3 || g_digits == 5) ? g_pointValue * 10.0 : g_pointValue; trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(SlippagePoints); trade.SetTypeFilling(ORDER_FILLING_IOC); Print("Ford-Fulkerson Liquidity EA initialized | Symbol=", g_symbol, " | PipValue=", g_pipValue, " | FlowThreshold=", FlowThreshold); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static datetime lastBar = 0; datetime curBar = iTime(g_symbol, PERIOD_CURRENT, 0); if(curBar == lastBar) return; lastBar = curBar; if(CountOpenTrades() >= MaxOpenTrades) return; //--- Layer 1 — Detect structures DetectSwings(); DetectFVGs(); DetectOrderBlocks(); DetectLiquidityPools(); //--- Layers 2-6 — Build graph, run flow, get signal SignalDir signal = SIG_NONE; double maxFlow = 0.0; double entryPx = 0.0, slPx = 0.0, tpPx = 0.0; if(UseMTF) { double mtfFlow = ComputeMTFFlow(); if(mtfFlow > FlowThreshold) signal = GetDirectionalSignal(maxFlow, entryPx, slPx, tpPx); maxFlow = mtfFlow; } else { signal = BuildAndRunGraph(maxFlow, entryPx, slPx, tpPx); } //--- Layer 7 — Execute with dynamic sizing if(signal != SIG_NONE && maxFlow > FlowThreshold) { double riskPct = DynamicRisk(maxFlow); double lots = CalcLotSize(riskPct, MathAbs(entryPx - slPx)); if(signal == SIG_BUY && EnableLong) trade.Buy(lots, g_symbol, entryPx, slPx, tpPx, "FF_Buy|Flow=" + DoubleToString(maxFlow, 2)); else if(signal == SIG_SELL && EnableShort) trade.Sell(lots, g_symbol, entryPx, slPx, tpPx, "FF_Sell|Flow=" + DoubleToString(maxFlow, 2)); } ShowDashboard(signal, maxFlow); }
Советник построен на основе набора перечислений, структур и глобальных переменных, определяющих граф структуры рынка. Мы используем типы узлов для представления максимумов и минимумов свингов, пулов ликвидности, ценовых дисбалансов (FVG), ордер-блоков, включая специальные узлы SOURCE и SINK, необходимые для движка расчета потока. Специальные структуры хранят свойства каждой обнаруженной рыночной структуры, а узлы и ребра графа содержат информацию, необходимую для расчетов пропускной способности и потоков. Глобальные массивы хранят данные графа, матрицы пропускной способности, обнаруженные структуры и специфические для символов настройки, зависящие от конкретного символа.
Процедура инициализации загружает свойства символа, рассчитывает значения пунктов и настраивает параметры исполнения сделок. В каждой новой свече мы обнаруживаем структуры рынка и преобразуем их в представление текущего состояния рынка в виде графов. Затем движок расчета потока оценивает доступный путь к ликвидности с рассчитанной пропускной способностью и генерирует направленный сигнал, когда рассчитанный поток достаточно силен. Размер позиции динамически корректируется в зависимости от силы потока, позволяя увеличить позицию при более сильных торговых возможностях и уменьшить ее при более слабых. Если все торговые условия выполнены, советник совершает сделку и обновляет дашборд, предоставляя самую свежую информацию о потоках и сигналах.
//+------------------------------------------------------------------+ //| LAYER 1 — STRUCTURE DETECTION | //+------------------------------------------------------------------+ void DetectSwings() { ArrayResize(g_swingHighs, 0); ArrayResize(g_swingLows, 0); int bars = iBars(g_symbol, PERIOD_CURRENT); int limit = (int)MathMin(bars - SwingLookback - 1, 200); for(int i = SwingLookback; i < limit; i++) { double h = iHigh(g_symbol, PERIOD_CURRENT, i); double l = iLow(g_symbol, PERIOD_CURRENT, i); bool isHigh = true, isLow = true; for(int k = 1; k <= SwingLookback; k++) { if(iHigh(g_symbol,PERIOD_CURRENT,i-k) >= h || iHigh(g_symbol,PERIOD_CURRENT,i+k) >= h) isHigh = false; if(iLow(g_symbol,PERIOD_CURRENT,i-k) <= l || iLow(g_symbol,PERIOD_CURRENT,i+k) <= l) isLow = false; } if(isHigh) { int sz = ArraySize(g_swingHighs); ArrayResize(g_swingHighs, sz+1); g_swingHighs[sz].time = iTime(g_symbol,PERIOD_CURRENT,i); g_swingHighs[sz].price = h; g_swingHighs[sz].strength = SwingStrength(i); } if(isLow) { int sz = ArraySize(g_swingLows); ArrayResize(g_swingLows, sz+1); g_swingLows[sz].time = iTime(g_symbol,PERIOD_CURRENT,i); g_swingLows[sz].price = l; g_swingLows[sz].strength = SwingStrength(i); } } } //+------------------------------------------------------------------+ //| Swing Strength | //+------------------------------------------------------------------+ int SwingStrength(int bar) { double body = MathAbs(iOpen(g_symbol,PERIOD_CURRENT,bar) - iClose(g_symbol,PERIOD_CURRENT,bar)); double candle = iHigh(g_symbol,PERIOD_CURRENT,bar) - iLow(g_symbol,PERIOD_CURRENT,bar); if(candle < g_pipValue) return 1; double ratio = 1.0 - (body / candle); if(ratio > 0.7) return 3; if(ratio > 0.4) return 2; return 1; } //+------------------------------------------------------------------+ //| Detect Fair Value Gaps | //+------------------------------------------------------------------+ void DetectFVGs() { ArrayResize(g_fvgs, 0); int bars = iBars(g_symbol, PERIOD_CURRENT); int limit = (int)MathMin(bars - 2, 200); for(int i = 2; i < limit; i++) { double c1H = iHigh(g_symbol,PERIOD_CURRENT,i); double c1L = iLow(g_symbol,PERIOD_CURRENT,i); double c3H = iHigh(g_symbol,PERIOD_CURRENT,i-2); double c3L = iLow(g_symbol,PERIOD_CURRENT,i-2); if(c1H < c3L && (c3L - c1H) >= FVG_MinPips * g_pipValue) { int sz = ArraySize(g_fvgs); ArrayResize(g_fvgs, sz+1); g_fvgs[sz].upper = c3L; g_fvgs[sz].lower = c1H; g_fvgs[sz].bullish = true; g_fvgs[sz].time = iTime(g_symbol,PERIOD_CURRENT,i-1); } else if(c1L > c3H && (c1L - c3H) >= FVG_MinPips * g_pipValue) { int sz = ArraySize(g_fvgs); ArrayResize(g_fvgs, sz+1); g_fvgs[sz].upper = c1L; g_fvgs[sz].lower = c3H; g_fvgs[sz].bullish = false; g_fvgs[sz].time = iTime(g_symbol,PERIOD_CURRENT,i-1); } } } //+------------------------------------------------------------------+ //| Detect Order Blocks | //+------------------------------------------------------------------+ void DetectOrderBlocks() { ArrayResize(g_obs, 0); int bars = iBars(g_symbol, PERIOD_CURRENT); int limit = (int)MathMin(bars - 5, 200); for(int i = 3; i < limit; i++) { double displacement = MathAbs(iClose(g_symbol,PERIOD_CURRENT,i-2) - iOpen(g_symbol,PERIOD_CURRENT,i-2)); double avgCandle = AverageCandleSize(i, 10); if(displacement < avgCandle * 1.5) continue; int sz = ArraySize(g_obs); ArrayResize(g_obs, sz+1); g_obs[sz].high = iHigh(g_symbol,PERIOD_CURRENT,i); g_obs[sz].low = iLow(g_symbol,PERIOD_CURRENT,i); g_obs[sz].bullish = (iClose(g_symbol,PERIOD_CURRENT,i) < iOpen(g_symbol,PERIOD_CURRENT,i)); g_obs[sz].time = iTime(g_symbol,PERIOD_CURRENT,i); } } //+------------------------------------------------------------------+ //| Average Candle Size | //+------------------------------------------------------------------+ double AverageCandleSize(int startBar, int count) { double total = 0; for(int i = startBar; i < startBar + count; i++) total += iHigh(g_symbol,PERIOD_CURRENT,i) - iLow(g_symbol,PERIOD_CURRENT,i); return (count > 0) ? total / count : g_pipValue; } //+------------------------------------------------------------------+ //| Detect Liquidity Pools | //+------------------------------------------------------------------+ void DetectLiquidityPools() { ArrayResize(g_liq, 0); int limit = (int)MathMin(iBars(g_symbol,PERIOD_CURRENT), LiquidityLookback); for(int i = 1; i < limit; i++) { double hi = iHigh(g_symbol,PERIOD_CURRENT,i); double lo = iLow(g_symbol,PERIOD_CURRENT,i); for(int j = i+1; j < limit; j++) { double hj = iHigh(g_symbol,PERIOD_CURRENT,j); double lj = iLow(g_symbol,PERIOD_CURRENT,j); if(MathAbs(hi - hj) <= EqualLevelTolerance) { int sz = ArraySize(g_liq); ArrayResize(g_liq, sz+1); g_liq[sz].price = (hi + hj) * 0.5; g_liq[sz].buySide = true; g_liq[sz].time = iTime(g_symbol,PERIOD_CURRENT,i); } if(MathAbs(lo - lj) <= EqualLevelTolerance) { int sz = ArraySize(g_liq); ArrayResize(g_liq, sz+1); g_liq[sz].price = (lo + lj) * 0.5; g_liq[sz].buySide = false; g_liq[sz].time = iTime(g_symbol,PERIOD_CURRENT,i); } } } //--- Swing highs → buy-side liquidity; swing lows → sell-side for(int i = 0; i < ArraySize(g_swingHighs); i++) { int sz = ArraySize(g_liq); ArrayResize(g_liq, sz+1); g_liq[sz].price = g_swingHighs[i].price; g_liq[sz].buySide = true; g_liq[sz].time = g_swingHighs[i].time; } for(int i = 0; i < ArraySize(g_swingLows); i++) { int sz = ArraySize(g_liq); ArrayResize(g_liq, sz+1); g_liq[sz].price = g_swingLows[i].price; g_liq[sz].buySide = false; g_liq[sz].time = g_swingLows[i].time; } }
Этот раздел отвечает за выявление рыночных структур, которые впоследствии станут узлами в сети ликвидности Форда—Фалкерсона. Мы сканируем исторические данные о ценах, чтобы определить максимумы и минимумы свингов, сравнивая каждую свечу с соседними свечами в пределах заданного диапазона просмотра. Когда обнаруживается подтвержденная точка свинга, ее цена, временная метка и сила сохраняются для последующего использования. Далее функция SwingStrength() оценивает качество каждого свинга, сравнивая тело свечи с ее общим диапазоном, позволяя свечам с более сильным отбоем цены получать более высокие значения силы.
Мы также обнаруживаем ценовые дисбалансы (FVG) и ордер-блоки, которые служат ключевыми структурами ликвидности в графе. Разрыв справедливой стоимости определяется, когда существует ценовой дисбаланс (FVG) между несмежными свечами, и разрыв превышает минимальный требуемый размер. Бычьи и медвежьи разрывы сохраняются вместе с их границами и временными метками. Ордер-блоки обнаруживаются после сильных дисбалансных импульсов, когда размер свечи смещения должен быть заметно больше среднего размера последних свечей. Этот процесс помогает отфильтровать слабые структуры и сохранить только те, которые связаны со значительным рыночным импульсом.
Далее определяются пулы ликвидности путем поиска равных максимумов и равных минимумов в пределах заданного окна ретроспективного анализа. Если два уровня попадают в допустимый диапазон, они рассматриваются как потенциальная ликвидность на стороне покупателей или ликвидность на стороне продавцов и добавляются в массив ликвидности. Процедура также преобразует ранее обнаруженные максимумы свингов в узлы ликвидности на стороне покупателей, а минимумы свингов — в узлы ликвидности на стороне продавцов. В конце этого уровня у нас есть свинги, ценовые дисбалансы (FVG), ордер-блоки и пулы ликвидности. Далее эти элементы преобразуются в узлы графа для расчетов пропускной способности и потоков.
//+------------------------------------------------------------------+ //| GRAPH BUILD + FORD-FULKERSON DISPATCH | //+------------------------------------------------------------------+ SignalDir BuildAndRunGraph(double &maxFlow, double &entry, double &sl, double &tp) { double curPrice = SymbolInfoDouble(g_symbol, SYMBOL_BID); LiquidityNode bullTarget, bearTarget; double bullFlow = 0.0, bearFlow = 0.0; if(FindNearestLiquidity(curPrice, true, bullTarget)) bullFlow = BuildDirectionalGraph(curPrice, bullTarget, true); if(FindNearestLiquidity(curPrice, false, bearTarget)) bearFlow = BuildDirectionalGraph(curPrice, bearTarget, false); bool bullSig = CheckBullEntry(curPrice); bool bearSig = CheckBearEntry(curPrice); if(bullSig && bullFlow >= bearFlow && bullFlow > FlowThreshold) { maxFlow = bullFlow; entry = SymbolInfoDouble(g_symbol, SYMBOL_ASK); sl = GetBullishSL(curPrice); tp = GetTP(entry, sl, true); return SIG_BUY; } if(bearSig && bearFlow > bullFlow && bearFlow > FlowThreshold) { maxFlow = bearFlow; entry = SymbolInfoDouble(g_symbol, SYMBOL_BID); sl = GetBearishSL(curPrice); tp = GetTP(entry, sl, false); return SIG_SELL; } maxFlow = MathMax(bullFlow, bearFlow); return SIG_NONE; } //+------------------------------------------------------------------+ //| Find Nearest Liquidity | //+------------------------------------------------------------------+ bool FindNearestLiquidity(double price, bool buySide, LiquidityNode &found) { double best = DBL_MAX; bool ok = false; int n = ArraySize(g_liq); for(int i = 0; i < n; i++) { if(g_liq[i].buySide != buySide) continue; double dist = buySide ? (g_liq[i].price - price) : (price - g_liq[i].price); if(dist > 0.0 && dist < best) { best = dist; found = g_liq[i]; ok = true; } } return ok; }
Этот раздел выступает в качестве уровня принятия решений между анализом структуры рынка и исполнением сделок. Сначала мы определяем ближайшие цели ликвидности на стороне покупателей и продавцов относительно текущей рыночной цены, затем строим отдельные направленные графы для расчета доступного потока ликвидности и пропускной способности к каждому целевому уровню с использованием движка Форда—Фалкерсона. Полученные значения бычьего и медвежьего потока объединяются с проверками подтверждения точек входа, основанными на выявленных рыночных структурах. Сигнал на покупку генерируется, когда бычий сетап допустим, поток превышает требуемый порог, а его пропускная способность больше или равна пропускной способности медвежьего сетапа. Сигнал на продажу следует той же логике, но в обратном направлении. При обнаружении действительного сигнала мы рассчитываем цену входа, уровни стоп-лосса и тейк-профита. Функция FindNearestLiquidity() привязывает граф к ближайшему соответствующему пулу ликвидности, к которому цена реалистично может направиться.
//+------------------------------------------------------------------+ //| DIRECTED GRAPH CONSTRUCTION | //+------------------------------------------------------------------+ double BuildDirectionalGraph(double srcPrice, LiquidityNode &sinkNode, bool bullish) { ArrayResize(g_nodes, 0); ArrayResize(g_edges, 0); totalNodes = 0; totalEdges = 0; //--- Source node AddNode(NODE_SOURCE, TimeCurrent(), srcPrice, srcPrice, srcPrice); //--- Intermediate structure nodes AddStructureNodes(srcPrice, sinkNode.price, bullish); //--- Sink node AddNode(bullish ? NODE_LIQUIDITY_BUY : NODE_LIQUIDITY_SELL, sinkNode.time, sinkNode.price, sinkNode.price, sinkNode.price); int sinkID = totalNodes - 1; if(sinkID < 1) return 0.0; //--- Sort + connect SortNodesByPrice(bullish); ConnectConsecutiveNodes(); //--- Build flat capacity matrix (N×N stored as 1D array) int N = totalNodes; g_matrixN = N; ArrayResize(g_capFlat, N * N); ArrayResize(g_flowFlat, N * N); ArrayFill(g_capFlat, 0, N * N, 0.0); ArrayFill(g_flowFlat, 0, N * N, 0.0); for(int e = 0; e < totalEdges; e++) { int f = g_edges[e].fromNode; int t = g_edges[e].toNode; g_capFlat[Idx(f, t, N)] += g_edges[e].capacity; } //--- Bottleneck safety if(MinEdgeCapacity() < WeakBottleneck) return 0.0; //--- Run Edmonds-Karp return EdmondsKarp(0, sinkID, N); } //+------------------------------------------------------------------+ //| Add Node | //+------------------------------------------------------------------+ void AddNode(NodeType type, datetime t, double price, double hi, double lo) { int sz = totalNodes; ArrayResize(g_nodes, sz + 1); g_nodes[sz].id = sz; g_nodes[sz].type = type; g_nodes[sz].time = t; g_nodes[sz].price = price; g_nodes[sz].priceHigh = hi; g_nodes[sz].priceLow = lo; totalNodes++; } //+------------------------------------------------------------------+ //| Add Structure Nodes | //+------------------------------------------------------------------+ void AddStructureNodes(double srcP, double sinkP, bool bullish) { double lo = MathMin(srcP, sinkP); double hi = MathMax(srcP, sinkP); for(int i = 0; i < ArraySize(g_fvgs); i++) { double mid = (g_fvgs[i].upper + g_fvgs[i].lower) * 0.5; if(mid < lo || mid > hi) continue; if(bullish && !g_fvgs[i].bullish) continue; if(!bullish && g_fvgs[i].bullish) continue; AddNode(bullish ? NODE_FVG_BULL : NODE_FVG_BEAR, g_fvgs[i].time, mid, g_fvgs[i].upper, g_fvgs[i].lower); } for(int i = 0; i < ArraySize(g_obs); i++) { double mid = (g_obs[i].high + g_obs[i].low) * 0.5; if(mid < lo || mid > hi) continue; if(bullish && !g_obs[i].bullish) continue; if(!bullish && g_obs[i].bullish) continue; AddNode(bullish ? NODE_OB_BULL : NODE_OB_BEAR, g_obs[i].time, mid, g_obs[i].high, g_obs[i].low); } if(bullish) { for(int i = 0; i < ArraySize(g_swingLows); i++) { double p = g_swingLows[i].price; if(p < lo || p > hi) continue; AddNode(NODE_SWING_LOW, g_swingLows[i].time, p, p, p); } } else { for(int i = 0; i < ArraySize(g_swingHighs); i++) { double p = g_swingHighs[i].price; if(p < lo || p > hi) continue; AddNode(NODE_SWING_HIGH, g_swingHighs[i].time, p, p, p); } } } //+------------------------------------------------------------------+ //| Sort Nodes | //+------------------------------------------------------------------+ void SortNodesByPrice(bool bullish) { //--- Insertion sort — ascending for bullish, descending for bearish for(int i = 1; i < totalNodes; i++) { GraphNode key = g_nodes[i]; int j = i - 1; while(j >= 0) { bool swap = bullish ? (g_nodes[j].price > key.price) : (g_nodes[j].price < key.price); if(!swap) break; g_nodes[j+1] = g_nodes[j]; j--; } g_nodes[j+1] = key; } for(int k = 0; k < totalNodes; k++) g_nodes[k].id = k; } //+------------------------------------------------------------------+ //| Connect Nodes | //+------------------------------------------------------------------+ void ConnectConsecutiveNodes() { for(int i = 0; i < totalNodes - 1; i++) { double cap = ComputeEdgeCapacity(g_nodes[i], g_nodes[i+1]); int sz = totalEdges; ArrayResize(g_edges, sz + 1); g_edges[sz].fromNode = i; g_edges[sz].toNode = i + 1; g_edges[sz].capacity = cap; g_edges[sz].flow = 0.0; totalEdges++; } }
Здесь мы строим направленный рыночный граф, который будет анализироваться с помощью движка расчета ликвидности и пропускной способности на основе алгоритма Форда—Фалкерсона. Сначала очищаются все ранее построенные данные графа и создается исходный узел по текущей рыночной цене. Далее советник сканирует соответствующие ценовые дисбалансы (FVG), ордер-блоки и свинговые точки, расположенные между текущей ценой и выбранной целевой ликвидностью. Включаются только структуры, соответствующие заданному направлению, что гарантирует наличие бычьих структур в бычьих графах, в то же время, наличие медвежьих структур в медвежьих графах. После сбора всех допустимых структур целевой пул ликвидности добавляется в качестве узла-приемника, создавая полный путь от текущего положения цены на рынке до выбранной целевой зоны.
Далее граф организуется и подготавливается для анализа потоков. Все узлы сортируются по цене, так что граф следует логической последовательности к целевой ликвидности, а последовательные узлы соединяются ребрами, пропускная способность которых рассчитывается на основе силы базовых рыночных структур. Эти пропускные способности хранятся в матричном представлении, которое может эффективно обрабатываться движком анализа потоков. Перед запуском алгоритма советник проверяет наличие слабых узких мест, которые могут указывать на недостаточную ликвидность и пропускную способность на данном участке пути. Если граф проходит этот этап проверки, выполняется реализация алгоритма Форда—Фалкерсона, предложенная Эдмондсом—Карпом для расчета максимального потока от узла-источника к узлу-приемнику, что дает количественную оценку способности цены пройти этот путь в обнаруженной сети рыночной структуры.
//+------------------------------------------------------------------+ //| CAPACITY FORMULA | //+------------------------------------------------------------------+ double ComputeEdgeCapacity(GraphNode &from, GraphNode &to) { double avgVol = AverageVolume(20); double curVol = (double)iVolume(g_symbol, PERIOD_CURRENT, 1); double volScore = (avgVol > 0) ? MathMin(curVol / avgVol, 3.0) * 4.0 : 2.0; double priceDiff = MathAbs(to.price - from.price); double reactionPips = priceDiff / g_pipValue; double reactionScore = MathMin(reactionPips / 20.0, 1.0) * 5.0; double distScore = MathMin(priceDiff / (g_pipValue * 50.0), 1.0) * 4.0; double structScore = NodeStructureScore(to.type); double cap = W_Volume * volScore + W_Reaction * reactionScore + W_Distance * distScore + W_Structure* structScore; return MathMax(cap, 0.1); } //+------------------------------------------------------------------+ //| Node Structure Score | //+------------------------------------------------------------------+ double NodeStructureScore(NodeType t) { switch(t) { case NODE_LIQUIDITY_BUY: case NODE_LIQUIDITY_SELL: return 8.0; case NODE_OB_BULL: case NODE_OB_BEAR: return 5.0; case NODE_FVG_BULL: case NODE_FVG_BEAR: return 4.0; case NODE_SWING_HIGH: case NODE_SWING_LOW: return 3.0; default: return 1.0; } } //+------------------------------------------------------------------+ //| Average Volume | //+------------------------------------------------------------------+ double AverageVolume(int period) { double sum = 0; for(int i = 1; i <= period; i++) sum += (double)iVolume(g_symbol, PERIOD_CURRENT, i); return (period > 0) ? sum / period : 1.0; } //+------------------------------------------------------------------+ //| Minimum Edge Capacity | //+------------------------------------------------------------------+ double MinEdgeCapacity() { double minCap = DBL_MAX; for(int i = 0; i < totalEdges; i++) if(g_edges[i].capacity < minCap) minCap = g_edges[i].capacity; return (minCap == DBL_MAX) ? 0.0 : minCap; } //+------------------------------------------------------------------+ //| FORD-FULKERSON — EDMONDS-KARP (flat 1D matrix) | //+------------------------------------------------------------------+ double EdmondsKarp(int source, int sink, int N) { if(N < 2 || source == sink) return 0.0; //--- Build residual as flat 1D array double residual[]; ArrayResize(residual, N * N); for(int i = 0; i < N; i++) for(int j = 0; j < N; j++) residual[Idx(i,j,N)] = g_capFlat[Idx(i,j,N)]; double maxFlow = 0.0; while(true) { //--- BFS — find augmenting path int parent[]; double pathFlow[]; int bfsQueue[]; ArrayResize(parent, N); ArrayFill(parent, 0, N, -1); ArrayResize(pathFlow, N); ArrayFill(pathFlow, 0, N, 0.0); ArrayResize(bfsQueue, N); parent[source] = source; pathFlow[source] = 1e18; int qHead = 0, qTail = 0; bfsQueue[qTail++] = source; while(qHead < qTail && parent[sink] == -1) { int u = bfsQueue[qHead++]; for(int v = 0; v < N; v++) { if(parent[v] == -1 && residual[Idx(u,v,N)] > 1e-9) { parent[v] = u; pathFlow[v] = MathMin(pathFlow[u], residual[Idx(u,v,N)]); if(v == sink) break; if(qTail < N) bfsQueue[qTail++] = v; } } } if(parent[sink] == -1) break; // no augmenting path found double augFlow = pathFlow[sink]; maxFlow += augFlow; //--- Update residual along path int v = sink; while(v != source) { int u = parent[v]; residual[Idx(u,v,N)] -= augFlow; residual[Idx(v,u,N)] += augFlow; v = u; } } return maxFlow; }
В этом разделе определяется пропускная способность каждого ребра в графе рыночной структуры. Мы рассчитываем показатель ликвидности и пропускной способности, комбинируя несколько факторов, описывающих качество пути между двумя соединенными узлами. Компонент объема измеряет текущее участие в рынке относительно недавней активности, а компонент реакции оценивает движение цен между структурами. Показатель расстояния вознаграждает более крупные и более чистые движения, а показатель структуры отражает важность целевого узла. Пулы ликвидности получают наивысший показатель, поскольку они представляют основные рыночные цели, за ними следуют ордер-блоки, ценовые дисбалансы (FVG) и свинговые точки. Эти отдельные компоненты взвешиваются и объединяются для получения окончательной пропускной способности ребра, используемой движком потока.
Во второй части реализован вариант алгоритма Форда—Фалкерсона, предложенный Эдмондсом—Карпом, для расчета максимального потока ликвидности и пропускной способности через граф. Перед началом вычислений алгоритм строит остаточную сеть на основе матрицы пропускной способности графа и определяет самое слабое ребро с помощью проверки на наличие узкого места. Далее он многократно использует поиск в ширину для нахождения увеличивающих путей от узла-источника к узлу-приемнику. Для каждого допустимого пути алгоритм определяет, какой дополнительный поток может пройти через сеть, и соответствующим образом обновляет остаточные пропускные способности. Этот процесс продолжается до тех пор, пока больше нельзя найти увеличивающие пути, после чего накопленный поток представляет собой максимальную доступную способность цены пройти этот путь между текущим положением цены на рынке и выбранной целевой ликвидностью.
//+------------------------------------------------------------------+ //| ICT ENTRY CONDITIONS (DIRECTIONAL SIGNAL) | //+------------------------------------------------------------------+ bool CheckBullEntry(double price) { bool inFVG = false; for(int i = 0; i < ArraySize(g_fvgs); i++) if(g_fvgs[i].bullish && price >= g_fvgs[i].lower && price <= g_fvgs[i].upper) { inFVG = true; break; } bool nearOB = false; for(int i = 0; i < ArraySize(g_obs); i++) if(g_obs[i].bullish && price >= g_obs[i].low && price <= g_obs[i].high * 1.002) { nearOB = true; break; } bool liqAbove = false; for(int i = 0; i < ArraySize(g_liq); i++) if(g_liq[i].buySide && g_liq[i].price > price) { liqAbove = true; break; } return (inFVG || nearOB) && liqAbove; } //+------------------------------------------------------------------+ //| Check Bearish Entry | //+------------------------------------------------------------------+ bool CheckBearEntry(double price) { bool inFVG = false; for(int i = 0; i < ArraySize(g_fvgs); i++) if(!g_fvgs[i].bullish && price >= g_fvgs[i].lower && price <= g_fvgs[i].upper) { inFVG = true; break; } bool nearOB = false; for(int i = 0; i < ArraySize(g_obs); i++) if(!g_obs[i].bullish && price >= g_obs[i].low * 0.998 && price <= g_obs[i].high) { nearOB = true; break; } bool liqBelow = false; for(int i = 0; i < ArraySize(g_liq); i++) if(!g_liq[i].buySide && g_liq[i].price < price) { liqBelow = true; break; } return (inFVG || nearOB) && liqBelow; } //+------------------------------------------------------------------+ //| Get Directional Signal | //+------------------------------------------------------------------+ SignalDir GetDirectionalSignal(double &maxFlow, double &entry, double &sl, double &tp) { double curPrice = SymbolInfoDouble(g_symbol, SYMBOL_BID); LiquidityNode bullT, bearT; double bullFlow = 0.0, bearFlow = 0.0; if(FindNearestLiquidity(curPrice, true, bullT)) bullFlow = BuildDirectionalGraph(curPrice, bullT, true); if(FindNearestLiquidity(curPrice, false, bearT)) bearFlow = BuildDirectionalGraph(curPrice, bearT, false); bool bullSig = CheckBullEntry(curPrice); bool bearSig = CheckBearEntry(curPrice); if(bullSig && bullFlow >= bearFlow) { maxFlow = bullFlow; entry = SymbolInfoDouble(g_symbol, SYMBOL_ASK); sl = GetBullishSL(curPrice); tp = GetTP(entry, sl, true); return SIG_BUY; } if(bearSig && bearFlow > bullFlow) { maxFlow = bearFlow; entry = SymbolInfoDouble(g_symbol, SYMBOL_BID); sl = GetBearishSL(curPrice); tp = GetTP(entry, sl, false); return SIG_SELL; } maxFlow = MathMax(bullFlow, bearFlow); return SIG_NONE; } //+------------------------------------------------------------------+ //| MULTI-TIMEFRAME FLOW (LAYER 9) | //+------------------------------------------------------------------+ double ComputeMTFFlow() { return MTF_Weight_Fast * ComputeSingleTFFlow(TF_Fast) + MTF_Weight_Mid * ComputeSingleTFFlow(TF_Mid) + MTF_Weight_Slow * ComputeSingleTFFlow(TF_Slow) + MTF_Weight_Major * ComputeSingleTFFlow(TF_Major); } //+------------------------------------------------------------------+ //| Compute Single TimeFrame Flow | //+------------------------------------------------------------------+ double ComputeSingleTFFlow(ENUM_TIMEFRAMES tf) { double price = SymbolInfoDouble(g_symbol, SYMBOL_BID); int bars = iBars(g_symbol, tf); if(bars < 20) return 0.0; double bestBuyDist = DBL_MAX, bestSellDist = DBL_MAX; int limit = (int)MathMin(bars, LiquidityLookback); for(int i = 1; i < limit; i++) { double h = iHigh(g_symbol, tf, i); double l = iLow(g_symbol, tf, i); if(h > price && (h - price) < bestBuyDist) bestBuyDist = h - price; if(l < price && (price - l) < bestSellDist) bestSellDist = price - l; } if(bestBuyDist == DBL_MAX && bestSellDist == DBL_MAX) return 0.0; double avgVol = 0.0; int structs = 0; for(int i = 1; i < 20; i++) { avgVol += (double)iVolume(g_symbol, tf, i); double rng = iHigh(g_symbol,tf,i) - iLow(g_symbol,tf,i); if(rng > AverageCandleSize(i, 5) * 1.5) structs++; } avgVol /= 19.0; double volRatio = (avgVol > 0) ? (double)iVolume(g_symbol,tf,1) / avgVol : 1.0; double distBonus= (bestBuyDist < DBL_MAX) ? MathMin(50.0 / (bestBuyDist / g_pipValue), 5.0) : 0.0; return volRatio + structs * 0.5 + distBonus; }
В этом разделе определяются условия входа на основе ICT, которые определяют наличие бычьего или медвежьего сетапа. Для бычьего сетапа мы проверяем, торгуется ли текущая цена внутри бычьего ценового дисбаланса (FVG) или вблизи бычьего ордер-блока, а также подтверждаем наличие ликвидность на стороне покупателей выше рыночной. Медвежья логика следует тому же подходу, но в обратном порядке: поиск медвежьих ценовых дисбалансов (FVG), медвежьих ордер-блоков и доступной ликвидности со стороны продавцов ниже текущей цены. Эти условия гарантируют, что сделки рассматриваются только тогда, когда цена взаимодействует с корректной рыночной структурой и имеет четкую целевую ликвидность в нужном направлении.
Затем логика направленного сигнала объединяет эти условия ICT с анализом ликвидности и пропускной способности Форда—Фалкерсона. Строятся отдельные бычьи и медвежьи графы, и рассчитываются их максимальные значения потока для измерения доступной пропускной способности цены по отношению к ближайшим пулам ликвидности. Сторона с более сильным допустимым потоком выбирается в качестве предпочтительного направления торговли, и подготавливаются соответствующие уровни цены входа, стоп-лосса и тейк-профита. Мультитаймфреймовый движок расчета потока расширяет этот анализ, рассчитывая показатели потока по нескольким таймфреймам и объединяя их с использованием взвешенных средних. Это обеспечивает более широкое представление о рыночных условиях, позволяя более старшим таймфреймам оказывать большее влияние, при этом включая информацию о краткосрочной ликвидности и структуре в окончательную оценку потока.
//+------------------------------------------------------------------+ //| Stop-Loss / Take-Profit | //+------------------------------------------------------------------+ double GetBullishSL(double price) { double sl = price - 50.0 * g_pipValue; for(int i = 0; i < ArraySize(g_obs); i++) if(g_obs[i].bullish && g_obs[i].low < price) sl = MathMax(sl, g_obs[i].low - SL_BufferPips * g_pipValue); for(int i = 0; i < ArraySize(g_swingLows); i++) if(g_swingLows[i].price < price) sl = MathMax(sl, g_swingLows[i].price - SL_BufferPips * g_pipValue); return NormalizeDouble(sl, g_digits); } double GetBearishSL(double price) { double sl = price + 50.0 * g_pipValue; for(int i = 0; i < ArraySize(g_obs); i++) if(!g_obs[i].bullish && g_obs[i].high > price) sl = MathMin(sl, g_obs[i].high + SL_BufferPips * g_pipValue); for(int i = 0; i < ArraySize(g_swingHighs); i++) if(g_swingHighs[i].price > price) sl = MathMin(sl, g_swingHighs[i].price + SL_BufferPips * g_pipValue); return NormalizeDouble(sl, g_digits); } //+------------------------------------------------------------------+ //| Get Take-Profit | //+------------------------------------------------------------------+ double GetTP(double entry, double sl, bool bullish) { double risk = MathAbs(entry - sl); return bullish ? NormalizeDouble(entry + risk * TP_RR_Ratio, g_digits) : NormalizeDouble(entry - risk * TP_RR_Ratio, g_digits); } //+------------------------------------------------------------------+ //| DYNAMIC RISK SIZING (LAYER 7) | //+------------------------------------------------------------------+ double DynamicRisk(double flow) { double ratio = MathMin(flow / PeakFlowRef, 1.0); return BaseRiskPercent + ratio * (MaxRiskPercent - BaseRiskPercent); } //+------------------------------------------------------------------+ //| Calculate Lot Size | //+------------------------------------------------------------------+ double CalcLotSize(double riskPct, double slDist) { if(slDist <= 0) return SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MIN); double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmt = balance * riskPct / 100.0; double tickVal = SymbolInfoDouble(g_symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(g_symbol, SYMBOL_TRADE_TICK_SIZE); double lotStep = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_STEP); double minLot = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MAX); if(tickVal <= 0 || tickSize <= 0) return minLot; double slTicks = slDist / tickSize; double lots = riskAmt / (slTicks * tickVal); lots = MathFloor(lots / lotStep) * lotStep; return MathMax(minLot, MathMin(maxLot, lots)); } //+------------------------------------------------------------------+ //| UTILITIES | //+------------------------------------------------------------------+ int CountOpenTrades() { int count = 0; for(int i = PositionsTotal()-1; i >= 0; i--) if(PositionSelectByTicket(PositionGetTicket(i))) if(PositionGetString(POSITION_SYMBOL) == g_symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) count++; return count; } //+------------------------------------------------------------------+ //| DASHBOARD | //+------------------------------------------------------------------+ void ShowDashboard(SignalDir sig, double flow) { string dir = (sig == SIG_BUY) ? "BUY [LONG]" : (sig == SIG_SELL) ? "SELL [SHORT]" : "FLAT"; string qual = (flow > FlowThreshold) ? "QUALIFIED" : "FILTERED"; Comment( "\n══════════════════════════════════\n" " Ford-Fulkerson Liquidity EA\n" "══════════════════════════════════\n" " Symbol : ", g_symbol, "\n" " Signal : ", dir, "\n" " Max Flow : ", DoubleToString(flow, 2), "\n" " Threshold : ", DoubleToString(FlowThreshold, 2), "\n" " Status : ", qual, "\n" "──────────────────────────────────\n" " Swings H/L : ", ArraySize(g_swingHighs), " / ", ArraySize(g_swingLows), "\n" " FVGs : ", ArraySize(g_fvgs), "\n" " OrderBlocks: ", ArraySize(g_obs), "\n" " Liq Pools : ", ArraySize(g_liq), "\n" " Open Trades: ", CountOpenTrades(), "\n" "══════════════════════════════════" ); } //+------------------------------------------------------------------+
Наконец, этот раздел управляет уровнями защиты торговли путем расчета цен стоп-лосса и тейк-профита на основе обнаруженных рыночных структур. Для бычьих сделок стоп-лосс первоначально размещается ниже текущей цены, а затем корректируется с использованием ближайших бычьих ордер-блоков и минимумов свингов. Аналогичный процесс применяется в обратном порядке для медвежьих сделок, где в качестве защитных уровней используются ближайшие медвежьи ордер-блоки и максимумы свингов. К этим структурам добавляется настраиваемый буфер для обеспечения дополнительного пространства для обычных рыночных колебаний. После установки стоп-лосса уровень тейк-профита рассчитывается автоматически с использованием настроенного соотношения риск/прибыль, гарантируя, что целевые показатели прибыли остаются пропорциональными уровню риска, принимаемого по каждой сделке.
Управление рисками тесно связано с результатами работы движка управления ликвидностью и пропускной способностью Форда—Фалкерсона. Функция DynamicRisk() увеличивает позиционный риск по мере улучшения силы потока, позволяя более сильным путям с большей ликвидностью и пропускной способностью получать большую долю риска/капитала, в то время как более слабые пути получают меньшую подверженность риску. Далее расчет размера лота преобразует выбранный процент риска в фактический торговый объем с учетом баланса счета, расстояния стоп-лосса, значения тика и ограничений объема брокера. Такой подход гарантирует, что размер позиции рассчитывается единообразно независимо от рыночных условий или характеристик инструмента.
Остальные вспомогательные функции поддерживают управление и мониторинг сделок. Счетчик открытых сделок отслеживает активные позиции, связанные с советником, и предотвращает превышение установленного лимита новыми сделками. Дашборд предоставляет сводку состояния советника в реальном времени непосредственно на графике, включая текущий сигнал, максимальный поток, статус квалификации сделки, обнаруженные рыночные структуры, количество пулов ликвидности и количество открытых позиций. Это позволяет наглядно видеть как движок Форда—Фалкерсона интерпретирует рынок и подходят ли текущие условия для торговли.
Бэктестирование
Бэктестирование было проведено в течение примерно за двухмесячный период тестирования с 01 апреля 2026 года по 30 мая 2026 года со следующими настройками:

Ниже представлена кривая эквити и результаты бэктестирования:


Заключение
Благодаря этой реализации с нуля был создан полноценный многоуровневый советник на MQL5. Структура рынка определялась автоматически — максимумы и минимумы свингов, ценовые дисбалансы (FVG), ордер-блоки и пулы ликвидности — все это идентифицировалось и преобразовывалось в узлы графа. Далее был построен ориентированный граф для бычьего и медвежьего сценариев, причем каждому ребру присваивался показатель пропускной способности, определяемый объемом, силой реакции, расстоянием и качеством структуры. Затем по этому графу был применен алгоритм Форда—Фалкерсона, реализованный с использованием варианта алгоритма Эдмондса—Карпа BFS, для определения максимального потока. Условия входа в ICT были встроены как направленный фильтр, динамическое определение размера позиции было привязано к выходным данным потока, и поверх этого был добавлен слой агрегации потоков по нескольким таймфреймам. Каждый компонент имеет определенную роль, и ни одно действие не выполняется без прохождения полной цепочки квалификации сигнала.
Прочитав статью, трейдер получает нечто труднодоступное — логический мост между дискреционным анализом ICT и количественным принятием решений. Он уже умел распознавать структуру, но не всегда понимал, когда эта структура достаточно прочна, чтобы действовать. Теперь у него есть подход, который напрямую отвечает на этот вопрос. Сетапы больше не принимаются или отклоняются исключительно на основе инстинкта. Сеть либо обладает достаточной пропускной способностью, либо нет, и этот ответ вычисляется, а не определяется интуитивно. Помимо самого советника, трейдер получает новый способ восприятия структуры рынка: сеть взаимосвязанных узлов, через которые должна перемещаться ликвидность. Один только этот сдвиг в мышлении стоит больше, чем любой отдельный торговый сигнал, который когда-либо сгенерирует система.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22883
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
От начального до среднего уровня: Очереди, списки и деревья (II)
Возможности Мастера MQL5, которые вам нужно знать (Часть 81): Использование паттернов Ишимоку и ADX Уайлдера в обучении на основе β-VAE-инференса
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования