МЕТОДИКА ТЕСТИРОВАНИЯ
Все приведенные ниже тесты были осуществлены с помощью осцилляторных моделей на портфеле разнообразных ценных бумаг. Можно ли получать прибыльные сделки с осцилляторными моделями? Как они работают во времени — ухудшается или улучшается их функционирование за последние годы? Целью нашего тестирования был ответ на эти вопросы.
Во всех тестах использовался стандартный выход, описанный в предыдущих главах. Правила входов будут рассмотрены параллельно с кодом модели и отдельными тестами. Сделки закрывались при поступлении приказа на вход в обратном направлении или при исполнении стандартного выхода. Платформа тестирования тоже была стандартной.
За последние годы мы закодировали на языке C++ ряд осцилляторов, описанных в Technical Analysis of Stocks and Commodities и в ряде других источников. В этой главе мы сравниваем работу версий C++ осцилляторов MACD, RSI и стохастического с их версиями в TradeStation.
В большинстве случаев результаты идеально совпадали, но в одном случае, а именно для Медленного %К, результат разительно отличался. Разбор кода показал, что TradeStation рассчитывает Медленный %К как экспоненциальное скользящее среднее от Быстрого %К. В нашем же коде отдельно рассчитываются простые скользящие средние с периодом 3 дня для числителя и знаменателя формулы Быстрого %К. Согласно уравнениям, приведенным Мейбахом (Meibahr, 1992) и другими источниками, правильной является наша версия на C++. Если читатели захотят повторить наши расчеты в TradeStation и обнаружат расхождения, мы настоятельно рекомендуем проверить функции индикаторов TradeStation.
Кроме того, при попытке закодировать правильную версию Медленного %К для TradeStation на EasyLanguage мы неожиданно обнаружили, что TradeStation без предупреждения может выдать неверные результаты, если одна пользовательская функция вызывает другую. Когда мы изменили код так, чтобы рассчитывалась промежуточная переменная (чтобы избежать совместных вызовов), были получены правильные результаты. В этих тестах использовалась версия TradeStation 4.02 от 29 июля 1996 г.
Нижеследующий код включает большинство использованных в тестах моделей входов на основе осцилляторов. Реальный расчет осцилляторов достигается путем вызова внешних функций.
static void Model (float *parms, float *dt, float *opn, float *hi,
float *lo, float *cls, float *vol, float *oi, float *dlrv, int nb,
TRDSIM &ts, float *eqcls) (
// Выполнение тестирования моделей на осцилляторах
// File = xllmodOl.c
// parms - набор [1.. MAXPRM] параметров
// dt - набор [l..nb] дат в формате ГГММДД
// орn - набор [l..nb] цен открытия
// hi — набор [l..nb] максимальных цен
// 1о - набор [1..пЬ] минимальных цен
// cls — набор [l..nb] цен закрытия
// vol - набор [l..nb] значений обьема
// oi — набор [l..nb] значений открытого интереса
// dlrv — набор [l..nb] средних долларовой волатильности
// nb — количество дней в наборе данных
// ts — ссылка на класс торгового симулятора
// eqcls — набор [l..nb] уровней капитала при закрытых позициях
// объявляем локальные переменные
static int rc, cb, ncontracts, maxhold, len1, len2, len3;
static int modeltype, ordertype, osctype, signal, i, j, k;
static float mmstp, ptlim, stpprice, limprice, tmp;
static float exitatr[MAXBAR+1];
static float sigline[MAXBAR+1], oscline[MAXBAR+1];
static float upperband[MAXBAR+1] , lowerband [MAXBAR+1] ;
// копируем параметры в локальные переменные для удобного обращения
lenl = parms[l]; // более короткий для первого параметра длины
1еn2 = parms[2] ; // более длинный для второго параметра длины
1епЗ = parms[3]; // длина теста дивергенции
modeltype = parms[7]; // тип осцилляторной модели входа
osctype - parms[8]; // тип осциллятора
ordertype = parms[9]; // тип входного приказа
maxhold =10; // период максимального удержания позиции
ptlim =4; // целевая прибыль в единицах волатильности
mmstp =1; // защитная остановка в единицах волатильности
// пропускаем неправильные комбинации параметров,
if ( (osctype==4 && len1>=len2) ) (
set_vector(eqcls, 1, nb, 0.0);
return;
}
// выполняем вычисления для всех данных, используя процедуры быстрой обработки
// массивов
AvgTrueRangeS{exitatr,hi,lo,cls,50,nb); // средний истинный диапазон для выхода
switch(osctype} { // выбираем осциллятор
case 1: // классические быстрые стохастики
StochOsc(oscline,hi,lo,cls,1,len1,nb); //Быстрый %К
MovAvg(sigline, oscline, 1, 3, nb); //Быстрый %D
set_vector(upperband, 1, nb, 80.0); //верхняя граница
set_vector(lowerband, 1, nb, 20.0); //пробой нижней границы
break;
case 2: // классические медленные стохастики
StochOsc(oscline,hi,lo,cls,2,lenl,nb); //Медленный %К
MovAvg(sigline, oscline, 1, 3, nb); //Медленный %D
set_vector(upperband, 1, nb, 80.0); //верхняя граница
set_vector(lowerband, 1, nb, 20.0); //пробой нижней границы
break;
case 3: // классический RSI
RsiOsc(oscline, cls, 1, lenl, nb); //RSI
MovAvgtsigline, oscline, 1, 3, nb) ; //3-дневное ПСС
set_vector(upperband, 1, nb, 70.0); //верхняя граница
set_vector(lowerband, 1, nb, 30.0); //пробой нижней границы
break;
case 4 : // классический MACD
MacdOsc(oscline,cls,1,lenl,len2,nb); //классический MACD
MovAvg(sigline, oscline, 2, 9, nb) ; //9-дневное ЭСС
for{i=l; i<=nb; i++}
lowerband=1.5*fabs(oscline ) ; //пороги
MovAvg(upperband,lowerband,1,120,nb); //как долгосрочная
for{i=l; i<=nb; i++} //отклонение от среднего
lowerband= -upperband; //полосы
break;
default: nrerror("Invalid moving average selected");
};
// проходим через штрихи (дни), чтобы смоделировать реальную торговлю
for{cb = 1; cb <= nb; cb++} {
// не открываем позиций до начала периода выборки
// ... то же самое, что и установка MaxBarsBack в TradeStation
166 ЧАСТЬ II ИССЛЕДОВАНИЕ входов в РЫНОК
if(dt[cb] < IS_DATE) { egcls[cb] = 0.0; continue; )
// выполняем ожидающие приказы и считаем накопленный капитал
rс = ts.update{opn[cb], hi[cb], lo[cb], cls[cb], cb);
if(rc = 0) nrerror("Trade buffer overflow"};
eqcls[cb] = ts.currentequity(EQ_CLOSETOTAL);
// считаем количество контрактов для позиции
//... мы хотим торговать эквивалентом долларовой волатильности
//... 2 новых контрактов S&P-500 от 12/31/98
ncontracts = RoundToInteger(5673.0 / dlrvfcbj);
if(ncontracts < 1) ncontracts = 1;
// избегаем устанавливать приказы на дни с ограниченной торговлей
if(hi[cb+l] == lo[cb+l]) continue;
// генерируем входные сигналы, цены стоп- и лимитных приказов,
// используя определенную модель входа на осцилляторах
#define CrossesAbove (a,b,c) (а[с]>=b[с] && а[с-1]<b[с-1])
#define CrossesBelow(a,b, с) (a[c]<b[c] && а [с-1] >=b [с-1] )
#define TurnsUp(a,c) (а[с]>=а[с-1] && а[с-1]<а[с-2])
#define TurnsDn(a,c) (a[c]<a[c-l] && а [с-1] >=а [с-2] )
signal=0;
switch (modeltype) {
case 1: // модель перекупленности-перепроданности
if(CrossesAbove(oscline, lowerband, cb)}
signal = 1;
else if(CrossesBelow(oscline, upperband, cb))
signal = -1;
limprice = 0.5 * (hi [cb] + lo [cb] ) ;
stpprice = cls[cb] +0.5 * signal * exitatr[cb];
break;
case 2: // модель сигнальной линии
if(CrossesAbove(oscline, sigline, cb))
signal = 1;
else if(CrossesBelow(oscline, sigline, cb))
signal = -1;
limprice = 0.5 * (hi [cb] + lo[cb]) ;
stpprice = cls[cb] + 0.5 * signal * exitatr[cb];
break;
case 3: // модель дивергенции
i = LowestBar(cls, len3, cb) ;
j = LowestBar(oscline, len3, cb);
if(i < cb && i > cb-6 && j > cb-len3+l && i-j > 4
&& TurnsUp(oscline, cb)) signal = 1;
else {
i = HighestBar(cls, len3, cb} ;
j = HighestBar(oscline, len3, cb);
if(i < cb && i > cb-6 && j > cb-len3+l && i-j > 4
&& TurnsDn(oscline, cb)) signal = -1;
)
limprice = 0.5 * (hi[cb] + lo[cb]);
stpprice = cls[cb] + 0.5 * signal * exitatr[cb];
break;
default: nrerror("Invalid model selected");
)
#undef CrossesAbove
#undef CrossesBelow
#undef TurnsUp
#undef TurnsDn
// входим в сделку, используя определенный тип приказа
if (ts.position() <= 0 && signal == 1) {
switch(ordertype) { // выбираем нужный вид приказа
case 1: ts.buyopen('1', ncontracts); break;
case 2: ts.buylimit('2', limprice, ncontracts); break;
case 3: ts.buystop('3', stpprice, ncontracts); break;
default: nrerror("Invalid buy order selected");
}
}
else if(ts.position1) >= 0 && signal == -1) {
switch(ordertype) { // выбираем желаемый тип приказа
case 1: ts.sellopen('4', ncontracts); break;
case 2: ts.selllimit('5', limprice, ncontracts); break;
case 3: ts.sellstop('6', stpprice, ncontracts); break;
default; nrerror("Invalid sell order selected");
}
}
// симулятор использует стандартную стратегию выхода
tmp = exitatr[cb];
ts.stdexitcls ('Х', ptlim*tmp, nmstp*tmp, maxhold) ;
} // обрабатываем следующий день
Логика кода весьма напоминает программу, использованную для тестирования скользящих средних. Сначала копируется ряд параметров в местные переменные для простоты ссылок и считывания дальнейшим кодом. Затем проверяется наличие непригодных сочетаний параметров, например для MACD (osctype = 4) длина короткого скользящего среднего должна быть меньше, чем длинного, иначе тест будет пропущен. В следующем крупном блоке osctype выбирает вид рассчитываемого осциллятора (1 — быстрый стохастический, 2 — медленный стохастический, 3 — классический RSI, 4 — классический MACD) . Осциллятор oscline затем рассчитывается в виде ряда данных или вектора, генерируются дополнительные кривые, связанные с ним, например сигнальная линия sigline или медленная версия осциллятора. Верхний (upperband) и нижний (lowerband) пороги либо рассчитываются, либо задаются. Для стохастического осциллятора используются стандартные пороги 80 и 20, для RSI — пороги на уровне 70 и 30. Хотя MACD как таковой не имеет порогов, пороги для него устанавливаются на уровне плюс-минус полтора стандартных отклонения от нуля. Затем начинается процесс перебора данных, день за днем. В цикле перебора данных представляют интерес два главных блока — первый генерирует сигналы покупки и продажи, а также цены для лимитного и стоп-приказов, используемых выбранной моделью. Параметр mode/type выбирает модель: 1 — модель перекупленности/перепроданности, 2 — модель сигнальной линии, 3 — модель на расхождении. При этом используется один из вышеперечисленных осцилляторов, выбранный параметром osctype. Последний блок производит вход в рынок согласно выбранному значению параметра ordertype: 1 — для входа по цене открытия, 2 — по лимитному приказу, 3 — по стоп-приказу. Затем симулятор использует стандартную модель выхода для закрытия сделок.
Точные логические основания для входа будут обсуждаться ниже в контексте индивидуальных тестов, что не требует от читателя понимания или обращения к коду.