Тестирование стратегии при помощи индикатора

 
Ниже привожу простейший тестер стратегии для МТ4. Если можно, выскажитесь, пожалуйста. Особенно насчет логики - нет ли в ней принципиальных ошибок.
При тестировании используется худшая возможная цена, что снимает вопрос "где брать внутренние цены бара".
Подразумевается, что каждый вписывает свои несколько строчек логики тестера, та стратегия, которую я использовал в этом примере, прибыльной не является - это всего лишь пример.
Индикатор генерирует файл (test.txt) с отчетом, пользуясь к-л программой (например, Эксель), можно построить график прибыли, также рассчитывается дродаун.

Заранее спасибо за критику :)

Кварк

#property copyright "Copyright Quark"
#property link      ""

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1 Aqua
#property indicator_color2 Red
#property indicator_minimum -1
#property indicator_maximum 1

string strReportFileName = "test.txt";

// indicator buffers
double arrBuyBuffer[];		// Buy indicator line
double arrSellBuffer[];		// Sell indicator line		

// For example, if we use Ma14 and Clv21, it should be at least 21.
// Here I use 30, which is guaranteed to be larger than delays,
// produced by indicators I use.
int nRemoveFirst = 30;

double dStopLoss = 150;	// points

// Stochastic indicator
double dStochMain;	
double dStochSignal;
double dStochMainPrev;
double dStochSignalPrev;

// Balance (total amount of money and securities)
double dBalance;

double dLotSize = 100;	// 0.1 lot
int nType = 0;			// -1 buy, 1 sell, 0 - none

// Open price of the last order. Important: this (simple) implementation
// does not allow more than one order in a time
double dOpenPrice;

// dStop = dOpenPrice +/- dStopLoss * Point; 
double dStop;

////////////////////////
int init()
{
	// When we buy, we draw a blue bar down, when we sell,
	// we draw a red bar up.
	SetIndexStyle(0, DRAW_HISTOGRAM);
	SetIndexShift(0, 0);

	SetIndexStyle(1, DRAW_HISTOGRAM);
	SetIndexShift(1, 0);
				
	IndicatorDigits(4);
		
	IndicatorShortName("Tester");

	// indicator buffers mapping
	SetIndexBuffer(0, arrBuyBuffer);
	SetIndexBuffer(1, arrSellBuffer);
	
	SetIndexDrawBegin(0, nRemoveFirst);
	SetIndexDrawBegin(1, nRemoveFirst);

	return(0);
}
///////////////////////////
int deinit()
{
}
///////////////////
int start()
{
	dBalance = 1000;		// Initial amount for testing 1000$

	// How many bars were processed
	int nExtCountedBars = IndicatorCounted();

	// It is not a "real" indicator, we only calculate it once, 
	// when all data are uncounted and have to be updated
	if(nExtCountedBars != 0)
		return(-1);

	FileDelete(strReportFileName);
	int hFile = FileOpen(strReportFileName, FILE_WRITE, ';');
	string str = "Number	Operation	Price		Closed	ClosePrice	Total";
	FileWrite(hFile, str);
	
	bool bStop;							// A position was closed by a stop loss
	double dMaxDrawDown = 0;			// Max. Drawdown
	double dCurrentMax = dBalance;		// Used to calculate drawdown

	int nTradeNumber = 0;
	for(int nBar = Bars - nRemoveFirst; nBar > 0; nBar--)
	{
		dStochMain = iStochastic(NULL,0,10,6,6,MODE_SMA,1, MODE_MAIN,nBar);
		dStochSignal = iStochastic(NULL,0,10,6,6,MODE_SMA,1, MODE_SIGNAL,nBar);
		dStochMainPrev = iStochastic(NULL,0,10,6,6,MODE_SMA,1, MODE_MAIN,nBar+1);
		dStochSignalPrev = iStochastic(NULL,0,10,6,6,MODE_SMA,1, MODE_SIGNAL,nBar+1);

		arrBuyBuffer[nBar] = 0;		// Set to "no buy, no sell"
		arrSellBuffer[nBar] = 0;

		if(nType != 0)		// If we have an open order
		{
			bStop = false;	
			double dClosedAt;
			if(nType == -1 && Low[nBar] <= dStop)	// If BUY and stop loss reached
			{
				// Close position at the worse possible price
				// 100 is a leverage.
				dBalance = dBalance + 100 * (Low[nBar] - dOpenPrice - 5 * Point) * dLotSize;
				bStop = true;
				dClosedAt = Low[nBar];
			}
			else if(nType == 1 && High[nBar] >= dStop) // If SELL and stop loss reached
			{
				dBalance = dBalance + 100 * (dOpenPrice - High[nBar] - 5 * Point) * dLotSize;
				bStop = true;
				dClosedAt = High[nBar];
			}

			if(bStop == true)
			{
				nType = 0;	// No more open positions
				
				str = str + "Stop	" + dClosedAt + "	" + dBalance;
				FileWrite(hFile, str);
			}
		}

		double dDrawDown = (dCurrentMax - dBalance) / dCurrentMax;
		
		dMaxDrawDown = MathMax(dMaxDrawDown, dDrawDown);
		dCurrentMax = MathMax(dCurrentMax, dBalance);

		// Logic of the trading system begins
		
		// If it is time to buy
		if(nType != -1 	// no long positions
			&& dStochMainPrev <= dStochSignalPrev && dStochMain >= dStochSignal
			&& dStochMainPrev < 5) 
		{
			if(nType == 1)	// If we have short - close it
			{
				dBalance = dBalance + 100 * (dOpenPrice - High[nBar] - 5 * Point) * dLotSize;

				str = str + "Close	" + High[nBar] + "	" + dBalance;
				FileWrite(hFile, str);
			}

			arrBuyBuffer[nBar] = -1;
			
			dOpenPrice = High[nBar];
			dStop = dOpenPrice - dStopLoss * Point;
			nType = -1;

			str = nTradeNumber + "	Buy		" + High[nBar] + "	";
			nTradeNumber++;
		}
		// Time to sell
		else if(nType != 1 	// no short positions
			&& dStochSignalPrev <= dStochMainPrev && dStochSignal >= dStochMain
			&& dStochMainPrev > 95)
		{
			if(nType == -1)
			{
				dBalance = dBalance + 100 * (Low[nBar] - dOpenPrice - 5 * Point) * dLotSize;
				
				str = str + "Close	" + Low[nBar] + "	" + dBalance;
				FileWrite(hFile, str);
			}				
				
			arrSellBuffer[nBar] = 1;
				
			dOpenPrice = Low[nBar];
			dStop = dOpenPrice + dStopLoss * Point;
			nType = 1;

			str = nTradeNumber + "	Sell		" + Low[nBar] + "	";
			nTradeNumber++;
		}
	}

	// If at the end we have open positions, close them
	if(nType == 1)
	{
		dBalance = dBalance + 100 * (dOpenPrice - High[1] - 5 * Point) * dLotSize;

		str = str + "Close	" + High[nBar] + "	" + dBalance;
		FileWrite(hFile, str);
	}
	else if(nType == -1)
	{
		dBalance = dBalance + 100 * (Low[1] - dOpenPrice - 5 * Point) * dLotSize;

		str = str + "Close	" + Low[nBar] + "	" + dBalance;
		FileWrite(hFile, str);
	}

	str = "Balance: " + dBalance + ", Max. Drawdown: " + dMaxDrawDown 
		+ ", Number of trades: " + nTradeNumber;
	FileWrite(hFile, str);
	
	Comment("Balance: ", dBalance);
	
	FileClose(hFile);

	return(0);
}
///////////////////


 
Quark, может проще сделать? В буферы/массивы индикатора записывать значения Equity, Balance и TotalProfit? Можно их дополнительно в файл сбрасывать, чтобы потом в Excel открывать.
А цены открытия Buy и Sell хранить в переменных (ну и стопы с тейк/профитами).
Вопрос разработчикам: в МТ3 размер маржи по открытию 1 лота по любой валютной паре при бэк-тестинге всегда равен 1000$. В МТ4 это будет считаться более правильно или так и останется?
 
Думал об этом. Неудобно. Представьте, что вам повезло, и с 1000 долларов вы поднялись до миллиона. Что вы разглядите на таком графике? Да ничего. Также, нет способа увидеть все сделки в одном окне - без скроллинга. МетаТрейдеровское индикаторное окошко не приспособленно для показа постоянно возрастающих величин :) Проще использовать другие графические пакеты.
 
В МТ4 это будет считаться более правильно или так и останется?

Будет считаться правильно, но с оговорками:
на кроссах или любых символах, требующих участия дополнительного курса конвертации, курс конвертации будет браться последний известный, а не будет моделироваться параллельно основному символу.
 
Повторно обращаюсь с просьбой - кто-нибудь, посмотрите, пожалуйста, на логику торгов в этом коде - правильно ли вычисляется прибыль? Просто на уровне - вдруг я где-то не там на 100 умножил, не те спрэды взял...
 
Тут не надо спред брать, так как Low идет по бидам
dBalance = dBalance + 100 * (Low[nBar] - dOpenPrice - 5 * Point) * dLotSize;



Здесь

		else if(nType == 1 && High[nBar] >= dStop) // If SELL and stop loss reached

тоже спред
надо

		else if(nType == 1 && High[nBar] >= dStop-5*Point) // If SELL and stop loss reached

тоже спред
И вообще, если стоплосс фиксированный в 150 пунктов, зачем вычислять размер лося, надо просто отнимать от Balance это количество пунктов.
Пока все

 
Переделаю. То есть, кроме этих, больше огрех - я имею в виду явных, в логике - нет? Приятно, конечно, но зная себя, как-то не верится :)

> И вообще, если стоплосс фиксированный в 150 пунктов, зачем вычислять размер лося, надо просто отнимать от Balance это количество пунктов.
Ну, это ведь общая стратегия. Может быть, кто-то будет динамически менять стоп лосс...

Спасибо за помощь.
С уважением,
Кварк
 
2 Quark
Я таким образом проверил парочку стратегий еще в "ранних" билдах. Идея рабочая в принципе. Советовать ничего не буду, просто поделюсь "наработанными" наблюдениями. На примере длинной позиции. Если условия срослись на каком-то баре, открываемся на следующем баре по цене Low+spread (=Ask). Закрываемся на баре, на котором срослись условия, по цене Low. Эти уровни запоминаются в двух переменных, разница между которыми даёт профит в пунктах. Деньги считать не надо и баланс тоже, в принципе не нужен (и связанные со всем этим проверки). В файле получается общий профит в пунктах +350 или -540, т.е. сразу всё становится ясно. В файл записывается ДатаВремя, тип (buy,sell), валюта, уровень открытия, уровень закрытия, профит в пунктах. Получается очень простой для анализа в Excel'e файл. И достаточно близко к реалиям. Одну стратегию я проверял неделю на демо, потом этот же кусок истории "прогнал" таким вот образом. Расхождения в 1-3 пункта по сделке. Да и тело программы-индикатора-тестера получается довольно простым и компактным. Есть только один момент, за которым надо последить. Поскольку тестер работает со статичной историей, он может открываться и закрываться лучше, чем на живом рынке - проскальзывание. Поэтому в индикаторе надо специально ухудшать условия. Т.е., например, добавить пару пунктов к уровню открытия, убрать пару пунктов от уровня закрытия.
 
Собственно, именно поэтому я (для длинной позиции) открываюсь по High и закрываюсь по Low - это гарантирует, что любое проскальзывание принято во внимание. Худший из возможных вариантов. Также, мне кажется, честнее и открываться и закрываться на следующем баре. Представьте, что условия выполнились, за счет быстрого движения цены, и в следующем баре она продолжает двигаться. Что касается стопов, то, по-моему, тут как раз можно закрывать на текущем баре, хотя я не уверен. Может быть "честнее" брать худшую цену из текущего (где выполнилось условие) и следующего за ним?
 
2 Quark 2 avm

 Также, мне кажется, честнее и открываться и закрываться на следующем баре. 


Если условия срослись на каком-то баре, открываемся на следующем баре по цене Low+spread (=Ask).
Эти уровни запоминаются в двух переменных, разница между которыми даёт профит в пунктах. 
Деньги считать не надо и баланс тоже, в принципе не нужен (и связанные со всем этим проверки). 
В файле получается общий профит в пунктах +350 или -540, т.е. сразу всё становится ясно. 



Проходили, знаем. Очень четко заметна разница: работаем по текущему бару - есть профит, например, 500pt. Пробуем работать по следующему бару - сразу почти такой же убыток, например, -500pt. Это к вопросу о важности момента принятия решения. А вот насчет проскальзывания - если работать по следующему бару хотя бы на часовике - проскальзывание вряд ли ощутишь - большинство сделок пройдут нормально. С другй стороны, часовой бар пунктов в 50 - не редкость и ждать его завершения для принятия решения не всегда правильно. Не грааль, в общем, будем искать...

Причина обращения: