Формат файлов тестера *.FXT

 
В своей работе тестер использует файл *.FXT со сгенерированной последовательностью баров. Проводя моделирование баров, он берет из этого файла новые бары и обновляет/добавляет к существующим.

Любой может отказаться от стандартного моделирования баров и подсунуть тестеру свой файл для расчетов. Для этого достаточно перед тестом отключить галочку "Recalculate", поместить необходимый файл *.FXT в каталог /tester/history . Имя файла должно быть в определенном формате SymbolPeriod_Type.fxt, где Symbol - название символа, Period - период графика в минутах, Type - тип выбранного моделирования (0 - по всем тикам, 1 - по контрольным точкам, 2 - по ценам открытия).

Ниже приведено краткое описание формата.

Файл начинается с заголовка:
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
struct TestHistoryHeader
  {
   int               version;
   char              copyright[64];      // копирайт
   char              symbol[12];
   int               period;
   int               model;              // для какого режима тестирования сгенерирована последовательность
   int               bars;               // количество баров в истории
   time_t            fromdate;
   time_t            todate;
   double            modelquality;       // качество генерации
   //---- общие параметры
   char              currency[12];       // валютная база
   int               spread;
   int               digits;
   double            point;
   int               lot_min;            // минимальный размер лота
   int               lot_max;            // максимальный размер лота
   int               lot_step;
   int               stops_level;        // величина отступа стопов
   int               gtc_pendings;       // указание закрывать отложенные ордера в конце дня
   //---- параметры расчёта профитов
   double            contract_size;      // величина контракта
   double            tick_value;         // цена одного пункта
   double            tick_size;          // размер одного пункта
   int               profit_mode;        // тип расчёта профитов { PROFIT_CALC_FOREX, PROFIT_CALC_CFD, PROFIT_CALC_FUTURES }
   //---- расчёты свопов
   int               swap_enable;        // разрешение взятия свопа
   int               swap_type;          // тип свопа            { SWAP_BY_POINTS, SWAP_BY_DOLLARS, SWAP_BY_INTEREST }
   double            swap_long;
   double            swap_short;         // величина овернайт свопа
   int               swap_rollover3days; // день тройных свопов
   //---- расчёт маржи
   int               leverage;           // плечо
   int               free_margin_mode;   // тип расчетов свободной маржи   { MARGIN_DONT_USE, MARGIN_USE_ALL, MARGIN_USE_PROFIT, MARGIN_USE_LOSS }
   int               margin_mode;        // тип расчетов маржи             { MARGIN_CALC_FOREX,MARGIN_CALC_CFD,MARGIN_CALC_FUTURES,MARGIN_CALC_CFDINDEX };
   int               margin_stopout;     // уровень стопаута
   double            margin_initial;     // маржевые требования
   double            margin_maintenance; // обязательные маржевые требования
   double            margin_hedged;      // маржевые требования на хеджируемых позициях
   double            margin_divider;     // делитель маржи
   char              margin_currency[12];// валюта маржевых требований
   //---- расчёт комиссии
   double            comm_base;          // базовая комиссия
   int               comm_type;          // тип базовой комиссии { COMM_TYPE_MONEY, COMM_TYPE_PIPS, COMM_TYPE_PERCENT }
   int               comm_lots;          // за лот или сделку    { COMMISSION_PER_LOT, COMMISSION_PER_DEAL }
   //----
   int               from_bar;           // номер бара fromdate
   int               to_bar;             // номер бара todate
   int               start_period[6];    // номер бара с которого началось моделирование меньшего периода
   //----
   int               reserved[64];
  };


После чего идет массив сгенерированных баров:

#pragma pack(push,1)
struct TestHistory
  {
   time_t            otm;                // время бара
   double            open;               // значения OHLCV
   double            low;
   double            high;
   double            close;
   double            volume;
   time_t            ctm;                // текущее рабочее время внутри бара
   int               flag;               // флаг запуска эксперта (0-бар модифицируем, а эксперта не запускаем)
  };
#pragma pack(pop)


Чтобы было более понятно, можно посмотреть как терминал сам заполняет поля этих структур.

 
Можно подробнее насчет struct TestHistory. Я предполагал, что в тестере просто хранится поток котировок, которые передаются в терминал так же как и реальные котировки, а терминал уже сам из них формирует бары, а у вас там хранятся сами бары. Можете объяснить, как из этих баров получаются котировки, т.е. Bid для каждого тика? Или у вас эти бары просто разлиные этапы развития одного главного бара, и каждый раз в метод start() передается сlose последнего бара?

Было бы не плохо, если бы вы привели пример заполнения TestHistory для различных методов. Например тестируемый период H1 и мы эмулируем два H1 бара 2005.07.13 00:00 и 2005.07.13 01:00. Для первого метода моделирования у нас будет две TestHistory, для второго четыре, для третьего .... и соответственно значения этих TestHistory для каждого метода моделирования.

Для чего используется flag?
 
когда вы открываете автономно сгенерированный файл (сколько можно про это говорить?), то как раз и видно, что сгенерировано. фактически, это состояние бара на определённый (time_t ctm) момент времени. к сожалению, этот момент времени не виден на графике, так как он не входит в формат HST. обратите внимание на то, что первые 6 полей формата TestHistory точно соответствуюь формату RateInfo.
 
Вернулся из отпуска и вижу, что формат FXT большой заинтересованности у пользователей не вызвал :) Или кто-то уже написал свой конвертор?

Если нет, подскажите можно ли средствами MQL4 сформировать данный файл? Прежде всего меня интерсует совпадает ли байтовое представление double и int, записываемых через FileWriteDouble(), FileWriteInteger() и т.п. и байтовое представление этих данных, используемое оригинальным генератором.

Как записать, например, char margin_currency[12]? Т.е. масив символов заданной длины?

Как рассчитывать double modelquality?

Правильно ли я понял, что в TestHistory на int flag принимающий значения {0,1} расходуется 4 байта? Когда у этого флага должно быть значение 0?
 
подскажите можно ли средствами MQL4 сформировать данный файл?

можно

Прежде всего меня интерсует совпадает ли байтовое представление double и int, записываемых через FileWriteDouble(), FileWriteInteger() и т.п. и байтовое представление этих данных, используемое оригинальным генератором.

Как записать, например, char margin_currency[12]? Т.е. масив символов заданной длины?

посмотрите наш пример period_converter - там решаются аналогичные задачи

Как рассчитывать double modelquality?

как хотите, так и рассчитывайте. число должно быть в диапазоне от 0 до 100. иначе файл будет признан ошибочным и стёрт.

Правильно ли я понял, что в TestHistory на int flag принимающий значения {0,1} расходуется 4 байта? Когда у этого флага должно быть значение 0?

да, 4 байта. на флаге, равном 0, эксперт не запускается. если Вы почитаете нашу статью про моделирование, то обратите внимание на тот факт, что при моделировании по ценам открытия бар рисуется 2 раза - сначала открытие с флагом 1, потом закрытие бара с флагом 0 (первые 100 баров также помечены флагом 0). в сгенерированном файле хранятся "слепки" баров на тот или иной момент времени.

структура заголовка претерпела изменения. следующим постом я выложу обновлённую структуру
 
struct TestHistoryHeader
  {
   int               version;
   char              copyright[64];      // копирайт
   char              symbol[12];
   int               period;
   int               model;              // для какого режима тестирования сгенерирована последовательность
   int               bars;               // количество баров в истории
   time_t            fromdate;
   time_t            todate;
   double            modelquality;       // качество генерации
   //---- общие параметры
   char              currency[12];       // валютная база
   int               spread;
   int               digits;
   double            point;
   int               lot_min;            // минимальный размер лота
   int               lot_max;            // максимальный размер лота
   int               lot_step;
   int               stops_level;        // величина отступа стопов
   int               gtc_pendings;       // указание закрывать отложенные ордера в конце дня
   //---- параметры расчёта профитов
   double            contract_size;      // величина контракта
   double            tick_value;         // цена одного пункта
   double            tick_size;          // размер одного пункта
   int               profit_mode;        // тип расчёта профитов { PROFIT_CALC_FOREX, PROFIT_CALC_CFD, PROFIT_CALC_FUTURES }
   //---- расчёты свопов
   int               swap_enable;        // разрешение взятия свопа
   int               swap_type;          // тип свопа            { SWAP_BY_POINTS, SWAP_BY_DOLLARS, SWAP_BY_INTEREST }
   double            swap_long;
   double            swap_short;         // величина овернайт свопа
   int               swap_rollover3days; // день тройных свопов
   //---- расчёт маржи
   int               leverage;           // плечо
   int               free_margin_mode;   // тип расчетов свободной маржи   { MARGIN_DONT_USE, MARGIN_USE_ALL, MARGIN_USE_PROFIT, MARGIN_USE_LOSS }
   int               margin_mode;        // тип расчетов маржи             { MARGIN_CALC_FOREX,MARGIN_CALC_CFD,MARGIN_CALC_FUTURES,MARGIN_CALC_CFDINDEX };
   int               margin_stopout;     // уровень стопаута
   double            margin_initial;     // маржевые требования
   double            margin_maintenance; // обязательные маржевые требования
   double            margin_hedged;      // маржевые требования на хеджируемых позициях
   double            margin_divider;     // делитель маржи
   char              margin_currency[12];// валюта маржевых требований
   //---- расчёт комиссии
   double            comm_base;          // базовая комиссия
   int               comm_type;          // тип базовой комиссии { COMM_TYPE_MONEY, COMM_TYPE_PIPS, COMM_TYPE_PERCENT }
   int               comm_lots;          // за лот или сделку    { COMMISSION_PER_LOT, COMMISSION_PER_DEAL }
   //----
   int               from_bar;           // номер бара fromdate
   int               to_bar;             // номер бара todate
   int               start_period[6];    // номер бара с которого началось моделирование меньшего периода
   //----
   int               reserved[64];
  };


поле version должно содержать число 402
ниже приведу наш код проверки сгенерированного файла на адекватность

      //---- проверяем адекватность заголовка
      if(fread(&m_header,sizeof(m_header),1,m_file)==1 &&
                m_header.version==TestHistoryVersion   &&
                m_header.model==model && m_header.period==scheme->period &&
                strcmp(m_header.symbol,scheme->symbol)==0)
        {
         //---- проверяем дальше
         if(m_header.bars<=100   || m_header.modelquality<=0.0 ||
            m_header.spread<0    || m_header.spread>100000 ||
            m_header.digits<0    || m_header.digits>8 ||
            m_header.lot_min<0   || m_header.lot_step<=0 ||
            m_header.leverage<=0 || m_header.leverage>500 ||
            m_header.swap_rollover3days<0 ||
            m_header.swap_rollover3days>6 ||
            m_header.stops_level<0) refresh=TRUE;
         //---- проверяем необходимость обновления
         if(refresh==FALSE)
           {
            m_testes_total=(_filelength(_fileno(m_file))-sizeof(m_header))/sizeof(TestHistory);
            return(TRUE);
           }
        }


некоторые дополнительные пояснения. как я уже сказал version должна быть равна 402. символ, период и модель в заголовке должны точно соответствовать имени файла SSSSSSPP_M.fxt

 
Спасибо за разъяснения. Будем пробовать. Можно ли еще объяснить смысл следующих параметров:
int               from_bar;           // номер бара fromdate
int               to_bar;               // номер бара todate
int               start_period[6];   // номер бара с которого началось моделирование меньшего периода



Когда я в настройках тестирования в интерфейсе меняю промежкток дат для тестирования, у меня что переписывается заголовок файла, а в самом файле всегда содержатся тики по всем барам моделируемого периода (независимо от того какая дата установлена)?

Если тестеру подложить свой файл с тиками, можно ли будет из интерфейса менять начало и конец тестирования (UseDate) или при попытке изменить дату свой файл с тиками будет испорчен?

 
эти параметры нужны для расчёта качества моделирования и для рисования цветной полоски в отчёте. можете забить нулями.
в заголовке файла пишется состояние на момент генерации. например, при генерации днёвок данные до даты From и после даты To будут представлены только конечными состояниями баров без промежуточных тиков.
без проблем можно использовать даты, главное, не нажимайте галочку "пересчитать"
 
2dev
Делись успехами :)
иначе не понятно или моделирование "хромает", или тестер "врет" что скорее всего, т.к. смоделированные бары (те что на графике вроде совпадают) хотел сравнить с реальными , но не знаю как сделать экспорт смоделированных баров в ексцел, а котировки записанные экспертом (что ему подаются на вход) в режиме тестирования отличаются от реальных даже на контрольных точках, про внутри я пока молчу
 
Последнюю неделю не было времени заниматься тестером, только сегодня получилось. Выяснил, что формат fxt несколько отличается от представленного выше в ветке.

1. Такие поля как ниже, реально не int, a double.
int               lot_min;            // минимальный размер лота
int               lot_max;            // максимальный размер лота
int               lot_step;



Так же есть и другие поля объявленные как int, а реально они double (в исходном коде это видно - код записывающий их как int закомментирован, а ниже идет запись как double).

2. Между modelquality и currency[12] в TestHistoryHeader, есть еще 4 байта, природа которых неизвества (либо какой-то недокументированный параметр либо где-то выше невыявленный double) . Сейчас я в то место пишу 16457 (такое же значение туда пишет оригинальный тестер). Что это за 4 байта?

3. Есть подозрение, что между TestHistoryHeader и TestHistory, есть что-то еще? Влияет ли на файл #pragma pack(push,1) - эту инструкцию я игнорировал?

Поскольку все вышеречисленное я искал с помощью hex-редактора, возможно, что-то я интерпртеровал неправильно, Слава, посмотри, пожалуйста, мой код, который пишет заголовок.


Непосредственно о тестере. Принцип работы прост - используются данные 1 (Одного) самого младшего интересующего периода и из них генерируются данные нужного старшего периода. Параметр CurrentBarsPerTargetBar отвечают за то, как будут группироваться бары. Например, из M1 можно генерировать H1, на котром потом тестироваться, установив CurrentBarsPerTargetBar == 60. Или генерировать D1 из H1 для быстрого предварительного тестирования, установив CurrentBarsPerTargetBar == 24.

Изменяя FromDate и ToDate можно ограничивать диапазон первичных данных и следовательно размер файла. По умолчанию нет ограничений, но полезно знать, что при генерации из M1 любого таймфрема размер будет около 70Мб/год.

Сам алгоритм моделирования предельно прост (а значит близок к реальности :))) и представлен в методе writeTestHistory():

		double peak1 = high0, peak2 = low0;		
		if (close0 > open0 || (MathAbs(close0 - open0) < Point && i % 2 == 0)) {//up bar
			peak1 = low0;
			peak2 = high0;		
		}
		
		writeTick(barTimeStep, open0, tickVolume, 1);
		
		if (!SkipEqualQuotes || !(MathAbs(open0 - peak1) < Point))//skip peak1==open0 
			writeTick(barTimeStep, peak1, tickVolume, 1);
			
		writeTick(barTimeStep, median0, tickVolume, 1);
		writeTick(barTimeStep, peak2, tickVolume, 1);
		
		if (!SkipEqualQuotes || !(MathAbs(close0 - peak2) < Point))//skip close0==peak2
			writeTick(barTimeStep, close0, tickVolume, 1);



Из каждого исходного бара берется 5 котировок OHLC и средняя цена M = (H+L)/2. Если O<C, то считаем что вначале будет достигнут минимум бара, потом максимум и наоборот. В итоге эти пять котировок будут записаны в выходной файл, как OLMHC или OHMLC. Если открытие и закрытие совпадают с близким пиком, их можно не записывать (параметр SkipEqualQuotes). Таким образом на один H1 бар при генерации из M1 баров мы получим 60*5=300 тиков. каждый из этих тиков будет находится внутри соотвествующего M1 бара, т.е. максимально близко к реальному движению цены. Таким образом максимальная погрешность этого морделирования составляет примерно 1/4 от среднего разброса H и L внутри первичного бара (в случае M1 - около 3 пунктов) , а средняя погрешность, понятное дело, стремиться к нулю :)

Графики генерируются и открываются через Open Offline. Пробовал EURUSD и USDCHF. Тестирование идет, но в конце выдается ошибка
TestGenerator: error reading file for 'EURUSD60'

Тестированием я еще плотно не занимался, все больше сгенерированные графики с оригинальными сравнивал.

Собственно код скрипта:

//+------------------------------------------------------------------+
//|                                         TestHistoryGenerator.mq4 |
//|                                                              dev |
//|Генерирует историю для тестирования экспертов на основе OHLCM     |
//+------------------------------------------------------------------+
#property copyright "dev"
#property link      "self at dogada dot com"
#property show_inputs

#define VERSION 402
#define COPYRIGHT "Public domain, generated by dev"


//сколько баров текущего периода попадает в 1 бар моделируемого
extern int CurrentBarsPerTargetBar = 4;
extern datetime FromDate = 0;//D'2005.07.25 00:00'
extern datetime ToDate = 0;
extern bool SkipEqualQuotes = true;

int fileHandle = -1;
int targetPeriod, targetBars, targetFromDate, targetToDate;
double targetModelQuality;
string targetBaseCurrency = "";

int firstBarOffset, lastBarOffset;

int start() {
	Print(Symbol() + " " + Period() + " Bars=" + Bars);
	if (analyzeTarget() < 0)
		return (-1);

	fileHandle = FileOpen(Symbol() + targetPeriod + "_0.fxt", FILE_BIN | FILE_WRITE);
		
   if(fileHandle < 0)
   	return(-1);
   	
		   	
	writeTestHistoryHeader();
	writeTestHistory();   		
   return(0);
}

void deinit() {
   if (fileHandle >= 0) {
   	FileClose(fileHandle);
   	//TODO: check last error
   	fileHandle =-1;
   }
}

int analyzeTarget() {

	if (Bars < 100)
		return (-1);
		
	if (CurrentBarsPerTargetBar < 2)
		return (-2);

	if (ToDate > Time[1])
		ToDate = 0;
		
	string symbol = Symbol();
	
	int period = Period();
	targetPeriod = CurrentBarsPerTargetBar*Period();
	targetBaseCurrency = StringSubstr(Symbol(), 0, 3);
		

	Print("targetPeriod="  + targetPeriod);
	firstBarOffset = Bars - 1;
	if (FromDate > 0)
		firstBarOffset = iBarShift(symbol, period, FromDate, false);
	
	lastBarOffset = 1;
	
	if (ToDate > 0)
		lastBarOffset = iBarShift(symbol, period, ToDate, false);
	
	if (firstBarOffset - lastBarOffset < 2*CurrentBarsPerTargetBar || 
	firstBarOffset < 0 || lastBarOffset < 0)
		return (-3);
	
	int targetSeconds = targetPeriod*60;
	targetFromDate = (Time[firstBarOffset]/targetSeconds)*targetSeconds;
	targetToDate = (Time[lastBarOffset]/targetSeconds)*targetSeconds;					

	targetBars = 0;
	int lastTargetBarTime = targetFromDate;
	for (int i = firstBarOffset; i >= lastBarOffset; i--) {
		int time0 = Time[i];
		if (time0 >= lastTargetBarTime + targetSeconds || i == lastBarOffset) {
			targetBars++;
			lastTargetBarTime = (time0/targetSeconds)*targetSeconds;
		}	
	}

	
	Print(" From=" + timeStr(targetFromDate)  + " To=" + timeStr(targetToDate) +
	" targetBars=" + targetBars + " firstBarOffset=" + firstBarOffset + 
	" lastBarOffset=" + lastBarOffset);
	
	if (targetBars < 100)
		return (-4);		

	int innerBars = targetBars*CurrentBarsPerTargetBar;
	int missedBars = innerBars - (firstBarOffset - lastBarOffset + 1);
	targetModelQuality = MathMin(1.0*(firstBarOffset - lastBarOffset + 1)/innerBars, 1.0);
	
	Print("Model quality=" + targetModelQuality + 
	", was missed " + missedBars + " from " + innerBars + " inner bars.");
}

void writeTestHistoryHeader() {
	FileWriteInteger(fileHandle, VERSION, LONG_VALUE);
   FileWriteString(fileHandle, COPYRIGHT, 64);	
	FileWriteString(fileHandle, Symbol(), 12);
	FileWriteInteger(fileHandle, targetPeriod, LONG_VALUE);
	FileWriteInteger(fileHandle, 0, LONG_VALUE);//model
	FileWriteInteger(fileHandle, targetBars, LONG_VALUE);
	FileWriteInteger(fileHandle, targetFromDate, LONG_VALUE);
	FileWriteInteger(fileHandle, targetToDate, LONG_VALUE);
	FileWriteDouble(fileHandle, targetModelQuality, DOUBLE_VALUE);

	FileWriteInteger(fileHandle, 16457, LONG_VALUE);//??
	
   //---- общие параметры
   FileWriteString(fileHandle, targetBaseCurrency, 12);//??currency[12]			
	FileWriteInteger(fileHandle, MarketInfo(Symbol(), MODE_SPREAD), LONG_VALUE);
	FileWriteInteger(fileHandle, Digits, LONG_VALUE);	
	FileWriteDouble(fileHandle, Point, DOUBLE_VALUE);
	//FileWriteInteger(fileHandle, 1, LONG_VALUE);//lot_min ?? int or double
	FileWriteDouble(fileHandle, 0.1, DOUBLE_VALUE);//lot_min ?? int or double
	//FileWriteInteger(fileHandle, 10, LONG_VALUE);//lot max
	FileWriteDouble(fileHandle, 10.0, DOUBLE_VALUE);//lot max
	
	//FileWriteInteger(fileHandle, 1, LONG_VALUE);//lot step
	FileWriteDouble(fileHandle, 0.1, DOUBLE_VALUE);//lot step
	
	FileWriteInteger(fileHandle, MarketInfo(Symbol(), MODE_STOPLEVEL), LONG_VALUE);
	FileWriteInteger(fileHandle, 0, LONG_VALUE);//gtc_pendings
	
   //---- параметры расчёта профитов
	FileWriteDouble(fileHandle, 100000.0, DOUBLE_VALUE);//contract_size
	FileWriteDouble(fileHandle, 0.0, DOUBLE_VALUE);//tick_value; цена одного пункта
	FileWriteDouble(fileHandle, 0.0, DOUBLE_VALUE);//tick_size
	FileWriteInteger(fileHandle, 0, LONG_VALUE);//profit_mode { PROFIT_CALC_FOREX, PROFIT_CALC_CFD, PROFIT_CALC_FUTURES }
	
	
   //---- расчёты свопов
	FileWriteInteger(fileHandle, 1, LONG_VALUE);//swap_enable
	FileWriteInteger(fileHandle, 0, LONG_VALUE);//swap_type
	FileWriteDouble(fileHandle, -0.35, DOUBLE_VALUE);//swap_long
	FileWriteDouble(fileHandle, 0.15, DOUBLE_VALUE);//swap_short
	FileWriteInteger(fileHandle, 4, LONG_VALUE);//swap_rollover3days

	//---- расчёт маржи
	FileWriteInteger(fileHandle, 100);//leverage
	FileWriteInteger(fileHandle, 1);//free_margin_mode
	FileWriteInteger(fileHandle, 0);//margin_mode
	
	
	//FileWriteInteger(fileHandle, 30);//margin_stopout
	FileWriteDouble(fileHandle, 30.0, DOUBLE_VALUE);//margin_stopout
	
	FileWriteDouble(fileHandle, 0.0, DOUBLE_VALUE);//margin_initial		
	FileWriteDouble(fileHandle, 0.0, DOUBLE_VALUE);//margin_maintenance		
	FileWriteDouble(fileHandle, 0.0, DOUBLE_VALUE);//margin_hedged		
	FileWriteDouble(fileHandle, 2.0, DOUBLE_VALUE);//margin_divider
   FileWriteString(fileHandle, targetBaseCurrency, 12);//??margin_currency

   //---- расчёт комиссии
	FileWriteDouble(fileHandle, 0.0, DOUBLE_VALUE);//comm_base
	FileWriteInteger(fileHandle, 0);//comm_type
	//FileWriteInteger(fileHandle, 0);//comm_lots
	FileWriteDouble(fileHandle, 0.0);//comm_lots
		
   //----
	FileWriteInteger(fileHandle, targetBars - 2);//from_bar номер бара fromdate
	FileWriteInteger(fileHandle, 0);//to_bar номер бара todate
	int startPeriod[6];
	FileWriteArray(fileHandle, startPeriod, 0, 6);//start_period[6]	
	   
   //----
   int reserved[64];
   FileWriteArray(fileHandle, reserved, 0, 64);//reserved[64]
}

double barLow = 0.0, barHigh = 0.0, barOpen = 0.0, barClose = 0.0, barVolume = 0.0;
int barTime, barWorkTime;
 
void writeTestHistory() {
	int targetSeconds = targetPeriod*60;
	int barTimeStep = (60*Period())/4 - 1;
	
	Print("barTimeStep=" + barTimeStep);
	
	barTime = targetFromDate;
	for (int i = firstBarOffset; i >= lastBarOffset; i--) {
		int time0 = Time[i];
		double low0 = Low[i];
		double high0 = High[i];
		double open0 = Open[i];
		double close0 = Close[i];
		double volume0 = Volume[i];
		double median0 = NormalizeDouble((high0 + low0)/2, Digits);
			
		if (time0 >= barTime + targetSeconds || i == firstBarOffset) {// || i == lastBarOffset
			barTime = (time0/targetSeconds)*targetSeconds;
			barLow = 1000000.0;
			barHigh = 0.0;
			barOpen = open0;
			barClose = 0.0;
			barVolume = 0.0;
		}
		
		barWorkTime = time0;
		double tickVolume = MathMax(volume0/5, 1.0);

		double peak1 = high0, peak2 = low0;		
		if (close0 > open0 || (MathAbs(close0 - open0) < Point && i % 2 == 0)) {//up bar
			peak1 = low0;
			peak2 = high0;		
		}
		
		writeTick(barTimeStep, open0, tickVolume, 1);
		
		if (!SkipEqualQuotes || !(MathAbs(open0 - peak1) < Point))//skip peak1==open0 
			writeTick(barTimeStep, peak1, tickVolume, 1);
			
		writeTick(barTimeStep, median0, tickVolume, 1);
		writeTick(barTimeStep, peak2, tickVolume, 1);
		
		if (!SkipEqualQuotes || !(MathAbs(close0 - peak2) < Point))//skip close0==peak2
			writeTick(barTimeStep, close0, tickVolume, 1);
	}
}

void writeTick(int time, double price, double volume, int flag) {
	if (false && barTime < D'2005.07.25 02:00') {
		Print(StringConcatenate("tick: ", timeStr(barWorkTime), "  ", quoteStr(price), 
		", bar: ", timeStr(barTime), " O=", quoteStr(barOpen), " L=", quoteStr(barLow),
		" H=", quoteStr(barHigh), " C=", quoteStr(barClose)));
	}

	barClose = price;
	barVolume += volume;
	
	if (price < barLow)
		barLow = price;
		
	if (price > barHigh)
		barHigh =  price;


	//52 байта в файле - 52 здесь
	FileWriteInteger(fileHandle, barTime);//otm
	FileWriteDouble(fileHandle, barOpen, DOUBLE_VALUE);//open
	FileWriteDouble(fileHandle, barLow, DOUBLE_VALUE);//low
	FileWriteDouble(fileHandle, barHigh, DOUBLE_VALUE);//high
	FileWriteDouble(fileHandle, barClose, DOUBLE_VALUE);//close
	FileWriteDouble(fileHandle, barVolume, DOUBLE_VALUE);//volume
	FileWriteInteger(fileHandle, barWorkTime);//ctm
	FileWriteInteger(fileHandle, flag);//flag
	
	barWorkTime += time;
}

string quoteStr(double quote) {
    return (DoubleToStr(NormalizeDouble(quote, Digits), Digits));
}

string timeStr(int time) {
	return (TimeToStr(time, TIME_DATE | TIME_SECONDS));
}
 
Также в коде можно заметить, что
сurrency[12]
margin_currency[12]// валюта маржевых требований

определяются неуниверсально, но как их получить универсально через MT4 я не знаю.
Причина обращения: