Графики индекса доллара и индекса евро — пример сервиса в MetaTrader 5
Содержание
- Введение
- Ставим задачу
- Пишем включаемый файл функций расчёта индексов валют
- Тестируем программы-сервисы
- Заключение
Введение
Индекс американского доллара (U.S. Dollar Index) является самым известным и главным индексом валютного рынка. Благодаря ему можно прогнозировать движение курсов валют. Это важный показатель относительной стоимости американского доллара. Индекс американского доллара (USDX) существует с марта 1973 года, когда базовым его значением был принят уровень 100 пунктов. То есть индекс, равный 90 пунктам, сегодня будет значить падение доллара относительно показателя 1973 года на 10%, а индекс, равный 110 пунктов — рост на 10%.
Индекс доллара был введен в действие в 1973 году, когда по итогам Ямайской международной конференции вступили в действие плавающие курсы валют, входящих в этот индекс. С тех пор индекс доллара постоянно рассчитывается на основании данных о валютных торгах, предоставляемых 500 крупнейшими банками мира. Изменение методики расчета индекса американского доллара произошло в 1999 году, когда в оборот была введена единая европейская валюта — евро, заменившая собой национальные валюты ряда европейских стран и немецкой марки.
USDX рассчитывается как среднее геометрическое взвешенное валютной корзины, в которую входят значимые мировые валюты. Каждая валюта в этой корзине относится к группе шести основных торговых партнеров США, не равных по своим экономическим возможностям, поэтому каждой валюте в индексе отводится конкретная доля влияния (вес):
Валюта | Вес |
---|---|
Евро (EUR) | 0.576 (57.6%) |
Японская йена (JPY) | 0.136 (13.6%) |
Британский фунт (GBP) | 0.119 (11.9%) |
Канадский доллар (CAD) | 0.091 (9.1%) |
Шведская крона (SEK) | 0.042 (4.2%) |
Швейцарский франк (CHF) | 0.036 (3.6%) |
Формула расчёта индекса американского доллара:
USDX = 50.14348112 * EURUSD^(-0.576) * USDJPY^(0.136) * GBPUSD^(-0.119) * USDCAD^(0.091) * USDSEK^(0.042) * USDCHF^(0.036)
Степени в расчёте, в которые возводятся курсы, соответствуют весу валют в используемой корзине. Коэффициент 50.14348112 приводит индекс доллара к значению 100.0 в случае, если в формулу будут подставлены курсы валют на март 1973 года. Таким образом, текущий USDX отражает изменение стоимости американского доллара по отношению к корзине валют по сравнению с котировками 1973 года. Значение индекса меньше 100 пунктов говорит об удешевлении доллара, а больше 100 пунктов — о повышении стоимости американской валюты по сравнению с 1973 годом.
Индекс евро (Euro Currency Index) — это средний показатель изменения курсов пяти мировых валют (доллара США, британского фунта, японской йены, швейцарского франка и шведской кроны) по отношению к евро.
Как торговый инструмент, индекс евро (EURX) введён 13 января 2006 года на бирже New York Board of Trade (NYBOT), тикеры ECX, EURX или E.
Индекс евро стал "эталоном" текущей стоимости единой европейской валюты для участников международных финансовых рынков, а также инструментом для проведения торговых операций.
Расчёт индекса евро по корзине из пяти валют совпадает с данными, используемыми Европейским Центральным Банком при расчётах торгово-взвешенного индекса евро по валютам тех стран, которые образуют основной внешнеторговый оборот стран Еврозоны. Большая часть международной торговли стран, входящих в Еврозону, приходится на США (31.55 %), далее следуют Великобритания — 30,56 %, Япония — 18,91 %, Швейцария — 11,13 % и Швеция — 7,85 %.
Основные принципы расчёта текущего значения индекса евро аналогичны принципам, применяемым при расчете индекса доллара США (USDX). Индекс евро рассчитывается с использованием метода расчета среднего геометрического взвешенного значения:
EURX = 34.38805726 * EURUSD^(0.3155) * EURGBP^(0.3056) * EURJPY^(0.1891) * EURCHF^(0.1113) * EURSEK^(0.0785)
где в качестве степени используется веса валют в используемой корзине.
Ставим задачу
Итак, что же мы хотим получить в итоге... Нам нужно создать синтетический инструмент, цена которого рассчитывается по приведённым выше формулам. Нам нужен полноценный график инструмента, который будет обновляться с приходом каждого нового тика на символах, используемых в корзине инструментов, и на этом графике можно запускать любые индикаторы, скрипты и советники.
В общем, нам нужно создать график синтетического инструмента, практически ничем не отличающийся от графиков стандартных инструментов. И для этой цели подойдёт программа-сервис, которая будет всё делать в собственном потоке, независимо от других открытых графиков и программ, запущенных на них.
При запуске сервиса будем проверять наличие нужного синтетического инструмента, создавать его при его отсутствии и размещать в окне Обзор рынка. Далее будет создана история синтетического инструмента — минутная и тиковая, и будет открыт график созданного инструмента. После проведения этих манипуляций, сервис будет получать новые тики по каждому из символов, на основе которых рассчитывается цена инструмента, и новые тики будут добавляться в историю созданного пользовательского символа. После перезапуска терминала программа-сервис автоматически запускается в случае, если она была запущена при закрытии терминала. Таким образом, единожды запущенный требуемый сервис, будет всегда перезапускаться самостоятельно при запуске терминала.
Таких пользовательских символов создадим два — индекс американского доллара и индекс евро. Для каждого из этих инструментов будет создана своя программа-сервис. Но созданы они будут на основе одного включаемого файла, в котором разместим все функции для создания нужного инструмента. В программе-сервисе будут лишь указаны требуемые для расчёта инструмента размер корзины, базовый коэффициент и структура символов с их весами. Всё остальное будет происходить внутри подключаемого файла при вызове функций, прописанных в нём. Это позволит на основе созданных функций создавать собственные индексы с иным набором валют и их весов — всего лишь указав список символов и вес каждого из них.
Пишем включаемый файл функций расчёта индексов валют
В каталоге терминала \MQL5\Services\ создадим новую папку Indexes\, а в ней — новый включаемый файл с именем CurrencyIndex.mqh. В него будем записывать все функции, необходимые для работы проекта.
В самом начале файла впишем необходимые для работы макроподстановки структуры и перечисления:
//+------------------------------------------------------------------+ //| CurrencyIndex.mqh | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #define SECONDS_IN_DAY (24*60*60) // количество секунд в сутках #define SECONDS_IN_MINUTE 60 // количество секунд в минуте #define MSECS_IN_MINIUTE (60*1000) // количество миллисекунд в минуте //--- структура символа корзины struct SymbolWeight { string symbol; // символ double weight; // вес }; //--- структура исторических данных struct str_rates { int index; // индекс данных MqlRates rates[]; // массив исторических данных }; //--- структура тиковых данных struct str_ticks { int index; // индекс данных MqlTick ticks[]; // массив тиков }; //--- перечисление типов цен enum ENUM_RATES_VALUES { VALUE_OPEN, // цена Open VALUE_HIGH, // цена High VALUE_LOW, // цена Low VALUE_CLOSE // цена Close }; int ExtDigits=5; // точность измерения цены символа
В структуре символа корзины прописаны два поля: наименование символа и его вес в корзине инструментов. При составлении корзины инструментов для расчёта индекса, удобно будет использовать массив таких структур: в него сразу же при инициализации запишем наименования символов и их веса, а далее из этого массива будем в цикле получать символ и его вес для расчёта цен индекса. При этом в массиве могут быть записаны любые символы с их весами, что даёт гибкость в создании любых корзин инструментов для расчёта индексов.
В структурах исторических и тиковых данных используются массивы соответствующих структур: MqlRates и MqlTick — в них будут содержаться данные по каждому из символов корзины инструментов. И есть ещё индекс данных в каждой из этих структур. Индекс необходим для указания номера существующего бара, с которого берутся данные для расчёта индекса. Например, для расчёта индекса на каком-либо баре, необходимо, чтобы у каждого из символов корзины инструментов, участвующих в расчёте индекса, на этом минутном баре были данные. Но не обязательно они могут быть на каждом из символов — где-то есть пропуски баров (если за эту минуту не было тиков на каком-то из символов). В этом случае и требуется указание индекса бара, с которого берутся данные для расчёта — там, где на символе нет данных, индекс увеличивается — чтобы данные взять с предыдущего бара. А так как мы не знаем заранее количество символов в корзине инструментов для расчёта индекса, то мы не можем заранее объявить в программе нужное количество индексов для каждого инструмента. Поэтому удобно их хранить и использовать в такой структуре.
В перечислении типов цен просто заданы константы, указывающие цену, которую нужно получить для расчёта цен баров.
При запуске сервиса в первую очередь необходимо создать пользовательский символ, построить исторические бары М1 за месяц и историю тиков. Это будет являться инициализацией сервиса. Напишем такую функцию:
//+------------------------------------------------------------------+ //| Инициализация сервиса | //+------------------------------------------------------------------+ bool InitService(const string custom_symbol,const string custom_group) { MqlRates rates[100]; MqlTick ticks[100]; //--- инициализируем пользовательский символ if(!CustomSymbolInitialize(custom_symbol,custom_group)) return(false); ExtDigits=(int)SymbolInfoInteger(custom_symbol,SYMBOL_DIGITS); //--- делаем активными все символы корзины инструментов, участвующие в расчёте индекса for(int i=0; i<BASKET_SIZE; i++) { //--- выбираем символ в окне "Обзор рынка" if(!SymbolSelect(ExtWeights[i].symbol,true)) { PrintFormat("cannot select symbol %s",ExtWeights[i].symbol); return(false); } //--- запрашиваем исторические данные баров и тиков по выбранному символу CopyRates(ExtWeights[i].symbol,PERIOD_M1,0,100,rates); CopyTicks(ExtWeights[i].symbol,ticks,COPY_TICKS_ALL,0,100); } //--- строим M1 бары за 1 месяц if(!PrepareRates(custom_symbol)) return(false); //--- получаем последние тики после построения M1 баров PrepareLastTicks(custom_symbol); //--- сервис инициализирован Print(custom_symbol," datafeed started"); return(true); }
Проверка существования и создание пользовательского символа происходит в функции CustomSymbolInitialize():
//+------------------------------------------------------------------+ //| Инициализация пользовательского символа | //+------------------------------------------------------------------+ bool CustomSymbolInitialize(string symbol,string group) { bool is_custom=false; //--- если символ выбран в окне "Обзор рынка", получаем флаг, что это пользовательский символ bool res=SymbolSelect(symbol,true); if(res) is_custom=(bool)SymbolInfoInteger(symbol,SYMBOL_CUSTOM); //--- если выбранный символ не пользовательский - создаём его if(!res) { if(!CustomSymbolCreate(symbol,group,"EURUSD")) { Print("cannot create custom symbol ",symbol); return(false); } //--- символ успешно создан - устанавливаем флаг, что это пользовательский символ is_custom=true; //--- помещаем созданный символ в окно "Обзор рынка" if(!SymbolSelect(symbol,true)) { Print("cannot select custom symbol ",symbol); return(false); } } //--- откроем график созданного пользовательского символа if(is_custom) { //--- получаем идентификатор первого окна открытых графиков long chart_id=ChartFirst(); bool found=false; //--- в цикле по списку открытых графиков найдём график созданного пользовательского символа while(chart_id>=0) { //--- если график открыт - сообщаем об этом в журнал, ставим флаг найденного графика и выходим из цикла поиска if(ChartSymbol(chart_id)==symbol) { found=true; Print(symbol," chart found"); break; } //--- на основании текущего выбранного графика получаем идентификатор следующего для очередной итерации поиска в цикле chart_id=ChartNext(chart_id); } //--- если график символа не найден среди открытых графиков if(!found) { //--- сообщаем об открытии графика M1 пользовательского символа, //--- получаем идентификатор открываемого графика и переходим на него Print("open chart ",symbol,",M1"); chart_id=ChartOpen(symbol,PERIOD_M1); ChartSetInteger(chart_id,CHART_BRING_TO_TOP,true); } } //--- пользовательский символ инициализирован return(is_custom); }
Здесь мы проверяем есть ли пользовательский символ с указанным именем. Если нет — создаём его. Далее ищем открытый график этого символа и, если график не найден среди открытых в терминале — открываем график.
После создания пользовательского символа и открытия его графика, необходимо создать историю периода M1 за месяц. Делается это при помощи функции PrepareRates():
//+------------------------------------------------------------------+ //| Подготовка исторических данных | //+------------------------------------------------------------------+ bool PrepareRates(const string custom_symbol) { str_rates symbols_rates[BASKET_SIZE]; int i,reserve=0; MqlRates usdx_rates[]; // массив-таймсерия синтетического инструмента MqlRates rate; // данные одного бара синтетического инструмента datetime stop=(TimeCurrent()/SECONDS_IN_MINUTE)*SECONDS_IN_MINUTE; // время бара M1 конечной даты datetime start=stop-31*SECONDS_IN_DAY; // время бара M1 начальной даты datetime start_date=0; //--- копируем исторические данные M1 за месяц для всех символов корзины инструментов start/=SECONDS_IN_DAY; start*=SECONDS_IN_DAY; // время бара D1 начальной даты for(i=0; i<BASKET_SIZE; i++) { if(CopyRates(ExtWeights[i].symbol,PERIOD_M1,start,stop,symbols_rates[i].rates)<=0) { PrintFormat("cannot copy rates for %s,M1 from %s to %s [%d]",ExtWeights[i].symbol,TimeToString(start),TimeToString(stop),GetLastError()); return(false); } PrintFormat("%u %s,M1 rates from %s",ArraySize(symbols_rates[i].rates),ExtWeights[i].symbol,TimeToString(symbols_rates[i].rates[0].time)); symbols_rates[i].index=0; //--- находим и устанавливаем минимальную ненулевую начальную дату из корзины символов if(start_date<symbols_rates[i].rates[0].time) start_date=symbols_rates[i].rates[0].time; } Print("start date set to ",start_date); //--- резерв массива исторических данных для избежания перераспределения памяти при изменении размера массива reserve=int(stop-start)/60; //--- установим начало всех исторических данных корзины символов на одну дату (start_date) for(i=0; i<BASKET_SIZE; i++) { int j=0; //--- до тех пор, пока j меньше количества данных в массиве rates и //--- время по индексу j в массиве меньше времени start_date - увеличиваем индекс while(j<ArraySize(symbols_rates[i].rates) && symbols_rates[i].rates[j].time<start_date) j++; //--- если индекс был увеличен, и он в пределах массива rates - уменьшим его на 1 для компенсации последнего приращения if(j>0 && j<ArraySize(symbols_rates[i].rates)) j--; //--- запишем полученный индекс в структуру symbols_rates[i].index=j; } //--- таймсерии USD index int array_size=0; //--- первый бар таймсерии M1 rate.time=start_date; rate.real_volume=0; rate.spread=0; //--- до тех пор, пока время бара меньше времени конечной даты таймсерии M1 while(!IsStopped() && rate.time<stop) { //--- если исторические данные бара инструмента рассчитаны if(CalculateRate(rate,symbols_rates)) { //--- увеличиваем массив-таймсерию на 1 и добавляем в него рассчитанные данные ArrayResize(usdx_rates,array_size+1,reserve); usdx_rates[array_size]=rate; array_size++; //--- сбросим размер резервного значения размера массива, так как он применяется только при первом изменении размера reserve=0; } //--- следующий бар таймсерии M1 rate.time+=PeriodSeconds(PERIOD_M1); start_date=rate.time; //--- в цикле по списку инструментов корзины for(i=0; i<BASKET_SIZE; i++) { //--- получаем текущий индекс данных int j=symbols_rates[i].index; //--- пока j в пределах данных таймсерии и, если время бара по индексу j меньше времени, установленного для этого бара в rate.time, увеличиваем индекс j while(j<ArraySize(symbols_rates[i].rates) && symbols_rates[i].rates[j].time<rate.time) j++; //--- если j в пределах данных таймсерии и время в start_date меньше времени данных таймсерии по индексу j //--- и время в таймсерии по индексу j меньше, либо равно времени в rate.time - записывавем в start_date время из таймсерии по индексу j if(j<ArraySize(symbols_rates[i].rates) && start_date<symbols_rates[i].rates[j].time && symbols_rates[i].rates[j].time<=rate.time) start_date=symbols_rates[i].rates[j].time; } //--- в цикле по списку инструментов корзины for(i=0; i<BASKET_SIZE; i++) { //--- получаем текущий индекс данных int j=symbols_rates[i].index; //--- пока j в пределах данных таймсерии и, если время бара по индексу j меньше времени, установленного для этого бара в start_date, увеличиваем индекс j while(j<ArraySize(symbols_rates[i].rates) && symbols_rates[i].rates[j].time<=start_date) symbols_rates[i].index=j++; } //--- в rate.time запишем время из start_date для последующего бара rate.time=start_date; } //--- добавляем в базу созданную таймсерию if(array_size>0) { if(!IsStopped()) { int cnt=CustomRatesReplace(custom_symbol,usdx_rates[0].time,usdx_rates[ArraySize(usdx_rates)-1].time+1,usdx_rates); Print(cnt," ",custom_symbol,",M1 rates from ",usdx_rates[0].time," to ",usdx_rates[ArraySize(usdx_rates)-1].time," added"); } } //--- успешно return(true); }
В функции сначала копируются данные по каждому из символов корзины инструментов и выставляется общее время начала копирования данных. Затем для каждого из инструментов устанавливается индекс данных, время которого совпадает со временем остальных символов корзины так, чтобы начальная дата была одинакова для всех символов корзины, а если для какого-либо символа нет бара по начальной дате, то для него устанавливается индекс ближайшего предыдущего бара, на котором данные существуют.
Далее в цикле побарно рассчитывается каждый бар таймсерии синтетического инструмента с корректировкой индекса каждого последующего бара так, чтобы данные на нём были либо с текущего бара, при наличии на нём данных, либо с предыдущего, если на текущем баре нет данных. Рассчитанные бары добавляются в массив таймсерии синтетического инструмента. После расчёта всех баров таймсерии инструмента, рассчитанная таймсерия добавляется в ценовую историю пользовательского символа.
Исторческие данные одного бара синтетического инструмента рассчитываются в функции CalculateRate():
//+------------------------------------------------------------------+ //| Расчёт цен и объёмов синтетического инструмента | //+------------------------------------------------------------------+ bool CalculateRate(MqlRates& rate,str_rates& symbols_rates[]) { double values[BASKET_SIZE]={0}; long tick_volume=0; int i; //--- получаем цены Open всех символов корзины инструментов в массив values[] for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_OPEN); //--- если тиковый объём нулевой, значит нет данных на этой минуте - возвращаем false if(tick_volume==0) return(false); //--- запишем совокупный объем всех таймсерий rate.tick_volume=tick_volume; //--- рассчитаем цену Open по ценам и весам всех инструментов корзины rate.open=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.open*=MathPow(values[i],ExtWeights[i].weight); //--- рассчитаем цену High по ценам и весам всех инструментов корзины for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_HIGH); rate.high=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.high*=MathPow(values[i],ExtWeights[i].weight); //--- рассчитаем цену Low по ценам и весам всех инструментов корзины for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_LOW); rate.low=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.low*=MathPow(values[i],ExtWeights[i].weight); //--- рассчитаем цену Close по ценам и весам всех инструментов корзины for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_CLOSE); rate.close=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.close*=MathPow(values[i],ExtWeights[i].weight); //--- возвращаем результат проверки цен на корректность return(CheckRate(rate)); }
Для каждой из цен бара (Open, High, Low и Close) синтетического инструмента производится расчёт цен по формуле синтетического инструмента:
Open USDX = 50.14348112 * Open EURUSD^(-0.576) * Open USDJPY^(0.136) * Open GBPUSD^(-0.119) * Open USDCAD^(0.091) * Open USDSEK^(0.042) * Open USDCHF^(0.036); High USDX = 50.14348112 * High EURUSD^(-0.576) * High USDJPY^(0.136) * High GBPUSD^(-0.119) * High USDCAD^(0.091) * High USDSEK^(0.042) * High USDCHF^(0.036); Low USDX = 50.14348112 * Low EURUSD^(-0.576) * Low USDJPY^(0.136) * Low GBPUSD^(-0.119) * Low USDCAD^(0.091) * Low USDSEK^(0.042) * Low USDCHF^(0.036); CloseUSDX = 50.14348112 * CloseEURUSD^(-0.576) * CloseUSDJPY^(0.136) * CloseGBPUSD^(-0.119) * CloseUSDCAD^(0.091) * CloseUSDSEK^(0.042) * CloseUSDCHF^(0.036);
Цены каждого символа корзины получаем в функции GetRateValue():
//+------------------------------------------------------------------+ //| Возвращает указанную цену бара | //+------------------------------------------------------------------+ double GetRateValue(long &tick_volume,str_rates &symbol_rates,datetime time,ENUM_RATES_VALUES num_value) { double value=0; // получаемое значение int index=symbol_rates.index; // индекс данных //--- если индекс в пределах таймсерии if(index<ArraySize(symbol_rates.rates)) { //--- в зависимости от типа запрашиваемых данных записываем соответствующее значение в переменную value switch(num_value) { //--- цена Open case VALUE_OPEN: if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) { value=symbol_rates.rates[index].open; //--- при запросе цены Open добавляем тиковый объём к переменной tick_volume, передаваемой по ссылке, //--- для получения суммарного объёма всех символов корзины инструментов tick_volume+=symbol_rates.rates[index].tick_volume; } } break; //--- цена High case VALUE_HIGH: if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) value=symbol_rates.rates[index].high; } break; //--- цена Low case VALUE_LOW: if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) value=symbol_rates.rates[index].low; } break; //--- цена Close case VALUE_CLOSE: if(symbol_rates.rates[index].time<=time) value=symbol_rates.rates[index].close; break; } } //--- возвращаем полученное значение return(value); }
Из функции CalculateRate() возвращается результат проверки рассчитанных цен синтетического инструмента:
//--- возвращаем результат проверки цен на корректность return(CheckRate(rate));
Так как все цены бара синтетического инструмента рассчитываются, то необходимо проверить корректность их расчёта и скорректировать ошибки (если таковые есть).
Всё это делается в функции CheckRate(), результат работы которой и возвращается:
//+------------------------------------------------------------------+ //| Проверка цен на корректность и возврат результата проверки | //+------------------------------------------------------------------+ bool CheckRate(MqlRates &rate) { //--- если цены представляют собой не корректные действительные числа, или меньше, либо равны нулю - возвращаем false if(!MathIsValidNumber(rate.open) || !MathIsValidNumber(rate.high) || !MathIsValidNumber(rate.low) || !MathIsValidNumber(rate.close)) return(false); if(rate.open<=0.0 || rate.high<=0.0 || rate.low<=0.0 || rate.close<=0.0) return(false); //--- нормализуем цены до требуемого количества знаков rate.open=NormalizeDouble(rate.open,ExtDigits); rate.high=NormalizeDouble(rate.high,ExtDigits); rate.low=NormalizeDouble(rate.low,ExtDigits); rate.close=NormalizeDouble(rate.close,ExtDigits); //--- корректируем при необходимости цены if(rate.high<rate.open) rate.high=rate.open; if(rate.low>rate.open) rate.low=rate.open; if(rate.high<rate.close) rate.high=rate.close; if(rate.low>rate.close) rate.low=rate.close; //--- всё успешно return(true); }
Если любая из рассчитанных цен является плюс или минус бесконечностью, либо "не числом" (NaN — not a number), либо меньше или равна нулю, функция возвращает false.
Далее все цены нормализуются до точности, заданной для символа, и проверяется корректность цен Open, High, Low и Close относительно друг друга. При необходимости, цены корректируются и возвращается true.
В рассматриваемой выше функции InitService() после построения минутных баров за месяц получаем последние тиковые данные синтетического инструмента и возвращаем true:
//--- строим M1 бары за 1 месяц if(!PrepareRates(custom_symbol)) return(false); //--- получаем последние тики после построения M1 баров PrepareLastTicks(custom_symbol); //--- сервис инициализирован Print(custom_symbol," datafeed started"); return(true); }
Тиковые данные получаем в функции PrepareLastTicks():
//+------------------------------------------------------------------+ //| Подготовка последних тиков | //+------------------------------------------------------------------+ void PrepareLastTicks(const string custom_symbol) { str_ticks symbols_ticks[BASKET_SIZE]; int i,j,cnt,reserve=0; MqlTick usdx_ticks[]; // массив тиков синтетического инструмента MqlTick tick={0}; // данные одного тика синтетического инструмента long time_to=TimeCurrent()*1000; // время конца тиковых данных в миллисекундах long start_date=(time_to/MSECS_IN_MINIUTE)*MSECS_IN_MINIUTE; // время открытия бара в миллисекундах со временем TimeCurrent() long time_from=start_date-MSECS_IN_MINIUTE; // время начала копирования тиковых данных в миллисекундах //--- если были тики за последнюю минуту if(SymbolInfoTick(custom_symbol,tick) && tick.time_msc>=start_date) { Print(custom_symbol," last tick at ",datetime(tick.time_msc/1000),":",IntegerToString(tick.time_msc%1000,3,'0')); str_rates symbols_rates[BASKET_SIZE]; bool copy_error=false; //--- в цикле по количеству символов в корзине инструментов for(i=0; i<BASKET_SIZE; i++) { //--- копируем два последних бара исторических данных инструмента if(CopyRates(ExtWeights[i].symbol,PERIOD_M1,0,2,symbols_rates[i].rates)!=2) { Print("cannot copy ",ExtWeights[i].symbol," rates [",GetLastError(),"]"); copy_error=true; break; } symbols_rates[i].index=1; } //--- рассчитываем данные последней минуты if(!copy_error) { MqlRates rate; double values[BASKET_SIZE]={0}; rate.time=datetime(start_date/1000); rate.real_volume=0; rate.spread=0; //--- если исторические данные бара инструмента рассчитаны if(CalculateRate(rate,symbols_rates)) { MqlRates usdx_rates[1]; //--- заменяем рассчитанными данными бара историю последнего бара M1 пользовательского инструмента usdx_rates[0]=rate; cnt=CustomRatesUpdate(custom_symbol,usdx_rates); if(cnt==1) { Print(custom_symbol,",M1 last minute rate ",rate.time," added"); //--- время в миллисекундах последующих добавляемых тииков start_date=tick.time_msc+1; } } else Print(custom_symbol,",M1 last minute rate ",rate.time," ",rate.open," ",rate.high," ",rate.low," ",rate.close," not updated"); } } //--- получаем все тики с начала предыдущей минуты for(i=0; i<BASKET_SIZE; i++) { if(CopyTicksRange(ExtWeights[i].symbol,symbols_ticks[i].ticks,COPY_TICKS_ALL,time_from,time_to)<=0) { PrintFormat("cannot copy ticks for %s",ExtWeights[i].symbol); return; } PrintFormat("%u %s ticks from %s",ArraySize(symbols_ticks[i].ticks),ExtWeights[i].symbol,TimeToString(symbols_ticks[i].ticks[0].time,TIME_DATE|TIME_SECONDS)); symbols_ticks[i].index=0; } //--- резерв массива тиков для избегания перераспределения памяти при изменении размера reserve=ArraySize(symbols_ticks[0].ticks); //--- установим начало всех тиков на одну дату start_date j=0; while(j<ArraySize(symbols_ticks[0].ticks) && symbols_ticks[0].ticks[j].time_msc<start_date) j++; if(j>=ArraySize(symbols_ticks[0].ticks)) { Print("no ticks at ",datetime(start_date/1000),":",IntegerToString(start_date%1000,3,'0')," (",start_date/1000,")" ); return; } symbols_ticks[0].index=j; long time_msc=symbols_ticks[0].ticks[j].time_msc; for(i=1; i<BASKET_SIZE; i++) { j=0; while(j<ArraySize(symbols_ticks[i].ticks) && symbols_ticks[i].ticks[j].time_msc<time_msc) j++; if(j>0 && j<ArraySize(symbols_ticks[i].ticks)) j--; symbols_ticks[i].index=j; } //--- тики USD index double values[BASKET_SIZE]={0}; int array_size=0; //--- первый тик tick.last=0; tick.volume=0; tick.flags=0; //--- в цикле от индекса j (от начальной даты всех тиков корзины инструментов) //--- по количеству полученных тиков первого инструмента корзины for(j=symbols_ticks[0].index; j<ArraySize(symbols_ticks[0].ticks); j++) { //--- записываем данные тика по индексу цикла j tick.time=symbols_ticks[0].ticks[j].time; // время тика tick.time_msc=symbols_ticks[0].ticks[j].time_msc; // время тика в миллисекундах //--- рассчитаем значение цены Bid по весам всех символов корзины инструментов values[0]=symbols_ticks[0].ticks[j].bid; symbols_ticks[0].index++; for(i=1; i<BASKET_SIZE; i++) values[i]=GetTickValue(symbols_ticks[i],symbols_ticks[0].ticks[j].time_msc); tick.bid=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) tick.bid*=MathPow(values[i],ExtWeights[i].weight); //--- цена Ask равна рассчитанной цене Bid инструмента tick.ask=tick.bid; //--- добавляем рассчитанный тик в массив тиков синтетического инструмента ArrayResize(usdx_ticks,array_size+1,reserve); usdx_ticks[array_size]=tick; array_size++; //--- обнуляем размер резервированной памяти, так как он нужен только при первом ArrayResize reserve=0; } //--- Добавляем в ценовую историю пользовательского инструмента данные из собранного массива тиков if(array_size>0) { Print(array_size," ticks from ",usdx_ticks[0].time,":",IntegerToString(usdx_ticks[0].time_msc%1000,3,'0')," prepared"); cnt=CustomTicksAdd(custom_symbol,usdx_ticks); if(cnt>0) Print(cnt," ticks applied"); else Print("no ticks applied"); } }
Логика функции расписана в комментариях к коду. Сначала получаем два последних бара инструмента и заменяем ими данные в истории инструмента. Затем получаем тиковые данные с начала предыдущего бара, рассчитываем по ним значение тика и добавляем его в массив таймсерии инструмента. По окончании расчёта всех тиков, заменяем исторические тиковые данные инструмента данными из заполненного массива тиков.
Данные из массива тиков для расчёта тика синтетического инструмента получаем функцией GetTickValue():
//+------------------------------------------------------------------+ //| Возвращает значение тика | //+------------------------------------------------------------------+ double GetTickValue(str_ticks &symbol_ticks,long time_msc) { double value=0; //--- если индекс данных, записанный в структуре symbol_ticks, находится в пределах массива тиков структуры if(symbol_ticks.index<ArraySize(symbol_ticks.ticks)) { //--- получаем значение цены Bid из структуры по индексу данных value=symbol_ticks.ticks[symbol_ticks.index].bid; //--- если время в структуре в миллисекундах по индексу в структуре меньше переданного в функцию времени if(symbol_ticks.ticks[symbol_ticks.index].time_msc<time_msc) { //--- до тех пор, пока индекс находится в пределах таймсерии в структуре и //--- если время в структуре меньше переданного в функцию времени - увеличиваем индекс while(symbol_ticks.index<ArraySize(symbol_ticks.ticks) && symbol_ticks.ticks[symbol_ticks.index].time_msc<time_msc) symbol_ticks.index++; } } //--- возвращаем полученное значение return(value); }
После успешной инициализации сервиса, он должен в бесконечном цикле получать данные всех символов корзины инструментов и обновлять историю баров и тиков инструмента, тем самым эмулируя в реальном времени поток данных пользовательского символа. Всё это происходит в функции ProcessTick():
//+------------------------------------------------------------------+ //| Обработка тиков | //+------------------------------------------------------------------+ void ProcessTick(const string custom_symbol) { static long last_time_msc=0; // время в миллисекундах последнего тика static MqlTick synth_tick[1]; // структура последнего тика синтетического инструмента static MqlTick ticks[BASKET_SIZE]; // массив данных последних тиков корзины символов static MqlTick tick; // вспомогательная переменная для получения данных и поиска времени int success_cnt=0; int change_cnt=0; //--- инициализируем время тика синтетического символа synth_tick[0].time=0; //--- в цикле по количеству символов в корзине инструментов for(int i=0; i<BASKET_SIZE; i++) { //--- получаем данные очередного символа if(SymbolInfoTick(ExtWeights[i].symbol,tick)) { //--- увеличиваем количество успешных запросов данных success_cnt++; //--- получаем самое свежее время из списка символов корзины if(synth_tick[0].time==0) { synth_tick[0].time=tick.time; synth_tick[0].time_msc=tick.time_msc; } else { if(synth_tick[0].time_msc<tick.time_msc) { synth_tick[0].time=tick.time; synth_tick[0].time_msc=tick.time_msc; } } //--- сохраняем полученные данные по символу в массиве ticks в соответствии с индексом символа корзины ticks[i]=tick; } } //--- если получены тики всех инструментов корзины, и это новый тик if(success_cnt==BASKET_SIZE && synth_tick[0].time!=0 && last_time_msc<synth_tick[0].time_msc) { //--- сохраняем время последнего тика last_time_msc=synth_tick[0].time_msc; //--- рассчитываем значение цены Bid синтетического инструмента synth_tick[0].bid=MAIN_COEFF; for(int i=0; i<BASKET_SIZE; i++) synth_tick[0].bid*=MathPow(ticks[i].bid,ExtWeights[i].weight); //--- цена Ask равна цене Bid synth_tick[0].ask=synth_tick[0].bid; //--- добавляем в ценовую историю пользовательского инструмента новый тик CustomTicksAdd(custom_symbol,synth_tick); } }
Здесь мы в цикле получаем тики всех инструментов корзины, одновременно рассчитывая время тика. За время тика берётся время последнего пришедшего тика из всех символов корзины инструмента. Если получены тики от каждого инструмента корзины, то рассчитываем значение цены Bid тика синтетического инструмента по формуле расчёта индекса. После расчёта цены Bid приравниваем значение цены Ask к значению рассчитанной цены Bid синтетического инструмента. По окончании всех расчётов добавляем в ценовую историю пользовательского символа данные из массива тиков synth_tick, эмулируя тем самым приход нового тика синтетического инструмента.
Все функции для создания индексов валют и для работы с их данными готовы. Теперь, на основе созданных функций, напишем программы-сервисы для создания индекса американского доллара и индекса евро.
Тестируем программы-сервисы
В папке, в которой написали файл включаемых функций \MQL5\Services\Indexes\, создадим новый файл программы с типом Сервис с именем USD_Index.mq5.
Сразу же определим макроподстановки для имени пользовательского символа и его группы. Укажем количество символов в корзине инструментов и базовый коэффициент.
Подключим файл написанных выше функций для работы с индексами валют и определим структуру символа корзины, указав наименования всех символов и их веса:
//+------------------------------------------------------------------+ //| USD_Index.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property service #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #define CUSTOM_SYMBOL "USDX.synthetic" #define CUSTOM_GROUP "Synthetics" #define BASKET_SIZE 6 #define MAIN_COEFF 50.14348112 #include "CurrencyIndex.mqh" SymbolWeight ExtWeights[BASKET_SIZE]= { { "EURUSD",-0.576 }, { "USDJPY", 0.136 }, { "GBPUSD",-0.119 }, { "USDCAD", 0.091 }, { "USDSEK", 0.042 }, { "USDCHF", 0.036 } };
Теперь в единственном обработчике сервиса OnStart() инициализируем пользовательский символ и запустим обработку поступающих новых тиков в бесконечном цикле:
//+------------------------------------------------------------------+ //| Service program start function | //+------------------------------------------------------------------+ void OnStart() { if(!InitService(CUSTOM_SYMBOL,CUSTOM_GROUP)) return; //--- while(!IsStopped()) { ProcessTick(CUSTOM_SYMBOL); Sleep(10); } //--- Print(CUSTOM_SYMBOL," datafeed stopped"); }
Скомпилируем файл сервиса и запустим его:
После добавления сервиса и его запуска, в журнале увидим сообщения:
USD_Index open chart USDX.synthetic,M1 USD_Index 31117 EURUSD,M1 rates from 2024.07.29 00:00 USD_Index 31101 USDJPY,M1 rates from 2024.07.29 00:01 USD_Index 31114 GBPUSD,M1 rates from 2024.07.29 00:00 USD_Index 31112 USDCAD,M1 rates from 2024.07.29 00:01 USD_Index 30931 USDSEK,M1 rates from 2024.07.29 00:00 USD_Index 31079 USDCHF,M1 rates from 2024.07.29 00:01 USD_Index start date set to 2024.07.29 00:01:00 USD_Index 31119 USDX.synthetic,M1 rates from 2024.07.29 00:01:00 to 2024.08.27 14:45:00 added USD_Index 44 EURUSD ticks from 2024.08.27 14:45:01 USD_Index 45 USDJPY ticks from 2024.08.27 14:45:00 USD_Index 40 GBPUSD ticks from 2024.08.27 14:45:00 USD_Index 45 USDCAD ticks from 2024.08.27 14:45:00 USD_Index 66 USDSEK ticks from 2024.08.27 14:45:00 USD_Index 29 USDCHF ticks from 2024.08.27 14:45:00 USD_Index 12 ticks from 2024.08.27 14:46:02:319 prepared USD_Index 12 ticks applied USD_Index USDX.synthetic datafeed started
Будет открыт график созданного синтетического инструмента, который далее будет обновляться как обычный график стандартного символа. На графике можно разместить индикаторы и производить любой технический анализ:
Теперь создадим сервис для создания и работы с графиком индекса евро.
В папке \MQL5\Services\Indexes\, создадим новый файл программы с типом Сервис с именем EUR_Index.mq5:
//+------------------------------------------------------------------+ //| EUR_Index.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property service #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #define CUSTOM_SYMBOL "EURX.synthetic" #define CUSTOM_GROUP "Synthetics" #define BASKET_SIZE 5 #define MAIN_COEFF 34.38805726 #include "CurrencyIndex.mqh" SymbolWeight ExtWeights[BASKET_SIZE]= { { "EURUSD", 0.3155 }, { "EURGBP", 0.3056 }, { "EURJPY", 0.1891 }, { "EURCHF", 0.1113 }, { "EURSEK", 0.0785 } }; //+------------------------------------------------------------------+ //| Service program start function | //+------------------------------------------------------------------+ void OnStart() { if(!InitService(CUSTOM_SYMBOL,CUSTOM_GROUP)) return; //--- while(!IsStopped()) { ProcessTick(CUSTOM_SYMBOL); Sleep(10); } //--- Print(CUSTOM_SYMBOL," datafeed stopped"); } //+------------------------------------------------------------------+
Всё отличие от файла программы-сервиса для создания и работы с графиком индекса американского доллара — это размер корзины символов, базовый коэффициент и структура символов корзины с их весами.
Скомпилируем программу и запустим сервис. В журнале увидим записи:
EUR_Index open chart EURX.synthetic,M1 EUR_Index 31148 EURUSD,M1 rates from 2024.07.29 00:00 EUR_Index 31137 EURGBP,M1 rates from 2024.07.29 00:01 EUR_Index 31148 EURJPY,M1 rates from 2024.07.29 00:01 EUR_Index 31123 EURCHF,M1 rates from 2024.07.29 00:00 EUR_Index 30898 EURSEK,M1 rates from 2024.07.29 00:00 EUR_Index start date set to 2024.07.29 00:01:00 EUR_Index 31151 EURX.synthetic,M1 rates from 2024.07.29 00:01:00 to 2024.08.27 15:16:00 added EUR_Index 53 EURUSD ticks from 2024.08.27 15:16:00 EUR_Index 65 EURGBP ticks from 2024.08.27 15:16:00 EUR_Index 109 EURJPY ticks from 2024.08.27 15:16:00 EUR_Index 68 EURCHF ticks from 2024.08.27 15:16:00 EUR_Index 57 EURSEK ticks from 2024.08.27 15:16:00 EUR_Index 15 ticks from 2024.08.27 15:17:00:877 prepared EUR_Index 15 ticks applied EUR_Index EURX.synthetic datafeed started
Далее будет открыт график индекса евро, с которым можно полноценно работать:
Заключение
На примере программ-сервисов мы научились создавать пользовательские символы и поддерживать полноценное обновление их графиков. При перезапуске терминала также будут перезапущены и все сервисы, которые работали в момент закрытия клиентского терминала. Таким образом, единожды запустив сервис, он будет автоматически стартовать при запуске терминала. А данные сервисы будут постоянно "прокидывать" на график новые тики, постоянно поддерживая в актуальном состоянии историю синтетического инструмента.
На основе рассмотренных сервисов можно создавать собственные индексы на собственных данных и наглядно видеть график изменения корзины инструментов, использующихся в созданном индексе валюты. Например, можно построить график торгово-взвешенного индекса американского доллара (TWDI). Валютная корзина TWDI состоит из 26 символов и является более актуальной на сегодняшний день по сравнению с обычным индексом американского доллара.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Спасибо за статью!
Получилась отличная альтернатива для тестирования стратегий с использованием индекса доллара,
Лично я столкнулся с проблемой, что у брокера единой истории индекса доллара нет, только CFD контракты с определенной датой погашения.
Единственно, в предложенном решении, спред отличается от реального символа, но в моем случае это не важно, так как сам индекс по сути не торгуется
Добрый день!
подскажите как получить историю на бОльшем периоде? например получить данные за последний год..
Попробовал сам разобраться, в файле CurrencyIndex в строке 215 заменил 31 на 365
Увы самому разобраться не получилось, прошу подсказать.
Добрый день!
подскажите как получить историю на бОльшем периоде? например получить данные за последний год..
Попробовал сам разобраться, в файле CurrencyIndex в строке 215 заменил 31 на 365
Увы самому разобраться не получилось, прошу подсказать.
Попробуйте постепенно увеличивать количество дней истории
Попробовал,
Индексы из примеров раньше 1 июля не удается получить..
Попробовал сам индекс из 2х инструментов сделать, качнуло чуть глубже - до 26 июня..
История тиков за последний год доступна.
В любом случае спасибо за полезный инструмент )
спасибо большое автору за открытие новых возможностей в торговле индексами. В настоящее время формирую синтетику по металлам серебро- золото...
Спасибо за наводку, я немного наоборот сделал золото-серебро, по сути тоже самое, и самое интересное это можно проторговывать ))) нужно как то, хотя бы с начала года проверить