Было бы не плохо, если бы вы привели пример заполнения TestHistory для различных методов. Например тестируемый период H1 и мы эмулируем два H1 бара 2005.07.13 00:00 и 2005.07.13 01:00. Для первого метода моделирования у нас будет две TestHistory, для второго четыре, для третьего .... и соответственно значения этих TestHistory для каждого метода моделирования.
Для чего используется flag?
Если нет, подскажите можно ли средствами MQL4 сформировать данный файл? Прежде всего меня интерсует совпадает ли байтовое представление double и int, записываемых через FileWriteDouble(), FileWriteInteger() и т.п. и байтовое представление этих данных, используемое оригинальным генератором.
Как записать, например, char margin_currency[12]? Т.е. масив символов заданной длины?
Как рассчитывать double modelquality?
Правильно ли я понял, что в TestHistory на int flag принимающий значения {0,1} расходуется 4 байта? Когда у этого флага должно быть значение 0?
можно
Прежде всего меня интерсует совпадает ли байтовое представление 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 будут представлены только конечными состояниями баров без промежуточных тиков.
без проблем можно использовать даты, главное, не нажимайте галочку "пересчитать"
Делись успехами :)
иначе не понятно или моделирование "хромает", или тестер "врет" что скорее всего, т.к. смоделированные бары (те что на графике вроде совпадают) хотел сравнить с реальными , но не знаю как сделать экспорт смоделированных баров в ексцел, а котировки записанные экспертом (что ему подаются на вход) в режиме тестирования отличаются от реальных даже на контрольных точках, про внутри я пока молчу
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 я не знаю.

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