d9e5a92d

Методология тестирования

Для тестирования методов сезонных входов использованы данные с 1 августа 1985 г. по 31 декабря 1994 г. (оптимизационная выборка) и с 1 января 1995 г. по 1 февраля 1999 г. (период вне пределов выборки). Для исследования сезонности выборка размером около 10 лет является недостаточно протяженной. При обсуждении сезонных сигналов упоминалась практика расчета сезонного импульса (или же среднего поведения цен) на основе данных за предыдущие годы. Ввиду небольшого размера выборки расчеты будут основываться не только на прошедших, но и на будущих годах. Для этого используется специальная методика — так называемый подход складного ножа.
Метод перебирает целевые даты, перемещаясь вдоль временного ряда.
Если при усреднении сезонных эффектов использовать только соответствующие календарные даты прошлых лет, то для точек, приходящихся на начало выборки, таких данных очень мало или вообще нет. Поскольку для получения приемлемого сезонного среднего требуется не менее 6 лет, то для большей части периода выборки (всего 10 лет) расчет будет невозможен. Следовательно, данных для оптимизации важных параметров или анализа эффективности работы модели в пределах выборки явно недостаточно. Хорошо известный статистический метод складного ножа помогает решить проблему с недостатком данных.
Представьте, что рассчитывается сезонное поведение начиная с 1 июня 1987 г. Если использовать только данные из пределов выборки, то пришлось бы ограничиться данными за 1986 и 1985 гг. При использовании метода складного ножа в расчет можно включать даты не только из прошлого, но и из относительного будущего, т.е. с 1988 г. по 1994 г. Если год, для которого значение целевой даты рассчитывается (1987), удалить из пределов выборки, то сезонное поведение можно будет рассчитывать на основе 9 лет данных, а этого вполне достаточно. Подобная процедура оправданна, поскольку данные, исследуемые для получения прогноза, не зависят от прогнозируемых данных. Данные, используемые для получения прогнозов, отстоят от целевой даты не менее чем на год — следовательно, они не загрязнены текущим состоянием рынка. Этот метод позволяет значительно увеличить размер выборки, не снижая количество степеней свободы.
Для оценки влияния сезонных факторов вне пределов выборки были использованы все прошедшие годы. Например, для получения данных, соответствующих 14 января 1999 г., был использован метод всех прошедших лет: в анализ были включены данные с 1998 г. по 1985 г. Таким образом, ни один из расчетов вне пределов выборки не основывается на данных из будущего или настоящего времени.
Все тесты, следующие ниже, проведены с использованием сезонных входов на основе разнообразного портфеля рынков. Использованы стандартные выходы, как и в других исследованиях моделей в этой книге.
Позиции закрываются при срабатывании стандартного выхода или при получении сигнала на вход в противоположном направлении. Использована стандартная платформа тестирования. Ниже приведен код для тестирования сезонной торговли.


void SeasonalAvg (float *a, float *v, float *dt, int mode,
int m, int n) {
// Подсчитывает сезонное среднее для каждой календарной даты,
// основанное на предыдущих и (в некоторых случаях) на последующих
// годах. Работает на всех сериях данных.
// а — вне: значений [1..n] сезонных средних
// v — в: оригинальных сериях данных [1..n]
// dt — в: сериях [1..n] соответствующих дат
// mode — в: методе анализа:
// 1 = складной нож в пределах выборки, все


последние годы вне выборки
// 2 = фиксированный период анализа, выраженный в
// годах
// m — в: дата (режим = 1) или период анализа (режим = 2)
// n — в: число дней во всех рядах данных
static int i, j, cnt;
static unsigned long k;
static float sum, sdate;
if(mode == 1) { // режим складного ножа
for(i = 1; i <= n; i++) { // для каждой текущей даты
sum = 0.0; cnt = 0;
for{j = 1; j < 100; j++) { // двигаемся назад к
sdate = f(int)dt[i] - 10000 * j); // исходной дате
if (sdate < dt[3]) break; // переход к началу
k = max(0, (int){i- 260.893*j)); // приблизительный индекс
hunt(dt, n, sdate, &k) ; // находим точный индекс
if(sdate > dt[k]) k++;
if(sdate = dt[k]) continue;
cnt++; sum += v[k]; // накапливаем среднее
)
for(j = 1; j < 100; j++) { // двигаемся вперед
sdate = ((int)dt[i] + 10000 * j); //к исходной дате
if(sdate > m) break; // избегаем данных вне выборки
k = min(n, (int)(i+260.893*j); // приблизительный индекс
hunt(dt, n, sdate, &k); // находим точный индекс
if(sdate > dt[k]) k++;
if(sdate = dt[k]) continue;
cnt++; sum += v[k]; // накапливаем среднюю
}
a[i] = sum / (cnt + l.OE- 20); // заканчиваем среднюю
} // следующая текущая дата
}
else if(mode == 2) { // режим фиксированного периода
// анализа
for {i = 1; i <= n; i++) { // для каждой текущей даты
sum = 0.0; cnt = 0;
for(j = 1; j < 100; j++) ( // идем вперед
if(cnt >= m) break; // достаточность лет для теста
sdate = ((int)dt[i] - 10000 * j); // исходная дата
if (sdate < dt[3]) break; // идем к началу
k = max(0, (int)(i- 260.893*j)); // приблизительный индекс
hunt(dt, n, sdate, &k) ; // находим точный индекс
if(sdate > dt[k]) k++;
if(sdate = dt[k]) continue;
cnt++; sum += v[k]; // накапливаем среднюю
}
for(j = 1; j < 100; j++) ( // идем вперед
if (cnt >= m) break; // достаточность лет для теста
sdate = ((int)dt[i] + 10000 * j); // исходная дата
k = min(n, (int)(i+26Q.893*j)); // приблизительный индекс
hunt(dt, n, sdate, &k); // находим точный индекс
if(sdate > dt[k]) k++;
if(sdate = dt[k]) continue;
cnt++; sum += v[k]; // накапливаем среднюю
}
a[i] = sum / cnt; // заканчиваем среднюю
} // следующая текущая дата
}
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 = x12mod01.c
// parms — набор [1..MAXPRM] параметров
// dt - набор [l..nb] дат в формате ГГММДД
// орn — набор [1..nb] цен открытия
// hi - набор [l..nb] максимальных цен
// 1о — набор [1..nb] минимальных цен
// cls - набор [l..nb] цен закрытия
// vol — набор [1..nb] значений объема
// oi — набор [1..nb] значений открытого интереса
// dlrv — набор [1..nb] средних долларовой волатильности
// nb - количество торговых дней в наборе данных
// ts — ссылка на класс торгового симулятора
// eqcls — набор [1..nb] уровней капитала при закрытых позициях
// объявляем локальные переменные
static int rc, cb, neontracts, maxhold, ordertype, signal;
static int avglen, disp, k, modeltype, rnatype;
static float mmstp, ptlim, stpprice, limprice, tmp, thresh;
static float exitatr[MAXBAR+1];
static float savg[MAXBAR+1] , pchg[MAXBAR+1] , stoch[MAXBAR+1] ;
static float ma1[MAXBAR+1] , ma2 [MAXBAR+1] ;
// копируем параметры в локальные функции для удобного обращения
avglen = parms[1]; // длина скользящей средней
disp - parms[2]; // фактор смещения
thresh = parms[3]; // пороги для импульсных моделей
matype = parms[7]; // тип скользящей:
// 1=простое скользящее среднее
// 2- экспоненциальное
// 3=треугольное с передним взвешиванием
// 4- треугольное
// 5=простое центрованное
// 6 =экспоненциальное центрированное
// 7 =треугольное центрированное
modeltype = parms[8]; // тип модели:
// 1- импульс
// 2- пересечение
// 3=пересечение с подтверждением
// 4=пересечение с подтверждением и инверсией
ordertype = parms[9]; // вход: 1- на открытии, 2- по лимитному приказу,
// 3 - по стоп - приказу
maxhold = 10; // период максимального удержания позиции
рt1irn = 4 ; // целевая прибыль в единицах волатильности
mmstp = 1; // защитная остановка в единицах волатильности
// выполняем вычисления для всех данных, используя процедуры быстрой
// обработки массивов
AvgTrueRangeS(exitatr,hi,lo, cls, 50, nb) ; // средний истинный диапазон для
// выхода
pchg[l] = 0.0;
for(cb =2; cb <= nb; cb++) {
tmp = cls[cb] - cls[cb- l]; // изменение цены
tmp = tmp / exitatr[cb]; // нормирование
pchg[cb] = clip(tmp, - 2.0,2.0); // клиппинг
}
switch(modeltype) {
case 1 // данные для импульсной модели
SeasonalAvg(savg,pchg,dt,1,OOS_DATE,nb); // сезонности
MovAvg{savg,savg,matype,avglen,nb); // сглаживание
// скользящей
for(cb = 1; cb <= nb; cb++)
rna2 [cb] = fabs (savg [cb] } ;
MovAvg(mal, ma2, 1, 100, nb}; // среднее отклонение
break;
case 2: case 3: case 4: // данные для модели пересечения
SeasonalAvg(savg,pchg,dt,1,OOS_DATE,nb); // сезонности
for(cb =2; cb <= nb; cb++)
savg [cb] = savg[cb- l] ; // объединение
MovAvg{mal,savg,matype,avglen,nb); // сглаживание среднего
MovAvg(ma2,rnal,matype,avglen,nb) ; // пересечение средней
if(modeltype ==3 modeltype == 4) // стохастический
// осциллятор
StochOsc(stoch,hi,lo,cls,1,9 , nb) ; // 9- дневный Быстрый %К;
break;
default: nrerror{"TRAPSMOD: invalid modeltype");
}



// проходим через торговые дни, чтобы смоделировать реальную торговлю
for(cb = 1; cb <= nb; cb++) {
// не открываем позиций в периоде подсчета
// ... то же самое, что установка MaxBarsBack в TradeStation
if(dt[cb] < IS_DATE) { eqcls[cb] = 0.0; continue; }
// выполняем ожидающие приказы и считаем кумулятивный капитал
rc = ts.update (opn [cb] , hi [cb] , lo [cb] , cls [cb] , cb) ;
if(rc = 0) nrerror("Trade buffer overflow");
eqcls [cb] = ts.currentequity(EQ_CLOSETOTAL) ;
// не входим в сделки в последние 30 дней внутри выборки
// оставляем место в массивах для будущих сезонностей
if(cb > nb- 30) continue;
// считаем количество контрактов для позиции
// ... мы хотим торговать эквивалентом долларовой волатильности
// ... двух новых контрактов на S&P- 500 от 12/31/98
neontracts = RoundToInteger{5673 . О / dlrv[cb]) ;
if(ncontracts < 1) ncontracts = 1;
// избегаем устанавливать приказы на дни с ограниченной торговлей
if(hi[cb+l] == lo[cb+l]) continue;
// генерировать входные сигналы, цены стоп- и лимитных приказов
// для всех моделей сезонного входа
signal = 0;
switch{modeltype) {
case 1: // основная модель входа на основе порогов импульса
k = cb + disp;
tmp = thresh * mal[k];
if(savg[k] > tmp && savg [k- 1] <= tmp)
signal = 1;
else if (savg [k] < - tmp && savg[k- 1] >= - tmp)
signal = - 1;
break;
case 2: // основная модель входа на пересечении
k = cb + disp;
if(CrossesAbove(mal, ma2, k)) signal = 1;
else if{CrossesBelow(mal, ma2, k)} signal = - 1;
break;
case 3: // пересечение с подтверждением
k = cb + disp;
if(CrossesAbove(mal, ma2, k)) {
if(stoch[cb] < 25.0) signal = 1;
}
else if(CrossesBelow(mal, ma2, k)) (
if(stoch[cb] > 75.0) signal = - 1;
}
break;
case 4: // пересечение с подтверждением и инверсией
k = cb + disp;
if(CrossesAbove(mal, ma2, k)) (
if(stoch[cb] < 25.0) signal = 1;
else if(stoch[cb] > 75.0) signal = - 1;
}


else if(CrossesBelow(mal, ma2, k)) {
if(stoch[cb] > 75.0) signal = - 1;
else if(stoch[cb] < 25.0) signal = 1;
)
break;
default: nrerror("TRAPSMOD: invalid modeltype");
}
limprice = 0.5 * (hi[cb] + lo[cb]);
stpprice = cls[cb] + 0.5 * signal * exitatr[cb] ;
// входим в сделку, используя определенный тип приказа
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('X', ptlim*tmp, mmstp*tmp, maxhold);
} // обрабатываем следующий день
)

Определив локальные переменные и векторы, первый блок программы копирует различные параметры в соответствующие переменные для более удобного и понятного обращения к ним. Параметры описаны в ссылках, размещенных в коде.
Следующий блок проводит все расчеты на полной серии данных. Средний истинный интервал для 50 дней рассчитывается и сохраняется в векторе (exitatr). Впоследствии он будет использоваться для размещения защитных остановок управления капиталом и целевых уровней прибыли в стандартизованной стратегии выхода. Средний истинный интервал в этом векторе (или в ряду данных) также используется для нормализации возникающих в ходе работы программы изменений цен.
После вычисления среднего истинного интервала рассчитываются нормализованные и обрезанные изменения цен. Каждая точка в ряду данных pchg отражает изменение цены между ценами закрытия текущего и предшествующего дней. Изменения цены затем нормализуются путем деления их на средний истинный интервал и обрезаются для снижения влияния экстремальных перепадов цены (статистических выбросов). Нормализация необходима, поскольку волатильность рынков меняется со временем иногда очень сильно. Например, сейчас индекс S&P 500 в 5 и более раз дороже, чем 15 лет назад. Очевидно, что и средняя дневная волатильность изменилась соответствующим образом. Если бы изменения цены не подвергались нормализации и не представлялись в единицах текущей волатильности, сравнение сезонных явлений за разные годы было бы искаженным. Годы, когда волатильность была выше, давали бы больший вклад, чем годы с низкой волатильностью. В случае S&P 500 последние годы полностью доминировали бы при проведении усреднения, а при нормализованном представлении каждый год вносит почти одинаковый вклад. Срезание выбросов проводится на уровне — 2 и + 2 средних истинных интервала, чтобы удалять случайные и аномальные значения, не искажая общую оценку.

Опция выбора modeltype определяет, какие операции проводятся далее. Значение 1 выбирает основную импульсную модель. Сезонные показатели рассчитываются для обрезанных и нормализованных изменений цен, причем в пределах выборки используется метод складного ножа, а вне пределов выборки — метод всех прошедших лет. Эти операции обеспечиваются вызовом функции SeasonalAvg. Временной ряд сезонных показателей затем сглаживается скользящим средним (вид среднего устанавливается параметром matype, а длина— параметром avglen). Затем рассчитывается временной ряд средних абсолютных отклонений сезонных импульсов. Этот ряд представляет собой простое скользящее среднее с периодом 100 дней от ряда абсолютных значений сезонных импульсов, которое затем используется в дальнейших расчетах уровней порогов. Значения modeltype 2, 3 и 4 представляют собой вариации моделей, основанных на пересечении. Сезонные показатели рассчитываются, и показатель изменения цены для каждого дня интегрируется (вычисляется бегущая сумма), в результате образуется новый ряд, ведущий себя подобно ценовому ряду. Эта синтезированная серия отображает движение цен на основе типичного поведения рынка в предшествующие и, возможно, в будущие годы. Затем рассчитываются два скользящих средних: та! (скользящее среднее интегрированного сезонного ряда типа matype с периодом avglen) и та2 (сигнальная линия для определения момента пересечения, представляет собой скользящее среднее ma1 с теми же параметрами matype и avglen). Если же выбран modeltype 3 или 4, то проводятся дополнительные расчеты для моделей с подтверждением и/или инверсией; в данном случае рассчитывается значение Быстрого %К с периодом 9 дней, которое затем сохраняется в векторе stoch.
Следующий блок кода включает цикл, последовательно перебирающий все торговые дни в ряду данных, — такой же цикл, как и во всех предыдущих главах, посвященных стратегиям входа. Первые его строки обеспечивают обновление симулятора, рассчитывают количество контрактов в сделке и пропускают дни с ограниченной торговлей. Следующие строки генерируют сигналы входа для моделей, основанных на сезонных факторах. В зависимости от значения параметра modeltype используется один из четырех подходов.
Modeltype 1 представляет базовую модель, основанную на пороге ценового импульса. Порог рассчитывается как произведение множителя, определяющего относительную величину порога (thresh) на среднее абсолютное отклонение сезонного импульса за прошлые 100 дней. Сигнал к покупке генерируется, если сумма сезонного импульса (savg) и параметра смещения (disp) поднимается выше уровня порога. Если данная сумма опускается ниже величины, равной значению порога со знаком минус, подается сигнал на продажу. Иными словами, если для данного дня плюс- минус несколько дней (disp) предсказывается достаточно сильный сезонный импульс цен, то торговля ведется в направлении ожидаемого движения.
Modeltype 2 представляет базовую модель пересечения и использует скользящие средние интегрированных сезонных показателей текущего дня плюс фактор смещения. Если первое скользящее среднее поднимается выше второго, генерируется сигнал к покупке. В противоположном случае генерируется сигнал к продаже. Фактор смещения позволяет модели искать моменты пересечения, которые произойдут в будущем через несколько дней. Таким образом, преодолевается запаздывание, свойственное скользящим средним. Поскольку сезонные средние основываются на исторических данных, отстоящих от текущей даты не менее чем на один год, вполне приемлемо прогнозировать на несколько дней вперед.
Modeltype 3 представляет собой ту же модель на основе пересечения, но с добавлением подтверждения. Подтверждение обеспечивается проверкой стохастического осциллятора ценового ряда, определяющей, совпадает ли его динамика с ожидаемым поведением на основе сезонных факторов.
Modeltype 4 использует модель, основанную на пересечении с добавлением подтверждения и инверсии. При использовании modeltype 4 сигнал к покупке подается, если первое скользящее среднее пересекает второе снизу вверх. При этом значение стохастического осциллятора должно быть не менее 25. Если же при верхнем пересечении стохастический показатель превышает уровень 75, то модель подает сигнал к продаже исходя из предположения, что произошла инверсия. Если первое скользящее среднее ниже второго, и нормальная сезонная модель подтверждается значением стохастического осциллятора, превышающим уровень 75, генерируется сигнал к продаже. Если в этом случае показатель составит менее 25, предполагается инверсия и отдается сигнал к покупке.
В свою очередь цена лимитного приказа (limprice) устанавливается на уровне середины ценового диапазона текущего дня. Цена входного стопприказа (stpprice) устанавливается на уровне закрытия текущего дня плюс (для покупки) или минус (для продажи) половина среднего истинного диапазона последних 50 дней. Остальные блоки кода идентичны приводившимся в предыдущих главах: они обеспечивают размещение приказов указанного вида (ordertype) и стандартные выходы.


Содержание раздела