Думаю огромное количество трейдеров сломало много копий пытаясь выбрать оптимальные параметры для своей торговой системы. Действительно, мало разработать торговый алгоритм, нужно ещё определиться, как им пользоваться в дальнейшем. Какую бы Вы торговую стратегию не использовали - простую или сложную, используете Вы один инструмент или множество, всё равно в итоге возникает вопрос: "Какие выбрать параметры, чтобы получить прибыль в будущем?".
На данный момент довольно распространён метод для проверки своей торговой системы с параметрами, которые показали хороший результат на периоде оптимизации (бек-тест), на периоде, который следует за ним (форвард-тест). На самом деле необязательно для проверки использовать следующий период. Можно посмотреть, какой будет результат на данных в прошлом.
В этом методе есть довольно большой вопрос, на который Вы нигде не найдёте однозначный ответ. Он заключается в том, что: "Какое количество исторических данных использовать для того, чтобы оптимизировать параметры тестируемой торговой системы?". Дело в том, что вариантов огромное множество. Всё зависит от того, на каком размере колебаний цены Вы рассчитываете получить прибыль.
В этом методе есть довольно большой вопрос, на который Вы нигде не найдёте однозначный ответ. Он заключается в том, что: "Какое количество исторических данных использовать для того, чтобы оптимизировать параметры тестируемой торговой системы?". Дело в том, что вариантов огромное множество. Всё зависит от того, на каком размере колебаний цены Вы рассчитываете получить прибыль.
Возвращаясь к вопросу: "Какое количество данных использовать для оптимизации параметров?", приходим к тому, что для внутричасовой торговли имеющихся данных вполне может быть достаточно. А вот всё, что выше, не всегда. Как можно большее количество повторений предполагаемой закономерности и соответственно большее количество сделок, даёт более правдивую картину по результату тестируемой торговой системы, на результат которой можно рассчитывать в будущем.
Что делать, если ценовых данных не хватает на том или ином инструменте, чтобы получить достаточное количество повторений и быть более уверенным? Ответ: "Используйте данные со всех имеющихся инструментов."
Давайте перед тем, как перейти к программированию в MetaTrader 5, рассмотрим пример в программе NeuroShell DayTrader Professional 5.6 (далее NSDT). В NSDT есть хорошая возможность оптимизировать параметры торговой системы (собранной в конструкторе NSDT) на множестве символах. При этом можно указать в настройках торгового модуля, оптимизировать параметры для каждого символа отдельно или найти один набор оптимальных параметров сразу для всех символов. Эта опция находится на вкладке Optimization (выделено красным прямоугольником ниже):
Для примера можно использовать любую простую торговую систему. В данном случае нужно просто произвести сравнение между результатами двух методов оптимизации параметров, поэтому неважно, какую систему сейчас использовать. Как собирать торговые стратегии в NSDT можно посмотреть в других статьях на этом блоге (воспользуйтесь поиском или облаком меток). Также посмотрите статью "Загрузка исторических данных в NeuroShell DayTrader Professional", в ней рассказывается и показывается, как с помощью скрипта можно скачать котировки из MetaTrader 5 в формате, который NSDT понимает.
Для этого теста были подготовлены данные дневных баров восьми символов от 2000 года до текущего дня (январь 2013 года):
На рисунке ниже показаны два результата оптимизации. В верхней части результат, когда для каждого символа подбираются свои параметры. А в нижней части результат, когда параметры для всех символов общие.
Результат с общими параметрами не такой красивый, как тот, где параметры для каждого символа разные, но всё таки внушает больше доверия тем, что торговая система проходит через множество различных характеров поведения цены (волатильность, количество трендов/флэтов) с одними параметрами на всех символах.
Развивая размышления в этой теме дальше, логически можно прийти к ещё одному аргументу в пользу оптимизации на большем количестве данных. Вполне вероятно, что характер поведения цены той или иной валютной пары, например EURUSD, будет в следующий год (два, пять, десять) совершенно иным, то есть, не таким, каким он был в предыдущие годы. Например, у валютной пары GBPUSD цена покажет похожие тренды, как это было у EURUSD и наоборот. К этому нужно быть готовым. И это относится к каждому инструменту.
Теперь давайте посмотрим, какие режимы оптимизации параметров предлагает MetaTrader 5. На рисунке ниже стрелкой указан вариант в выпадающем списке режимов оптимизации: Все символы, выбранные в окне Обзор Рынка.
Но этот режим позволяет только протестировать эксперта с текущими параметрами поочерёдно на каждом символе, которые в текущий момент выбраны в окне Обзор Рынка. То есть, оптимизация параметров при этом не производится. Но зато MetaTrader 5 и MQL5 даёт возможность реализовать эту идею самостоятельно.
Теперь нужно решить, как реализовать схему эксперта. Список символов будем составлять в текстовом файле (*.txt). Реализуем возможность хранения нескольких наборов списков символов. Каждый набор будет в отдельной секции, у которой будет свой заголовок с номером секции. Номера нужны просто для удобного визуального контроля. Важно только чтобы перед номером был символ наклонной черты "/", на который будет ориентироваться эксперт при выборе, из какого набора данных заполнять массив символов. Вообще в заголовке могут присутствовать любые символы, но один из них должен быть обязательно "/". Вместо наклонной черты можно использовать любой другой символ, по которому эксперт будет определять/считать секции, просто это нужно будет указать в коде.
Ниже показан пример заполненного файла ListSymbols.txt, в котором есть три набора символов для тестов. Именно этот образец будем потом использовать при тестировании метода.
Чтобы указывать эксперту, какой набор символов использовать в текущем тесте, во внешних параметрах добавим параметр Section of List Symbols. В этом параметре нужно просто ввести номер (от нуля и выше), который определяет набор символов. Если выйти за предел количества наборов, то эксперт сообщит об этом в журнал и тест будет произведён только на текущем символе.
Файл ListSymbols.txt должен находиться в локальной папке терминала в директории Metatrader 5\MQL5\Files. Его можно расположить и в общей папке, но тогда он не будет доступен для оптимизации параметров в сервисе распределённых вычислений в сети MQL5 Cloud Network. Также для того, чтобы файл и используемые пользовательские индикаторы были доступны для теста, нужно в начале файла указать это вот такими строками:
//--- #property tester_file "ListSymbols.txt" #property tester_indicator "EventsSpy.ex5" //---
В качестве шаблона возьмём уже готового мультивалютного эксперта из статьи "Разработка мультивалютного эксперта с неограниченным количеством параметров". В него заложена простая торговая стратегия, но для примера и проверки эффективности метода этого вполне достаточно. Просто уберём из эксперта всё лишнее, добавим то, чего не хватает и подкорректируем то, что уже есть. И конечно же добавим в него возможность сохранения отчёта, создание которого подробно описывалось в предыдущей статье "Записываем историю сделок в файл и строим графики балансов для каждого символа в Excel". Графики балансов всех символов нам понадобятся также для оценки эффективности рассматриваемого метода.
Внешние параметры эксперта нужно сделать так, как показано ниже (изменения выделены):
//+------------------------------------------------------------------+ //| ВНЕШНИЕ ПАРАМЕТРЫ ЭКСПЕРТА | //+------------------------------------------------------------------+ sinput int SectionListSymbols = 1; // Section of List Symbols sinput bool UpdateReport = false; // Update Report //--- sinput string dlm_00=""; // * * * * * * * * * * * * * * * * * * * * * * * * * * //--- sinput long MagicNumber = 777; // Magic Number sinput int Deviation = 10; // Deviation (p) //--- sinput string dlm_01=""; // * * * * * * * * * * * * * * * * * * * * * * * * * * //--- input int PeriodInd = 5; // Period Indicator input double TakeProfit = 100; // Take Profit (p) input double StopLoss = 50; // Stop Loss (p) input double TrailingSL = 10; // Step Trailing Stop (p) input bool Reverse = true; // Reverse input double Lot = 0.1; // Lot input double Increase = 0.1; // Increase Volume input double StepIncrease = 10; // Step Increase (p) //---
В файле ARRAYS.mqh все массивы, которые относятся к внешним параметрам нужно удалить, теперь они не понадобятся. Вместо них везде нужно подставить внешние переменные. Оставить нужно только динамический массив символов aSymbols[], размер которого будет зависеть от количества используемых символов одного из наборов в файле ListSymbols.txt. Если эксперт используется вне тестера, то размер этого массива будет равен 1, так как в реальном времени эксперт будет работать на одном символе.
Соответственные изменения нужно произвести и в файле инициализации массивов INIT_ARRAYS.mqh. То есть, все функции, в которых производилась инициализация массивов внешних переменных, нужно удалить. Функция InitASymbols() теперь выглядит так, как показано в коде ниже:
//+------------------------------------------------------------------+ //| ЗАПОЛНЯЕТ МАССИВ СИМВОЛОВ | //+------------------------------------------------------------------+ void InitASymbols() { // Сообщение для теста string msg01="<--- Все имена символов в файле <- ListSymbols.txt -> некорректные ... --->\n" "<--- ... или в параметре \"Section of List Symbols\" указано большее значение, " "чем количество разделов в файле! --->\n" "<--- Поэтому будем тестировать только текущий символ. --->"; //--- // Сообщение для реал-тайма string msg02="<--- В реал-тайме работаем только на текущем символе. --->"; //--- if(!NotTest()) // Если реал-тайм { int cntTmpS=0; // Кол-во строк в файле символов string check_smb=""; // Для проверки символов //--- // Получим количество строк из указанного набора символов в файле и // заполним временный массив символов cntTmpS=GetTSymbolsFromFile(); //--- // Пройдём по всем символам из указанного набора for(int s=0; s<cntTmpS; s++) { // Если после проверки символа возвращена корректная строка, то... if((check_smb=CheckGetSymbol(aTmpSmb[s]))!="") { CNTS++; // ...увеличим счётчик, ArrayResize(aSymbol,CNTS); // установим/увеличим размер массива и aSymbol[CNTS-1]=check_smb; // проиндексируем именем символа } } } //--- // Если получилось так, что ... // ... все имена символов были некорректно введены или ... // ... сейчас реал-тайм if(CNTS==0) { if(NotTest()) { Print(msg02); } // Сообщение для реал-тайма if(!NotTest()) { Print(msg01); } // Сообщение для теста //--- CNTS=1; // Будем работать только с текущим символом ArrayResize(aSymbol,CNTS); // установим размер массива и aSymbol[0]=_Symbol; // проиндексируем именем текущего символа } } //---
В коде выше выделена строка с функцией GetTSymbolsFromFile(). Её код тоже нужно подвергнуть изменениям. До этого в ней производилось считывание всего списка символов, а теперь нам нужно, чтобы считывался только указанный набор символов. Ниже представлен уже готовый код этой функции:
//+------------------------------------------------------------------+ //| СЧИТАЕТ КОЛИЧЕСТВО СТРОК (СИМВОЛОВ) В ФАЙЛЕ СИМВОЛОВ | //| ИЗ УКАЗАННОГО НАБОРА И ЗАПОЛНЯЕТ ВРЕМЕННЫЙ МАССИВ СИМВОЛОВ | //+------------------------------------------------------------------+ int GetTSymbolsFromFile() { int hFl=-1; // Хэндл файла int limit=0; // Счётчик ограничитель int cntStr=0; // Счётчик строк string dlm="/"; // Признак начала раздела string nm_file="ListSymbols.txt"; // Имя файла, в котором нужно посчитать строки //--- string err_txt01="<--- Файл <- "+nm_file+" -> составлен некорректно! --->\n" "<--- В первой строке нет признака номера раздела ("+dlm+")! --->"; //--- string err_txt02="<--- Файл <- "+nm_file+" -> составлен некорректно! --->\n" "<--- Нет признака перевода строки в последней строке, --->\n" "<--- поэтому в тесте будет участвовать только текущий символ. --->"; //--- string err_txt03="<--- Файл <- "+nm_file+" -> не найден! --->" "<--- В тесте будет участвовать только текущий символ. --->"; //--- // Откроем файл nm_file (получим хэндл) для чтения в локальной папке терминала hFl=FileOpen(nm_file,FILE_READ|FILE_ANSI); //--- // Если хэндл файла получен if(hFl!=INVALID_HANDLE) { ulong tseek=0; // Для определения положения указателя int cntSections=-1; // Счётчик разделов string txt_string=""; // Для проверки прочитанной строки //--- // Читать пока текущее положение файлового указателя // не окажется в конце файла или программа не будет удалена while(!FileIsEnding(hFl) || !IsStopped()) { // Читать до конца строки или пока программа не будет удалена while(!FileIsLineEnding(hFl) || !IsStopped()) { txt_string=FileReadString(hFl); // Прочитаем всю строку //--- // Если нашли признак номера раздела if(StringFind(txt_string,dlm,0)>-1) { cntSections++; } // Увеличим счётчик раздела //--- // Если раздел прочитан, то выходим из функции if(cntSections>SectionListSymbols) { FileClose(hFl); // Закроем файл return(cntStr); // Вернём количество строк в наборе } //--- // Если это первая итерация и // в первой строке не было признака номера раздела if(limit==0 && cntSections==-1) { OnlyOneSymbol(cntStr,err_txt01); FileClose(hFl); // Закроем файл return(cntStr); // Вернём количество строк в наборе } //--- limit++; // Увеличим счётчик ограничитель кол-ва возможных открытых графиков //--- // Если дошли до ограничения if(limit>=CHARTS_MAX) { OnlyOneSymbol(cntStr,err_txt02); FileClose(hFl); // Закроем файл return(cntStr); // Вернём количество строк в наборе } //--- tseek=FileTell(hFl); // Получим положение указателя //--- if(FileIsLineEnding(hFl)) // Если это конец строки { // Переход на другую строку, если это не конец файла // Для этого увеличим счётчик указателя файла и... if(!FileIsEnding(hFl)) { tseek++; } //--- // ...переведём его на следующую строку FileSeek(hFl,tseek,SEEK_SET); //--- // Если мы сейчас не в указанном разделе файла if(cntSections!=SectionListSymbols) { break; } // выйдем из цикла else { if(txt_string!="") // Если строка не пустая... { cntStr++; // ...увеличим счётчик строк, ArrayResize(aTmpSmb,cntStr); // увеличим размер массива строк, aTmpSmb[cntStr-1]=txt_string; // занесём в текущий индекс строку } } //--- break; // Выйдем из этого цикла } } //--- // Если это конец файла прервём весь цикл if(FileIsEnding(hFl)) { break; } } //--- FileClose(hFl); // Закроем файл } else { OnlyOneSymbol(cntStr,err_txt03); } //--- return(cntStr); // Вернём количество строк в наборе } //---
В коде выше выделены строки с функцией OnlyOneSymbol(). В ней просто подготавливается массив для одного символа (текущего), если возникла какая-то ошибка.
//+------------------------------------------------------------------+ //| ПОДГОТОВКА МАССИВА ДЛЯ ОДНОГО СИМВОЛА | //+------------------------------------------------------------------+ void OnlyOneSymbol(int &cntStr,string msg) { Print(msg); // Выведем сообщение в журнал //--- cntStr=1; ArrayResize(aTmpSmb,cntStr); // Установим размер массива символов aTmpSmb[0]=_Symbol; // занесём в текущий индекс строку с именем текущего символа } //---
Что касается самого метода для оптимизации параметров, то всё готово для того, чтобы протестировать его. Но перед тем, как провести тест, добавим в отчёт ещё один ряд данных. До этого, кроме балансов всех символов, в файл записывались все просадки от локальных максимумов в процентном выражении. Добавим ещё все просадки в денежном выражении и заодно подкорректируем функцию CreateReportBalancesSymbols(), в которой формируется отчёт.
Ниже представлен код функции CreateReportBalancesSymbols():
//+------------------------------------------------------------------+ //| СОЗДАЁТ ОТЧЁТ ТЕСТИРОВАНИЯ ПО ИСТОРИИ СДЕЛОК В ФОРМАТЕ CSV | //+------------------------------------------------------------------+ void CreateReportBalancesSymbols() { int hdlFl=-1; // Хэндл файла string path=""; // Путь к файлу //--- // Если ошибка при создании/получении директории, выходим if((path=CheckCreateGetTestPath())=="") { return; } //--- // Создадим файл для записи данных в общей папке терминала hdlFl=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON); //--- // Если хэндл валиден (файл создался/открылся) if(hdlFl>0) { // Получим всю историю. Если не получилось, закроем файл и выйдем if(!HistorySelect(0,TimeCurrent())) { FileClose(hdlFl); return; } //--- deals_total=HistoryDealsTotal(); // Узнаем количество сделок //--- int dgt=0; // Количество знаков в цене после запятой int s=0,i=0,n=0; // Индексы в циклах int deals_total=0; // Количество сделок выбранной истории static double pmaxdds=0.0, // Просадка в процентах mmaxdds=0.0; // Просадка в деньгах //--- ulong ticket=0; // Тикет сделки double balance=0.0; // Баланс //--- string dlm=",",// Разделитель writeString=""; // Для формирования строки для записи //--- // Заголовки string Headers="TIME,SYMBOL,TYPE DEAL,TYPE ENTRY,LOT," "PRICE,SWAP($),PROFIT($),DRAWDOWNS(%),DRAWDOWNS($),BALANCE"; //--- // Если участвует больше одного символа, то... if(CNTS>1) // ...дополним строку заголовков { for(s=0; s<CNTS; s++) { StringAdd(Headers,","+aSymbol[s]); } } //--- FileWrite(hdlFl,Headers); // Запишем заголовки отчёта //--- // Установим размер массива балансов по кол-ву символов ArrayResize(blcS,CNTS); //--- // Установим размер массивов сделок для каждого символа for(s=0; s<CNTS; s++) { ArrayResize(blcS[s].b,deals_total); } //--- // Пройдёмся в цикле и запишем данные for(i=0; i<deals_total; i++) { ticket=HistoryDealGetTicket(i); // Получим тикет сделки //--- GetPropHistoryDeal(ticket,HDL_ALL); // Получим все свойства сделки dgt=(int)SymbolInfoInteger(hdl.symbol,SYMBOL_DIGITS); // Узнаем кол-во знаков в цене //--- balance+=hdl.profit+hdl.swap+hdl.commission; // Посчитаем общий баланс //--- // Посчитаем максимальную просадку от локального максимума TesterMaxDDs(i,balance,pmaxdds,mmaxdds); //--- // Сформируем строку для записи путём конкатенации StringConcatenate(writeString, hdl.time,dlm, DealSymbolCorrectStr(hdl.symbol),dlm, DealTypeToString(hdl.type),dlm, DealEntryToString(hdl.entry),dlm, DealLotToString(hdl.volume),dlm, DealPriceToString(hdl.price,dgt),dlm, DealSwapToString(hdl.swap),dlm, DealProfitToString(hdl.symbol,hdl.profit),dlm, MaxDDToStr(pmaxdds),dlm, MaxDDToStr(mmaxdds),dlm, DS(balance,2)); //--- // Если участвует больше одного символа, то... if(CNTS>1) // ...запишем их значения баланса { // Пройдёмся по всем символам for(s=0; s<CNTS; s++) { // Если символы равны и результат сделки ненулевой if(hdl.symbol==aSymbol[s] && hdl.profit!=0) { // ...отразим сделку в балансе с этим символом // Посчитаем blcS[s].b[i]=blcS[s].b[i-1]+ hdl.profit+ hdl.swap+ hdl.commission; //--- // Добавим к строке StringAdd(writeString,","+DS(blcS[s].b[i],2)); } else // Иначе запишем предыдущее значение { // Если тип сделки "Начисление баланса" (первая сделка), то... if(hdl.type==DEAL_TYPE_BALANCE) { // ...для всех символов баланс одинаковый blcS[s].b[i]=balance; StringAdd(writeString,","+DS(blcS[s].b[i],2)); } else { // Запишем предыдущее значение в текущий индекс blcS[s].b[i]=blcS[s].b[i-1]; StringAdd(writeString,","+DS(blcS[s].b[i],2)); } } } } //--- FileWrite(hdlFl,writeString); // Запишем сформированную строку writeString=""; // Обязательное обнуление переменной для следующей строки } //--- FileClose(hdlFl); // Закроем файл } else // Если файл не создался/открылся, выведем сообщение { Print("Ошибка при создании файла, error: "+IS(GetLastError())+""); } } //---
До этого просадки считались в функции StrTesterMaxDD(). Теперь вместо неё это делает функция TesterMaxDDs(), а в строку, значение просадки, преобразует функция MaxDDToStr(). Эти функции выделены в коде выше.
Код функции TesterMaxDDs():
//+------------------------------------------------------------------+ //| ВОЗВРАЩАЕТ МАКСИМАЛЬНУЮ ПРОСАДКУ ОТ ЛОКАЛЬНОГО МАКСИМУМА | //+------------------------------------------------------------------+ void TesterMaxDDs(int i,double balance,double &pmaxdds,double &mmaxdds) { string str=""; // Строка для отображения в отчёте static double max=0.0,min=0.0; // Для расчёта локального максимума и просадки //--- if(i==0) // Если первая сделка { pmaxdds=0.0; // Просадки нет mmaxdds=0.0; //--- // Зададим начальную точку, как локальный максимум max=balance; min=balance; } else { // Если текущий баланс больше, чем в памяти,... if(balance>max) { // ...посчитаем просадку по предыдущим значениям mmaxdds=max-min; // в деньгах pmaxdds=100-((min/max)*100); // в процентах и... //--- // ...обновим локальный максимум max=balance; min=balance; } else { ulong t=0; // Тикет сделки //--- mmaxdds=0.0; // Возвратим нулевое значение просадки pmaxdds=0.0; min=fmin(min,balance); // Обновим минимум //--- // Если тикет сделки по её позиции в списке получен, то... if((t=HistoryDealGetTicket(i))>0) { GetPropHistoryDeal(t,HDL_COMMENT); // ...получим комментарий сделки //--- static bool flgET=false; // Флаг последней сделки //--- // Последнюю сделку в тесте можно определить по комментарию "end of test" if(hdl.comment=="end of test" && !flgET) { flgET=true; // Установим флаг //--- // Обновим значения просадки... mmaxdds=max-min; // в деньгах pmaxdds+=100-((min/max)*100); // в процентах } } } } } //---
Код функции MaxDDToStr():
//+------------------------------------------------------------------+ //| ПРЕОБРАЗУЕТ ПРОСАДКУ В СТРОКУ | //+------------------------------------------------------------------+ string MaxDDToStr(double dd) { string str=""; //--- if(dd<=0) { str=""; } // Пустая строка или else { str=DS(dd,2); } // значение просадки //--- return(str); // Вернём строку } //---
Теперь всё готово для того, чтобы протестировать эксперта и проанализировать полученный результат. Почти в самом начале статьи был показан пример уже заполненного файла. Сделаем следующее. Оптимизируем параметры на символах из второго набора. Это три символа: EURUSD, AUDUSD и USDCHF. А затем, после оптимизации, проведём тест на всех символах из третьего набора (всего восемь символов), чтобы посмотреть, какой получится результат на тех символах, данные которых не участвовали в оптимизации параметров. Довольно жёсткий форвард-тест получится. :)
Настройки тестера установим так, как показано на скриншоте ниже:
Так как в оптимизации участвует три символа и на каждом производится наращивание объёма позиции, то лот для открытия позиции и наращивания объёма позиции установим минимальными. В данном случае это 0.01.
После оптимизации выберем самый верхний результат по максимальному балансу. Результат показан ниже:
Я обычно выбираю результаты по максимальному фактору восстановления, но в этом случае результаты по максимальному балансу имеют довольно высокие и другие показатели, поэтому я сделал такой выбор.
На скриншоте ниже показано, как выглядит результат в Excel 2010:
Просадка выраженная деньгами отображается на нижнем графике на второй (вспомогательной) шкале в виде зелёных маркеров.
Следует также знать об ограничениях диаграмм в Excel 2010 (см. рисунок ниже). А со всеми остальными ограничениями можно ознакомиться на сайте Microsoft на странице Технические характеристики и ограничения Microsoft Excel.
То есть, судя по ограничениям в таблице выше можно провести тест одновременно на 255-ти символах и вывести все результаты на диаграмму! И ограничены мы только ресурсами компьютера.
Теперь проведём тест на восьми символах из третьего набора с текущими параметрами и посмотрим, какой получится результат (см. скриншот ниже).
По восьми символам получилось около 20000 сделок и Excel 2010 довольно быстро их обновляет на диаграмме.
Я думаю, что представленный метод заслуживает внимания, если даже такая простая торговая стратегия показала положительный результат. При этом стоит учесть, что оптимизация проводилась только на трёх символах из восьми и результат можно попробовать улучшить, если провести оптимизацию на всех символах сразу. Но в первую очередь конечно же нужно думать о том, чтобы улучшить торговую систему. А лучше иметь портфель различных торговых систем, к этому вопросу мы ещё вернёмся.
На этом всё. Получился довольно полезный инструмент для изучения результатов мультивалютных торговых стратегий.
Успехов!
Комментариев нет :
Отправить комментарий