Ручное тестирование на исторических данных в тестере стратегий MetaTrader 5 с кнопками Buy/Sell на графике
Но я совсем на мели…
Некоторое время назад я искал способ вручную тестировать идеи на исторических данных, чтобы проверить, есть ли в них смысл и какие фильтры могут повысить точность. Это было до моего знакомства с MQL5, и я участвовал в соревнованиях, которые поощряли азартные игры, а не торговлю. Вы их знаете, те, где вход бесплатный, правила время от времени меняются, и первый же участник за день утроил свой счет… вот такие.
Я был довольно наивен, полагая, что если мне удастся хотя бы один раз победить, то я смогу позволить себе участвовать в конкурсах проп-фирм и провозгласить себя «элитным трейдером». Но после нескольких убыточных операций и моментов, когда я буквально рвал на себе волосы, я решил по-настоящему начать разбираться в торговле и инвестировании с финансово-математической точки зрения и в процессе этого обнаружил, что на моей платформе MetaTrader имеется тестер стратегий. С этого и началось мое глубокое погружение в тему, когда я понял: можно программировать торговые роботы (советники) и использовать тестер стратегий для оценки стратегий и настройки параметров.
Имея диплом в области информационно-коммуникационных технологий, я с легкостью разобрался в этом, поскольку язык MQL5 представлял собой, по сути, адаптированный под задачи трейдинга язык C++, но логика и основные принципы работали точно так же. После просмотра нескольких видеороликов на YouTube, бесчисленных часов изучения документации с небольшой помощью искусственного интеллекта я создал пару советников (Expert Advisors, EAs). Следующим препятствием стало тестирование.
Сначала я запускал отдельные тесты, потому что ничего не знал об оптимизации (я думал, что она оптимизирует код). Я продолжал тестировать, меняя параметры и снова тестируя. Так было до тех пор, пока я не спросил у ChatGPT, есть ли способ автоматизировать этот процесс, и это привело меня к разделу оптимизации в тестере стратегий. Я чувствовал себя умным, как самый умный мужик на земле, но достаточно было одного теста, чтобы понять, что мой старый, залежавшийся ноутбук (я получил его от отца еще в 2016 году, и тогда он уже был не новым) не справляется с этими тестами. Ну, точнее, он мог бы… но на их выполнение ушло несколько дней, и каждое падение напряжения в сети (которое, похоже, случалось только во время проведения этих тестов) означало, что приходилось начинать все сначала.
Это заставило меня вернуться к ручному тестированию на исторических данных. Если я смогу подтвердить жизнеспособность стратегии и определить условия сбоя, я смогу автоматизировать ее с более узкими диапазонами параметров (например, сосредоточиться на H1–D1 вместо M1–D1).
Мой первый выбор — функция воспроизведения графиков в Trading View. «Потрясающе… погодите-ка, это же платная функция, 59.95 долларов В МЕСЯЦ!» «Но у меня совсем нет денег…». Именно такой разговор я вел сам с собой. Затем я обратился в ChatGPT и спросил, можно ли как-нибудь добавить кнопки BUY и SELL в тестер стратегий. В ответ ChatGPT вежливо указал, что тестер стратегий предназначен для запуска автоматизированных стратегий и наблюдения за их выполнением, чтобы определить, работают ли они должным образом.
В общем, и от этой идеи пришлось отказаться, пока в поисках капитала через фриланс, создавая на MQL Freelance советники на основе чужих идей, я не наткнулся на человека, которому нужен был советник с кнопками на графике.
Стоп, я что, могу делать кнопки?
После небольшого поиска я обнаружил класс CButton, расположенный в папке Controls. В MQL5 его можно включить с помощью следующей строки:
#include <Controls/Button.mqh> Затем я приступил к изготовлению кнопки. Если я правильно помню, клиенту нужна была кнопка для закрытия всех сделок и еще одна — для установки стоп-лоссов и тейк-профитов на уровне безубыточности. Мне удалось сделать это для клиента после того, как я обнаружил этот класс и понял принцип его работы, и, как вы можете догадаться, вслед за выполнением этого задания мои мысли обратились к чему-то вроде: «Погодите, я же могу делать кнопки?!» Сумею ли я создать кнопки BUY и SELL для тестирования стратегии на исторических данных?
Поэтому я запустил MetaEditor и начал печатать. Я объясню код пошагово на случай, если кто-то новичок в программировании на MQL5, а также для всех остальных, кто читает это после обеда и просто хочет расслабиться.
//+------------------------------------------------------------------+ //| Include And Creation of Class Instances | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> #include <Controls/Button.mqh> CTrade trade; CButton buyButton; CButton sellButton
Первая строка содержит класс CTrade. Это крайне важно, если необходимо легко открывать позиции в MQL5, как будет продемонстрировано позже. Вторую строку я объяснил ранее. Последние три строки предназначены для объявления экземпляров классов CTrade и CButton. Да здравствует объектно-ориентированное программирование!
Далее последовал дизайн кнопок в функции OnInit(), и я не совсем понимаю, почему это так сильно напомнило мне программирование на HTML/CSS. Думаю, это из-за экспериментов с размерами и цветами. Тем не менее, ностальгический момент.
//+------------------------------------------------------------------+ //| Button Creation and Design | //+------------------------------------------------------------------+ sellButton.Create(0,SELL_BUTTON_NAME,0,2,20,95,75); sellButton.Text("SELL"); sellButton.Color(clrWhite); sellButton.ColorBackground(clrRed); buyButton.Create(0,BUY_BUTTON_NAME,0,97,20,190,75); buyButton.Text("BUY"); buyButton.Color(clrWhite); buyButton.ColorBackground(clrMediumBlue); ChartRedraw();
Этот код в основном понятен сам по себе.
Вызов функции Create создает кнопку. Первый ноль указывает на текущий график, следующий параметр — это название кнопки, а следующий ноль выбирает подокно (я использовал основную область графика). Я остановил свой выбор на области графика. Последние четыре входных параметра — это координаты x1, y1, x2, y2, которые определяют положение верхней левой и нижней правой частей кнопки, а следовательно, ее размер и положение.
Строка Text отвечает за надпись, которую пользователь видит на кнопке.
Строка Color задает цвет текста на кнопке, указанный в строке Text. Думаю, именно здесь меня так сильно захватила ностальгия по CSS.
Параметр ColorBackground задает цвет кнопки.
Функция ChartRedraw() обновляет график, чтобы на нем можно было отобразить только что созданные нами кнопки.
Как и обещал, вот определения:
#define BUY_BUTTON_NAME "buyBtn" #define SELL_BUTTON_NAME "sellBtn"
Благодаря этому коду кнопки успешно появились в визуальном режиме моего тестера стратегий, и хотя на этом этапе они не работали, я был очень рад видеть, как начинает воплощаться в жизнь идея, которую я столько раз отбрасывал как неосуществимую.
Покупай дешево, продавай дорого
Я дольше, чем хотелось бы признать, разглядывал кнопки в тестере стратегий и вернулся в MetaEditor, чтобы дать им функциональность. Это стало возможным благодаря нескольким строкам кода в функции OnTick().
//+------------------------------------------------------------------+ //| Buy and Sell Button Logic | //+------------------------------------------------------------------+ if(buyButton.Pressed()) { trade.Buy(0.01); buyButton.Pressed(false); } if(sellButton.Pressed()) { trade.Sell(0.01); sellButton.Pressed(false); }
Это был последний шаг на пути к воплощению моей идеи в жизнь. И цель была достигнута. Уверен, моя мышка недоумевала, что происходит, поскольку я от волнения немного слишком часто нажимал на эти кнопки.

Код проверяет, была ли нажата кнопка. В этом случае открывается ордер на покупку/продажу, а затем кнопка возвращается в ненажатое состояние. «0.01» — это размер лота. Значительная часть операций по открытию сделок обрабатывается классом CTrade, о котором я упоминал ранее, отсюда и простота кода.
Последние штрихи
Мне понадобилась секунда, чтобы понять: я не могу закрыть открытые сделки. Поэтому я снова обратился к MetaEditor, чтобы найти решение. Обладая этими новыми знаниями о создании кнопок, я создал кнопку CLOSE ALL («ЗАКРЫТЬ ВСЕ СДЕЛКИ»), которая закрывала бы все открытые сделки. Кто бы мог подумать.
CButton closeAll; #define CLOSE_ALL_NAME "closeAllBtn"
Эти строки (выше) были добавлены в глобальной области видимости.
//+------------------------------------------------------------------+ //| Close All Button Creation and Design | //+------------------------------------------------------------------+ closeAll.Create(0, CLOSE_ALL_NAME, 0,2,77,190,97); closeAll.Text("CLOSE ALL"); closeAll.Color(clrRed); closeAll.ColorBackground(clrWhite);
Эти (указанные выше) элементы были добавлены в функцию OnInit().
//+------------------------------------------------------------------+ //| Close All Button Logic | //+------------------------------------------------------------------+ if(closeAll.Pressed()) { for(int i = PositionsTotal() -1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); trade.PositionClose(ticket); } closeAll.Pressed(false); }
Эти строки (выше) были добавлены в функцию OnTick(). Весь этот фрагмент кода просто перебирает все открытые позиции (это делается с помощью цикла for), получает тикет этих позиций, закрывает их, а затем опять возвращает кнопку в ненажатое состояние.
Благодаря таким изменениям я теперь мог закрывать сделки, которые открывал, и какое-то время это работало. Так продолжалось до тех пор, пока мне снова не потребовалось больше от этого эксперта, а именно — решить небольшую задачу под названием «управление рисками».
Риск? Нет, не слышали.
Но вскоре ситуация должна была измениться. Мне нужны были простые входные данные, которые сводили бы к минимуму накладные расходы на обработку, и чтобы повторение рыночной ситуации не замедляло существенно формирование свечей. Я решил разобраться с этим, добавив в советник параметры ввода для Take Profit и Stop Loss.
Обратной стороной этого решения была необходимость настраивать их перед запуском тестера стратегий, а не корректировать в режиме реального времени, но для моего варианта использования это было вполне приемлемо. И вот мы снова вернулись к MetaEditor.
//+------------------------------------------------------------------+ //| Risk Management Inputs | //+------------------------------------------------------------------+ input double lotSize = 0.1; //Lot Size input double stopLoss = 10; //Stop Loss input double takeProfit = 20; //Take Profit
Эти строки (выше) были добавлены в глобальной области видимости. Первая нужна для регулировки размера лота, две другие — для установки уровней стоп-лосса и тейк-профита. Я тестировал это на золоте, поэтому такие значения нужно было добавлять непосредственно к цене для получения стоп-лосса и тейк-профита, как показано ниже.
//+------------------------------------------------------------------+ //| Buy and Sell Button Logic Modification | //+------------------------------------------------------------------+ double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(buyButton.Pressed()) { double SL = ask - stopLoss; double TP = ask + takeProfit; trade.Buy(lotSize, _Symbol, ask, SL, TP); buyButton.Pressed(false); } if(sellButton.Pressed()) { double SL = bid + stopLoss; double TP = bid - takeProfit; trade.Sell(lotSize, _Symbol, ask, SL, TP); sellButton.Pressed(false); }
Эти строки были добавлены и до некоторой степени изменены в функции OnTick(). Первые две используются для получения текущей цены Ask и цены Bid соответственно. Затем эти данные используются для расчета различных уровней SL и TP при нажатии кнопок. Все остальное же по большей части не требует разъяснений.
Однако эта версия просуществовала недолго, так как я нашел концепцию, которую захотел попробовать, и она случайно стала финальной версией советника. Трейлинг-стоп.
А что, если стоп-лосс мог бы двигаться вместе с ценой?
Именно этот вопрос решается с помощью концепции скользящего стоп-лосса (трейлинг-стопа). Это была простая идея, и я, немного подумав и проявив мастерство, внес следующие изменения (добавил эти строки):
CPositionInfo pos;
Этот класс добавлен для управления позициями. Еще один аргумент в пользу объектно-ориентированного программирования.
//+------------------------------------------------------------------+ //| Additional Inputs for Trailing Stop Logic | //+------------------------------------------------------------------+ input ulong magicID = 01001; //Magic Number input double lotSize = 0.1; //Lot Size input double initSL = 10; //Initial SL (Decimals) input double trailDecimals = 10; //Trailing SL (Decimals)
Здесь (выше) я отбросил входные данные TP и SL и вместо этого использовал начальный SL и десятичные знаки для скользящего SL. Начальный стоп-лосс работает точно так же, как и оригинальный StopLoss, а трейлинг-стоп удерживает стоп-лосс на заданной дистанции от цены (я объясню это чуть позже). Это позволяет стоп-лоссу двигаться вместе с ценой: по мере возрастания прибыли стоп-лосс фиксирует большую часть прибыли (или уменьшает убыток до тех пор, пока не пройдет точку входа). «Магическое число» нужно для того, чтобы советник обрабатывал только свои сделки, так что по какой бы причине я ни захотел использовать его на реальном графике, он будет корректировать только собственные сделки.
double ask; double bid;
Эти строки (выше) были добавлены в глобальной области видимости. Быстрее объявить их здесь и просто обращаться к ним в функции OnTick, чем объявлять их на каждом тике.
//+------------------------------------------------------------------+ //| OnTick Function Final Version | //+------------------------------------------------------------------+ void OnTick() { ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(buyButton.Pressed()) { double SL = initSL > 0 ? bid - initSL : 0; trade.Buy(lotSize, _Symbol, 0, SL); buyButton.Pressed(false); } if(sellButton.Pressed()) { double SL = initSL > 0 ? ask + initSL : 0; trade.Sell(lotSize, _Symbol, 0, SL); sellButton.Pressed(false); } if(closeAll.Pressed()) { for(int i = PositionsTotal() -1; i >= 0; i--) { pos.SelectByIndex(i); if(pos.Magic() != magicID) continue; trade.PositionClose(pos.Ticket()); } closeAll.Pressed(false); } if(PositionsTotal() > 0 && trailDecimals > 0) { trailingStop(trailDecimals, ask, bid); } }
Дамы и господа, представляем новую функцию OnTick. Единственное существенное отличие заключается в использовании класса CPositionInfo через переменную pos в функции if метода closeAll.Pressed(). Он используется для выбора позиции в цикле for, а затем для проверки, была ли она открыта данным советником, путем сравнения магических чисел.
Также появилась новая функция trailingStop, которая вызывается при наличии каких-либо открытых позиций. Вот как выглядит код для этого:
//+------------------------------------------------------------------+ //| Trailing Stop Logic | //+------------------------------------------------------------------+ void trailingStop(double points, double Ask, double Bid) { for(int i = PositionsTotal()-1; i >= 0; i--) { if(pos.SelectByIndex(i)) { if(pos.Magic() != magicID) continue; ENUM_POSITION_TYPE type = pos.PositionType(); ulong ticket = pos.Ticket(); double SL = pos.StopLoss(); double newSL; if(pos.Profit() > 0) { double diffS = MathAbs(Ask - SL); double diffB = MathAbs(Bid - SL); if(diffB > points && type == POSITION_TYPE_BUY) { newSL = Bid - points; trade.PositionModify(ticket, newSL, 0); } if(diffS > points && type == POSITION_TYPE_SELL) { newSL = Ask + points; trade.PositionModify(ticket, newSL, 0); } } } } }
Здесь учитываются следующие параметры: скользящие пункты (в данном случае это десятичные дроби, а не сами пункты... я просто обозначил их как пункты), цена Ask и цена Bid. Затем программа переходит к перебору позиций с помощью цикла for, и для каждой позиции выполняется проверка, чтобы убедиться, что мы собираемся изменять позиции, открытые этим советником. Если магическое число позиции не совпадает с магическим числом эксперта, мы переходим к следующей позиции.
Затем мы двигаемся дальше и получаем тип позиции — это необходимо, чтобы рассчитать, в какую сторону смещать стоп-лосс. Кроме того, мы получаем тикет и текущий уровень SL. А для хранения вновь рассчитанного уровня SL объявляется новая переменная.
Затем код проверяет, находимся ли мы в прибыли, и если да, то вычисляет разницу между стоп-лоссом и текущей ценой Bid и Ask. Функция MathAbs используется для преобразования разницы в положительное число (например, если разница равна -15, она выдаст 15), чтобы ее было легко сравнивать с «пунктами» на следующих строках.
Затем код переходит к вычислению нового уровня стоп-лосса, если полученная разница превышает «пункты», и именно таким образом стоп-лосс следует за ценой. Однако, если цена снизится в случае покупки, разница будет недостаточной, поэтому стоп-лосс останется прежним, и наоборот — в случае продажи.
Итоговый код:
//+------------------------------------------------------------------+ //| BTTF2.mq5 | //| Copyright 2025, Pips And Margins | //| https://www.mql5.com/en/users/flutrr/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Pips And Margins" #property link "https://www.mql5.com/en/users/flutrr/seller" #property version "2.00" //+------------------------------------------------------------------+ //| Include Files | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> #include <Controls/Button.mqh> //+------------------------------------------------------------------+ //| Class Initialization | //+------------------------------------------------------------------+ CTrade trade; CPositionInfo pos; CButton buyButton; CButton sellButton; CButton closeAll; //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input ulong magicID = 01001; //Magic Number input double lotSize = 0.1; //Lot Size input double initSL = 10; //Initial SL (Decimals) input double trailDecimals = 10; //Trailing SL (Decimals) //+------------------------------------------------------------------+ //| Definitions | //+------------------------------------------------------------------+ #define BUY_BUTTON_NAME "buyBtn" #define SELL_BUTTON_NAME "sellBtn" #define CLOSE_ALL_NAME "closeAllBtn" //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ double ask; double bid; //+------------------------------------------------------------------+ //| OnInit Function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(magicID); sellButton.Create(0,SELL_BUTTON_NAME,0,2,20,95,75); sellButton.Text("SELL"); sellButton.Color(clrWhite); sellButton.ColorBackground(clrRed); buyButton.Create(0,BUY_BUTTON_NAME,0,97,20,190,75); buyButton.Text("BUY"); buyButton.Color(clrWhite); buyButton.ColorBackground(clrMediumBlue); closeAll.Create(0, CLOSE_ALL_NAME, 0,2,77,190,97); closeAll.Text("CLOSE ALL"); closeAll.Color(clrRed); closeAll.ColorBackground(clrWhite); ChartRedraw(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| OnTick Function | //+------------------------------------------------------------------+ void OnTick() { ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(buyButton.Pressed()) { double SL = initSL > 0 ? bid - initSL : 0; trade.Buy(lotSize, _Symbol, 0, SL); buyButton.Pressed(false); } if(sellButton.Pressed()) { double SL = initSL > 0 ? ask + initSL : 0; trade.Sell(lotSize, _Symbol, 0, SL); sellButton.Pressed(false); } if(closeAll.Pressed()) { for(int i = PositionsTotal() -1; i >= 0; i--) { pos.SelectByIndex(i); if(pos.Magic() != magicID) continue; trade.PositionClose(pos.Ticket()); } closeAll.Pressed(false); } if(PositionsTotal() > 0 && trailDecimals > 0) { trailingStop(trailDecimals, ask, bid); } } //+------------------------------------------------------------------+ //| OnDeinit Function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { buyButton.Destroy(reason); sellButton.Destroy(reason); closeAll.Destroy(reason); } //+------------------------------------------------------------------+ //| Trailing Stop Function | //+------------------------------------------------------------------+ void trailingStop(double points, double Ask, double Bid) { for(int i = PositionsTotal()-1; i >= 0; i--) { if(pos.SelectByIndex(i)) { if(pos.Magic() != magicID) continue; ENUM_POSITION_TYPE type = pos.PositionType(); ulong ticket = pos.Ticket(); double SL = pos.StopLoss(); double newSL; if(pos.Profit() > 0) { double diffS = MathAbs(Ask - SL); double diffB = MathAbs(Bid - SL); if(diffB > points && type == POSITION_TYPE_BUY) { newSL = Bid - points; trade.PositionModify(ticket, newSL, 0); } if(diffS > points && type == POSITION_TYPE_SELL) { newSL = Ask + points; trade.PositionModify(ticket, newSL, 0); } } } } } //+------------------------------------------------------------------+
Сработало?
Я использовал этот советник для генерации множества торговых концепций и для тестирования своих самых смелых идей перед их автоматизацией. Это, как я и ожидал, сэкономило мне немного времени в попытках разобраться в них при тестировании в автоматическом режиме, а также выявило идеи, которые изначально не работали. Всё бесплатно (за вычетом времени на разработку и потерянных клеток мозга, потраченных на попытки во всем этом разобраться).
Помню свою идею о торговле на основе полос Боллинджера как о стратегии возврата к среднему значению, которая заключалась в покупке, когда цена опускалась ниже нижней полосы Боллинджера, а затем на следующей свече цена поднималась выше нижней полосы Боллинджера. Кроме того, когда цена опускалась ниже нижней полосы Боллинджера, индикатор RSI должен был опуститься ниже 30, а на следующей свече подняться выше 30. В моей голове это была хорошая стратегия, но прежде чем потратить следующие 2-4 часа на ее программирование и выяснить, что она на самом деле не работает, я открыл пустой график (без индикаторов), затем добавил индикатор полос Боллинджера и индикатор RSI.

Затем я сохранил его как tester.tpl (этот файл открывается в вашем тестере по умолчанию, и именно так вы добавляете индикаторы к этому советнику при тестировании новых стратегий). Затем я настроил тестер и приступил к работе.

Стратегия оказалась жизнеспособной, и я автоматизировал ее, зная, что это будет прибыльно, а автоматизация была лишь проверкой потенциальной прибыльности.
Заключение
В результате получился компактный советник с функцией воспроизведения графиков, который решает основную задачу: он предоставляет небольшой, целенаправленный инструмент для ручной проверки идей внутри тестера стратегий MetaTrader 5. Используя CButton в качестве интерфейса, CTrade для размещения ордеров и изменения позиций, а также CPositionInfo для управления сделками по «магическому числу», советник позволяет:
- открывать сделки на покупку или продажу одним щелчком мыши,
- закрывать все принадлежащие советнику позиции одним нажатием кнопки,
- настраивать размер лота и начальный уровень стоп-лосса с помощью входных параметров,
- а также использовать автоматический трейлинг-стоп, который фиксирует прибыль по мере изменения цены.
Этот рабочий процесс позволил сэкономить необходимое для разработки время, отбрасывая нежизнеспособные идеи до кодирования полной автоматизации стратегии, а также помогал выявлять пограничные сценарии на ранних этапах проектирования. Для его запуска в визуальном тестере: загрузите свой шаблон графика (с индикаторами) в качестве шаблона тестера, прикрепите советник, установите входные параметры (магическое число, размер лота, начальный стоп-лосс, трейлинг-стоп) и запустите в визуальном режиме с применением кнопок для имитации точек входа и выхода. В планах на будущее: внесение изменений в размер лота в режиме реального времени и кнопки для закрытия каждой позиции в отдельности. Советник (Chart Replay Pro) доступен бесплатно в моем профиле, если вы захотите его испробовать.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22256
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Возможности Мастера MQL5, которые вам нужно знать (Часть 78): Стратегии на основе осцилляторов Gator и A/D для повышения устойчивости на рынке
Нейросети в трейдинге: От единственного прогноза к пространству рыночных сценариев (сценарное кросс-внимание)
Возможности Мастера MQL5, которые вам нужно знать (Часть 77): Использование индикаторов Gator и A/D (Accumulation/Distribution)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
