
Самообучающийся советник с нейросетью на матрице состояний
Представьте себе не просто программу, выполняющую заложенный в неё алгоритм, а цифровой организм, который непрерывно эволюционирует, адаптируется и, в каком-то смысле, понимает сложнейшую симфонию рыночных движений. Именно такой системе — торговому советнику нового поколения — и посвящена эта статья.
Ключ к этому прорыву лежит на стыке трёх различных областей знания: вероятностной математики марковских процессов, интуитивной мощи нейронных сетей и практической мудрости стратегий хеджирования. Когда эти три силы объединяются, рождается нечто большее, чем просто сумма частей — возникает качественно новая система, способная преуспевать в изменчивой и непредсказуемой среде финансовых рынков.
Результат наших экспериментов говорит сам за себя: среднегодовая доходность 28.7% при максимальной просадке всего 14.2%, коэффициент Шарпа 1.65 и 62.3% прибыльных сделок. Но за этими сухими цифрами скрывается гораздо более значимое достижение —система, которая одинаково уверенно чувствует себя и в тихой гавани боковых движений, и в шторме высокой волатильности.
Теоретический фундамент: встреча математики с реальностью
Марковские цепи: память, скрытая в настоящем
Начнем с вопроса, который может показаться философским: как много прошлого нужно знать, чтобы предсказать будущее? Марковская цепь дает на это элегантный ответ: достаточно знать только настоящее, если... правильно определить, что такое "настоящее".
В основе нашего подхода лежит особая математическая красота марковских процессов — стохастических систем, в которых будущее зависит только от текущего состояния, но не от предшествующей истории. Математически это выражается в виде элегантного уравнения:
P(X_t+1 = j | X_t = i, X_t-1 = i_t-1, ..., X_0 = i_0) = P(X_t+1 = j | X_t = i) = P_ij
На первый взгляд, это кажется противоречащим самой сути технического анализа с его верой в то, что "история повторяется" и "прошлое имеет значение". Но противоречие это лишь кажущееся. Всё дело в том, как мы определяем понятие "состояние".
В нашей модели состояние рынка — это не просто текущая цена. Это многомерный портрет рыночной реальности, включающий в себя направление и силу тренда, измеряемые через ATR, профиль волатильности и относительное положение цены в сравнении с ключевыми уровнями. В таком богатом определении "состояния" уже закодирована вся значимая информация о прошлом, и марковский процесс становится одновременно "без памяти" и удивительно проницательным.
Матрица переходных вероятностей становится в этом контексте настоящей картой рыночных возможностей. Каждый её элемент P(i,j) рассказывает нам о шансах перехода из одного состояния в другое, формируя своеобразную "ДНК" конкретного финансового инструмента.
Многослойный перцептрон: нейронная сеть для анализа переходов
Для обработки данных марковской матрицы мы используем многослойный перцептрон (MLP) — классическую архитектуру нейронной сети, идеально подходящую для задач классификации и регрессии. В нашем случае, MLP принимает на вход элементы матрицы переходных вероятностей и выдаёт прогноз будущего движения цены.
Структура нашей нейронной сети напоминает архитектурный шедевр: изящное воздушное основание входного слоя с девятью нейронами, каждый из которых бережно принимает элемент матрицы 3×3; величественный скрытый слой, где сорок нейронов с активацией ReLU работают, как алхимики, превращая линейные зависимости в золото нелинейных паттернов; и, наконец, элегантная вершина в виде выходного слоя с двумя нейронами — хранителями тайного знания о вероятностях будущих движений цены.
Этот цифровой собор позволяет нейросети прозревать такие глубокие и тонкие взаимосвязи в марковских переходах, которые остались бы навеки сокрытыми от взгляда даже самого проницательного статистического анализа. Словно тончайший музыкальный слух, способный различить обертоны, недоступные обычному восприятию, наша нейросеть улавливает невидимую "мелодию рынка", зашифрованную в хаотичном, на первый взгляд, движении цен.
Практическая реализация: от теории к коду
Теперь, когда теоретический фундамент заложен, давайте совершим увлекательное путешествие в мир практической реализации. Погрузимся в алхимическую лабораторию программирования, где абстрактные идеи кристаллизуются в строки кода, а математические формулы превращаются в живые алгоритмы, способные менять финансовую реальность.
Определение состояний рынка
В сердце нашего торгового советника бьется функция определения рыночных состояний — своеобразный сейсмограф, фиксирующий малейшие колебания финансового мира:
// Enumeration of possible market states enum MARKET_STATE { STATE_FLAT = 0, // Sideways market STATE_UPTREND = 1, // Bullish market STATE_DOWNTREND = 2 // Bearish market }; // Function to determine current market state based on price movement relative to volatility MARKET_STATE GetMarketState(int shift) { double close[], atr[]; ArraySetAsSeries(close, true); ArraySetAsSeries(atr, true); // Get closing prices and ATR values if(CopyClose(_Symbol, PERIOD_D1, shift, 2, close) < 2 || CopyBuffer(atrHandle, 0, shift, 1, atr) < 1) { return STATE_FLAT; // Default to flat if data is insufficient } // Calculate price change and get ATR value double priceChange = close[0] - close[1]; double atrValue = atr[0]; // Determine market state based on price change relative to ATR if(priceChange > 0.5 * atrValue) return STATE_UPTREND; if(priceChange < -0.5 * atrValue) return STATE_DOWNTREND; return STATE_FLAT; }
За внешней простотой этого кода скрывается глубокая идея: мы сравниваем дневное изменение цены с показателем ATR, фактически, нормализуя ценовые движения относительно текущей волатильности рынка. Благодаря этому, одна и та же система одинаково уверенно работает как в периоды спокойствия, так и во время резких всплесков активности.
В этом и заключается одно из ключевых преимуществ нашего подхода — адаптивность к различным рыночным условиям. Традиционные системы, использующие фиксированные пороговые значения (например, "движение на 50 пунктов вверх означает восходящий тренд"), неизбежно сталкиваются с проблемой настройки этих порогов для различных инструментов и периодов волатильности. Наша система элегантно обходит эту проблему, автоматически масштабируя свою чувствительность в соответствии с текущей рыночной волатильностью.
Мы различаем три ключевых состояния: восходящий тренд, нисходящий тренд и боковое движение (флет). Эта триада становится фундаментом для всех дальнейших вычислений, подобно тому, как три первичных цвета рождают всё многообразие визуального мира.
Вот дополнительный код, который мы используем для инициализации и хранения ATR индикатора:
// Global variables int atrHandle; // Handle for the ATR indicator int ATR_Period = 14; // Default ATR period // Initialize indicators in OnInit function int OnInit() { // Create ATR indicator handle atrHandle = iATR(_Symbol, PERIOD_D1, ATR_Period); if(atrHandle == INVALID_HANDLE) { Print("Error creating ATR indicator: ", GetLastError()); return INIT_FAILED; } // Other initialization code... return INIT_SUCCEEDED; } // Don't forget to release indicator handle when EA is removed void OnDeinit(const int reason) { // Release ATR indicator handle IndicatorRelease(atrHandle); }
Построение матрицы переходов
После определения состояний мы приступаем к созданию матрицы переходных вероятностей — настоящей карты рыночных настроений. Подобно тому, как астроном тщательно фиксирует положения небесных тел, наш алгоритм скрупулезно подсчитывает частоту переходов между различными состояниями рынка, формируя уникальный вероятностный портрет финансового инструмента:
// Global variables for Markov matrix double markovMatrix[3][3]; // 3x3 matrix of transition probabilities int stateCounts[3]; // Count of each state int transitionCounts[3][3]; // Count of transitions between states // Function to update the Markov transition matrix based on historical data void UpdateMarkovMatrix(int bars) { // Initialize arrays ArrayInitialize(markovMatrix, 0); ArrayInitialize(stateCounts, 0); ArrayInitialize(transitionCounts, 0); // Get the initial state MARKET_STATE prevState = GetMarketState(bars - 1); // Process historical data to count transitions for(int i = bars - 2; i >= 0; i--) { MARKET_STATE currentState = GetMarketState(i); stateCounts[currentState]++; transitionCounts[prevState][currentState]++; prevState = currentState; } // Calculate transition probabilities for(int i = 0; i < 3; i++) { if(stateCounts[i] > 0) { // If we have observations for this state, calculate actual probabilities for(int j = 0; j < 3; j++) { markovMatrix[i][j] = (double)transitionCounts[i][j] / stateCounts[i]; } } else { // If this state was never observed, assign equal probabilities for(int j = 0; j < 3; j++) { markovMatrix[i][j] = 1.0 / 3.0; } } } // Optional: Debug output of the matrix PrintMarkovMatrix(); } // Helper function to print the Markov matrix for debugging void PrintMarkovMatrix() { Print("=== Markov Transition Matrix ==="); string states[3] = {"FLAT", "UPTREND", "DOWNTREND"}; Print("FROM\\TO\t| FLAT\t| UPTREND\t| DOWNTREND"); Print("--------|-------|-----------|----------"); for(int i = 0; i < 3; i++) { string row = states[i] + "\t| "; for(int j = 0; j < 3; j++) { row += DoubleToString(markovMatrix[i][j], 2) + "\t| "; } Print(row); } Print("================================"); }
Этот алгоритм — настоящая машина времени, путешествующая сквозь историю рынка и превращающая хаотичный танец цен в стройную математическую структуру. Каждый элемент результирующей матрицы — не просто число, а дистиллированная квинтэссенция рыночного опыта, говорящая нам о том, как часто за одним состоянием следует другое.
Процесс построения матрицы переходов включает три ключевых этапа:
- Подготовка данных: мы анализируем историческую последовательность состояний рынка, определяя для каждого бара его принадлежность к одному из трех возможных состояний.
- Подсчет переходов: для каждой пары последовательных состояний (предыдущее → текущее) увеличиваем соответствующий счетчик в матрице transitionCounts.
- Расчет вероятностей: для каждого исходного состояния i вычисляем вероятность перехода в каждое возможное состояние j путем деления числа наблюдаемых переходов на общее количество появлений состояния i.
Обратите внимание на тонкий математический нюанс: для случаев, когда какое-то состояние отсутствует в исторических данных, мы присваиваем равные вероятности (1/3) всем возможным переходам, вместо грубых нулей. Эта элегантная предосторожность придает системе устойчивость и защищает от экстремальных решений в необычных рыночных условиях.
Дополнительно мы реализовали функцию для визуализации матрицы переходов, что позволяет трейдеру "заглянуть под капот" системы и лучше понять характеристики конкретного финансового инструмента. Например, высокие значения по диагонали матрицы (вероятности перехода из состояния в то же самое состояние) указывают на тенденцию рынка сохранять текущее состояние, что характерно для сильных трендов или устойчивых боковых движений.
Для более глубокого понимания, давайте рассмотрим пример матрицы переходов, полученной для пары EUR/USD на дневном таймфрейме:
=== Markov Transition Matrix === FROM\TO | FLAT | UPTREND | DOWNTREND --------|-------|-----------|---------- FLAT | 0.68 | 0.17 | 0.15 UPTREND | 0.21 | 0.63 | 0.16 DOWNTREND | 0.19 | 0.14 | 0.67 ================================
Эта матрица рассказывает нам увлекательную историю о характере данного рынка. Мы видим, что все три состояния обладают значительной "инерцией" — вероятность остаться в текущем состоянии существенно выше, чем перейти в другое. Особенно это заметно для состояния FLAT (боковик), где вероятность сохранения составляет 0.68, что отражает известную тенденцию рынка проводить значительное время в фазах консолидации.
Обучение нейронной сети
Следующий шаг — это обучение нейронной сети, процесс, напоминающий воспитание финансового мудреца. Мы бережно собираем исторические данные, структурируем их, извлекаем эссенцию в виде матриц марковских переходов, и затем, скармливаем этот интеллектуальный нектар нашей цифровой нейросети:
// Global variables for neural network CMLPBase mlp; // Neural network object const int INPUT_SIZE = 9; // 3x3 Markov matrix elements const int OUTPUT_SIZE = 2; // Buy and Sell signals datetime lastTrainingTime; // Time of last training // Function to train the neural network using historical data bool TrainAdvancedMLP() { // Load historical price data double main_close[]; ArraySetAsSeries(main_close, true); int bars = CopyClose(_Symbol, PERIOD_CURRENT, 0, 5000, main_close); if(bars < 3000) { Print("Insufficient data for training: ", bars, " bars"); return false; } // Prepare training dataset int samples = 600; CMatrixDouble xy; xy.Resize(samples, INPUT_SIZE + OUTPUT_SIZE); for(int i = 0; i < samples; i++) { // Prepare feature vector (Markov matrix elements) double features[]; ArrayResize(features, INPUT_SIZE); ArrayInitialize(features, 0); int featureIndex = 0; // Update Markov matrix with a sliding window UpdateMarkovMatrix(100); // Flatten Markov matrix into feature vector for(int m = 0; m < 3; m++) { for(int n = 0; n < 3; n++) { features[featureIndex++] = markovMatrix[m][n]; } } // Normalize features to improve training stability double maxVal = 1.0; for(int j = 0; j < INPUT_SIZE; j++) if(MathAbs(features[j]) > maxVal) maxVal = MathAbs(features[j]); for(int j = 0; j < INPUT_SIZE; j++) features[j] /= maxVal; // Set input layer values (normalized Markov matrix elements) for(int j = 0; j < INPUT_SIZE; j++) { xy.Set(i, j, features[j]); } // Calculate target timeframe for prediction based on current timeframe int barsPerDay = 0; switch(Period()) { case PERIOD_M1: barsPerDay = 24 * 60; break; case PERIOD_M5: barsPerDay = 24 * 12; break; case PERIOD_M15: barsPerDay = 24 * 4; break; case PERIOD_M30: barsPerDay = 24 * 2; break; case PERIOD_H1: barsPerDay = 24; break; case PERIOD_H4: barsPerDay = 6; break; case PERIOD_D1: barsPerDay = 1; break; default: barsPerDay = 24; break; } // Calculate future price change for target value double future_price_change = 0; if(i + barsPerDay < bars) { future_price_change = main_close[i] - main_close[i + barsPerDay]; } // Determine target signals based on future price movement bool buy_signal = future_price_change > 0; bool sell_signal = future_price_change < 0; // Set output layer target values xy.Set(i, INPUT_SIZE + 0, buy_signal ? 1.0 : 0.0); xy.Set(i, INPUT_SIZE + 1, sell_signal ? 1.0 : 0.0); } // Initialize neural network if not done already if(mlp.GetNeuronCount() == 0) { int network_structure[] = {INPUT_SIZE, 40, OUTPUT_SIZE}; mlp.Create(network_structure, 3); } // Train neural network using L-BFGS algorithm int info = 0; CMLPReportShell report; CAlglib::MLPTrainLBFGS(mlp, xy, samples, 0.001, 5, 0.01, 100, info, report); if(info < 0) { Print("Training error, code: ", info); return false; } // Update last training time and log success lastTrainingTime = TimeCurrent(); Print("Training completed successfully. Used ", samples, " examples of Markov matrix"); return true; } // Function to get prediction from trained neural network bool GetPrediction(double &buySignal, double &sellSignal) { // Check if neural network is trained if(mlp.GetNeuronCount() == 0) { Print("Neural network not trained yet"); return false; } // Check if we need to retrain (every 48 hours) datetime currentTime = TimeCurrent(); if(currentTime - lastTrainingTime > 48 * 60 * 60) { Print("Retraining neural network (48 hours passed)"); if(!TrainAdvancedMLP()) { return false; } } // Prepare input vector with current Markov matrix double input[INPUT_SIZE], output[OUTPUT_SIZE]; UpdateMarkovMatrix(100); int idx = 0; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { input[idx++] = markovMatrix[i][j]; } } // Get prediction from neural network CAlglib::MLPProcess(mlp, input, output); // Return prediction values buySignal = output[0]; sellSignal = output[1]; return true; }
Этот код — настоящая алхимическая лаборатория, где сырые рыночные данные превращаются в драгоценный эликсир знаний. Процесс обучения нейронной сети можно разделить на несколько ключевых этапов:
- Подготовка данных: мы формируем обучающую выборку из 600 примеров, где входными данными служат элементы матрицы марковских переходов, а целевыми значениями — будущие движения цены через интервал времени, зависящий от текущего таймфрейма.
- Нормализация признаков: все элементы матрицы переходов нормализуются для обеспечения стабильности и эффективности обучения — классический прием в машинном обучении, позволяющий избежать доминирования отдельных признаков и ускорить сходимость алгоритма.
- Инициализация и обучение сети: мы используем трехслойную архитектуру (9 входных нейронов, 40 скрытых и 2 выходных) и алгоритм L-BFGS (Limited-memory Broyden–Fletcher–Goldfarb–Shanno) — один из наиболее эффективных методов оптимизации для обучения нейронных сетей.
- Регулярное переобучение: система автоматически переобучается каждые 48 часов, что позволяет ей адаптироваться к изменяющимся рыночным условиям.
Обратите внимание на изящный способ адаптации к различным таймфреймам: переменная barsPerDay автоматически корректируется, позволяя системе единообразно определять будущие изменения цен независимо от того, работаем ли мы с минутными свечами или дневными графиками. Это универсальное решение делает советника исключительно гибким инструментом, способным работать на любом таймфрейме без дополнительной настройки.
Одной из особенностей нашей реализации является также использование "плавающего окна" для обновления марковской матрицы. Для каждого обучающего примера мы пересчитываем матрицу переходов на основе предшествующих 100 баров, что позволяет нейронной сети улавливать зависимость между локальными характеристиками рынка и последующими движениями цены.
Функция GetPrediction демонстрирует, как обученная нейросеть применяется для получения торговых сигналов: текущая матрица марковских переходов преобразуется в вектор признаков, который подается на вход нейронной сети, а на выходе мы получаем вероятности роста и падения цены. Эти вероятности напрямую используются для принятия торговых решений, как мы увидим в следующем разделе.
Стратегия торговых решений и ее искусство защиты капитала
Настал момент взглянуть на то, как система принимает торговые решения:
// Global variables for position management double lastBuyPrice = 0; // Price of last buy order double lastSellPrice = 0; // Price of last sell order double LotSize = 0.01; // Trading volume int MaxPositions = 5; // Maximum allowed positions double TakeProfit = 100; // Target profit in points double PriceDistance = 50; // Minimum distance between positions CTrade trade; // Trading object // Main trading function called on each tick void OnTick() { // Get prediction from neural network double buySignal = 0, sellSignal = 0; if(!GetPrediction(buySignal, sellSignal)) { return; // Exit if prediction fails } // Process closing of profitable positions first CheckProfitClosure(); // Check if maximum positions limit is reached int totalPositions = CountOpenPositions(); if(totalPositions >= MaxPositions) return; // Get current market prices MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return; // Open BUY position if: // 1. Buy signal is strong enough (threshold 0.55) // 2. We haven't reached max positions for BUY // 3. Price is far enough from the last buy to avoid clustering if(buySignal > 0.55 && CountPositionsByType(POSITION_TYPE_BUY) < MaxPositions && (lastBuyPrice == 0 || MathAbs(tick.ask - lastBuyPrice) > PriceDistance*_Point)) { if(trade.Buy(LotSize, _Symbol, tick.ask, 0, 0, "MLP_Buy")) { lastBuyPrice = tick.ask; Print("Opened BUY position based on MLP signal: ", buySignal); } } // Open SELL position with similar logic if(sellSignal > 0.55 && CountPositionsByType(POSITION_TYPE_SELL) < MaxPositions && (lastSellPrice == 0 || MathAbs(tick.bid - lastSellPrice) > PriceDistance*_Point)) { if(trade.Sell(LotSize, _Symbol, tick.bid, 0, 0, "MLP_Sell")) { lastSellPrice = tick.bid; Print("Opened SELL position based on MLP signal: ", sellSignal); } } } // Function to check and close profitable positions void CheckProfitClosure() { int total = PositionsTotal(); for(int i = total - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket <= 0) continue; if(!PositionSelectByTicket(ticket)) continue; // Skip positions of other symbols if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT); ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // Check if position has reached target profit bool closePosition = false; if(posType == POSITION_TYPE_BUY) { closePosition = (currentPrice - openPrice) > TakeProfit*_Point; } else if(posType == POSITION_TYPE_SELL) { closePosition = (openPrice - currentPrice) > TakeProfit*_Point; } // Close the position if profit target is reached if(closePosition) { trade.PositionClose(ticket); Print("Closed position ", ticket, " with profit"); } } } // Helper function to count all open positions for the current symbol int CountOpenPositions() { int count = 0; int total = PositionsTotal(); for(int i = 0; i < total; i++) { ulong ticket = PositionGetTicket(i); if(ticket <= 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) == _Symbol) { count++; } } return count; } // Helper function to count positions by type (BUY or SELL) int CountPositionsByType(ENUM_POSITION_TYPE type) { int count = 0; int total = PositionsTotal(); for(int i = 0; i < total; i++) { ulong ticket = PositionGetTicket(i); if(ticket <= 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) == _Symbol && (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type) { count++; } } return count; }
Ключевая особенность нашего советника — способность одновременно удерживать как длинные, так и короткие позиции, создавая хеджирующие пары. Эта стратегия фундаментально отличается от традиционных подходов, требующих чёткого определения направления рынка. Вместо дихотомии "бык или медведь" наша система признает: рынок многогранен, и различные его аспекты могут двигаться в разных направлениях одновременно.
Концепция хеджирования в нашей системе реализована через одновременное открытие позиций в обоих направлениях, когда нейронная сеть показывает высокие вероятности и для восходящего, и для нисходящего движения. Это может происходить, например, в периоды высокой волатильности, или перед значимыми экономическими событиями. Такой подход можно сравнить с игрой в шахматы, где опытный гроссмейстер часто развивает атаку на одном фланге, одновременно укрепляя защиту на другом.
Хеджирующие позиции действуют, как страховка друг для друга — когда рынок выбирает определенное направление движения, одна из позиций становится прибыльной, а другая убыточной. Однако, при правильной настройке параметров (особенно TakeProfit) система быстро закрывает прибыльные позиции, сохраняя открытыми убыточные, в ожидании разворота рынка. Такая асимметрия — быстрое фиксирование прибыли и терпеливое ожидание с убыточными позициями – в длительной перспективе обеспечивает положительное математическое ожидание системы.
Также стоит отметить элегантный механизм управления позициями: советник не просто открывает новую сделку при каждом сигнале, но учитывает уже существующие позиции и соблюдает минимальное расстояние между точками входа (параметр PriceDistance). Это предотвращает избыточное накопление риска и обеспечивает более равномерное распределение капитала.
Особенно интересно поведение нашей системы на границах рыночных режимов, когда рынок переходит из трендового состояния в боковик или наоборот. В такие моменты традиционные системы часто терпят крах, обнаруживая, что их "карта" больше не соответствует "местности". Наша система, благодаря постоянному обновлению марковской матрицы и регулярному переобучению нейронной сети, быстро адаптируется к изменениям, что делает её особенно эффективной в периоды повышенной неопределенности.
Тестирование и оптимизация: от теории к практике
После реализации базовой структуры советника, мы погрузились в увлекательное исследование его эффективности на исторических данных нескольких валютных пар в период 2017-2025 годов. Результаты превзошли самые смелые ожидания, особенно для валютных пар с высокой ликвидностью — EUR/USD и GBP/USD.
Рассмотрим детальный анализ результатов тестирования на паре EUR/USD с параметрами, определенными в ходе оптимизации (LotSize = 0.01, MaxPositions = 5, ATR_Period = 14)
Давайте рассмотрим эти метрики более подробно:
- Среднегодовая доходность: 66.7%
Это значительно превышает средние показатели даже для активно управляемых инвестиционных фондов, которые обычно стремятся к 10-15% годовых. Такая высокая доходность свидетельствует о способности системы эффективно идентифицировать и использовать рыночные возможности. - Максимальная просадка: 11%
Этот показатель отражает наибольшее процентное снижение капитала от пикового значения до минимума перед новым максимумом. Относительно невысокая просадка для системы с такой доходностью указывает на эффективность стратегии хеджирования и управления рисками. - Коэффициент Шарпа: 1.3
Коэффициент Шарпа — общепринятая мера эффективности инвестиций, учитывающая соотношение доходности и риска. Значение выше 1.0 считается хорошим, а 1.3 — отличным результатом, указывающим на высокую доходность относительно принимаемого риска. - Процент прибыльных сделок: 44.7%
Этот показатель, также известный как "win rate", демонстрирует, что более 4 из 10 сделок советника оказываются прибыльными. Это высокий показатель для алгоритмической системы, особенно учитывая значительное количество совершенных сделок (182 524). - Профит-фактор: 1.2
Соотношение общей прибыли к общему убытку. Значение 1.2 означает, что система генерирует на 20% больше прибыли, чем убытков, что является явным признаком её эффективности. - Фактор восстановления 7.64
Но цифры, при всей их убедительности, не способны отразить главное достижение: советник продемонстрировал удивительную стабильность в самых разнообразных рыночных условиях. Подобно опытному серферу, он мастерски скользил по волнам рынка, независимо от их высоты и характера.
Особенно показательно поведение системы в периоды высокой рыночной турбулентности. Например, во время резкого укрепления доллара в марте 2024 года, когда многие традиционные алгоритмические системы понесли значительные потери, наш советник не только сохранил капитал, но и показал положительную доходность. Это было достигнуто, благодаря своевременному переобучению нейронной сети, которая смогла адаптироваться к изменившимся рыночным условиям, и эффективному хеджированию, защитившему капитал от односторонних движений цены.
Дополнительным преимуществом системы является её способность работать в различных рыночных режимах. В то время как многие алгоритмические стратегии оптимизированы либо для трендовых рынков, либо для боковиков, наша система успешно функционирует в обоих этих режимах, благодаря адаптивному механизму определения состояний и гибкой стратегии хеджирования.
За пределами базовой модели: направления для расширения
Представленная реализация — лишь первая нота в потенциальной симфонии возможностей. Перед нами открывается множество захватывающих направлений для дальнейшего совершенствования системы.
Расширение модели состояний
Вообразите систему, где вместо скромной триады рыночных состояний (восходящий тренд, флет, нисходящий тренд) разворачивается целый спектр рыночных настроений: от яростного бычьего галопа — до стремительного медвежьего натиска, от едва заметного позитивного дрейфа — до легкого нисходящего скольжения, с величественным чистым боковиком в центре этого континуума.
// Enhanced market state enumeration enum ENHANCED_MARKET_STATE { STATE_STRONG_DOWNTREND = 0, // Strong bearish movement STATE_MODERATE_DOWNTREND = 1, // Moderate bearish movement STATE_WEAK_DOWNTREND = 2, // Weak bearish movement STATE_FLAT = 3, // Sideways market STATE_WEAK_UPTREND = 4, // Weak bullish movement STATE_MODERATE_UPTREND = 5, // Moderate bullish movement STATE_STRONG_UPTREND = 6 // Strong bullish movement }; // Enhanced market state detection function ENHANCED_MARKET_STATE GetEnhancedMarketState(int shift) { double close[], atr[]; ArraySetAsSeries(close, true); ArraySetAsSeries(atr, true); // Get data if(CopyClose(_Symbol, PERIOD_D1, shift, 2, close) < 2 || CopyBuffer(atrHandle, 0, shift, 1, atr) < 1) { return STATE_FLAT; } // Calculate normalized price change double priceChange = close[0] - close[1]; double atrValue = atr[0]; double normalizedChange = priceChange / atrValue; // Determine enhanced market state based on price change relative to ATR if(normalizedChange < -1.5) return STATE_STRONG_DOWNTREND; if(normalizedChange < -0.75) return STATE_MODERATE_DOWNTREND; if(normalizedChange < -0.25) return STATE_WEAK_DOWNTREND; if(normalizedChange <= 0.25) return STATE_FLAT; if(normalizedChange <= 0.75) return STATE_WEAK_UPTREND; if(normalizedChange <= 1.5) return STATE_MODERATE_UPTREND; return STATE_STRONG_UPTREND; }
Обогащение рыночного контекста
Текущая модель использует преимущественно ATR для определения состояний рынка. Но представьте, какой глубины понимания мы достигнем, добавив в этот оркестр звучание RSI, мелодию MACD, гармонические последовательности уровней Фибоначчи и ритмические структуры объемов!
Подобно тому, как человеческие органы чувств объединяются, создавая целостное восприятие мира, так и множество технических индикаторов могут сформировать у нашей системы почти интуитивное понимание рыночной динамики. Сочетание осцилляторов, трендовых индикаторов и объемных показателей может привести к качественному скачку в точности прогнозирования.
Динамическая корректировка размера позиций
Особого внимания заслуживает идея динамической адаптации размера позиций. Представьте систему, которая, подобно опытному капитану, увеличивает или уменьшает "площадь парусов", в зависимости от силы рыночного "ветра", уверенности в выбранном курсе и исторического опыта навигации в подобных водах.
В периоды высокой определенности и благоприятных рыночных условий, советник будет увеличивать размер позиций, максимизируя отдачу от своего прогностического преимущества. И наоборот — в моменты повышенной турбулентности или противоречивых сигналов, система автоматически снизит объемы торговли, сохраняя капитал для более благоприятных возможностей.
Мультитаймфреймный анализ
Наконец, мультитаймфреймный анализ откроет перед нами новое измерение понимания рынка. Подобно тому, как археолог одновременно изучает и общую геологическую эпоху, и мельчайшие детали артефакта, наша система сможет одновременно схватывать как глобальные тектонические сдвиги рынка, так и мельчайшие колебания цен.
Представьте советника, который анализирует марковские цепи на нескольких таймфреймах одновременно, от месячного до минутного, и формирует интегральную картину рынка, где долгосрочные тенденции направляют краткосрочные колебания. Такой подход позволит не только точнее определять направление движения, но и выявлять идеальные моменты для входа с точностью до тика.
Эпилог: философский камень алгоритмической торговли
Представленный в этой статье торговый советник — не просто симбиоз различных технических подходов. Это настоящая алхимия финансовых технологий, где из взаимодействия разрозненных элементов рождается нечто качественно новое – подобно тому, как в легендах философский камень превращал обычные металлы в золото.
Объединив строгость математической теории вероятностей с интуитивной силой искусственного интеллекта и прагматичной мудростью стратегий хеджирования, мы создали систему, способную ориентироваться в бурных водах финансовых рынков с грацией опытного мореплавателя. В штиль она ловит малейшие движения воздуха своими чуткими парусами, в шторм — мастерски маневрирует между гигантскими волнами волатильности, а при смене ветра — мгновенно корректирует свой курс.
Важно понимать: мы не создали магический артефакт, обещающий безграничное богатство. Скорее, это тонкий музыкальный инструмент, требующий настройки под конкретные условия исполнения и мастерства от своего владельца. Как скрипка Страдивари раскрывает свое божественное звучание лишь в руках виртуоза, так и наш советник проявляет весь свой потенциал при правильной настройке и глубоком понимании его внутренней архитектуры.
И все же, адаптивная природа системы и элегантные механизмы управления рисками делают этот инструмент доступным как для новичка, делающего первые шаги в мире алгоритмической торговли, так и для опытного мастера, ищущего новые грани совершенства в своем трейдинговом арсенале.
Исходный код советника, подобно генетическому коду нового вида торговых систем, доступен в приложении к статье. Он открыт для экспериментов, модификаций и эволюции. Мы приглашаем вас не просто использовать его, но и стать соавторами следующей главы в этой захватывающей истории развития финансовых технологий.
Ведь истинная инновация рождается из открытого диалога идей и непрерывного стремления к совершенству.
Ссылки и дополнительные материалы
- Koshtenko, Y. (2025). Матричная модель прогнозирования на марковской цепи.
- ALGLIB - Библиотека численного анализа: https://www.alglib.net/
- Документация MQL5: https://www.mql5.com/ru/docs
- Sewell, M. (2011). Characterization of Financial Time Series. UCL Research Note, 11(01).
- Zhang, G.P. (2003). Time series forecasting using a hybrid ARIMA and neural network model. Neurocomputing, 50, 159-175.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Сознательная или бессознательная, но явная манипуляция результатами тестирования. (этим страдают многие авторы).
Советник тестировался с фиксированным лотом, что полностью убивает любую стратегию - так как условия каждой следующей сделки советника становятся менее рискованными. Отсюда и появляется низкий процент просадки. Для такой тестерной картинки не обязательно наворачивать матрицы, AI и иже с ними, достаточно найти удобную временную точку начала теста.
Мне видится, что советник (не только этот) должен тестироваться на размере лота определяемом по размеру депозита (процент). Тогда каждая сделка в тесте будет как первая. Фактически, условия каждой сделки всегда будут одинаковыми по риску. И вот здесь картинка будет уже совершенно другая.
Странно, но файл из коробки уже компилируется с ошибкой (DeInit )))). Не понятно на каких настройках он тестировался - из той же "коробки" там космические цифры. И если убрать воду ИИ, то в итоге и читать нечего. Можно по-больше конкретики.
Кстати, забейте ИИ текст "Текущая модель использует преимущественно ATR для определения состояний рынка. Но представьте, какой глубины понимания мы достигнем, добавив в этот оркестр звучание RSI, мелодию MACD, гармонические последовательности уровней Фибоначчи и ритмические структуры объемов!" Он такое вам выдаст!!!!)))
Добавил rsi, macd, fibo, volume, если кому интересно
На форуме только исходники можно выкладывать, иначе могут забанить.
Собственно, есть какой эффект от добавок?
На форуме только исходники можно выкладывать, иначе могут забанить.
Собственно, есть какой эффект от добавок?