d9e5a92d

Методология тестирования лунных моделей

Все тесты проводились с использованием входов по сигналам лунной модели для торговли портфелем различных финансовых инструментов.
Можно ли получить прибыль, используя лунную модель? Как результативность подобных моделей будет изменяться со временем? Как изменились их результаты за последние годы? Для того чтобы ответить на эти вопросы, и было проведено тестирование.
Применены стандартные выходы, правила входов будут рассмотрены при обсуждении отдельных тестов. Позиции закрываются при подаче сигнала на вход в противоположном направлении либо при срабатывании стандартного выхода. В приведенном ниже коде описана модель входа на основе лунных циклов.

int LunarEventDates (int n) {
// подсчитывает дату лунной фазы, начиная
// с января 1900.
// n - ввод: номер фазы луны
// 0,4,8... новолуния
// 1,5,9... луна в первом квартале
// 2,6,10... полнолуние
/ / 3,7,11... луна во втором квартале
//возвращает - вывод: дата события по юлианскому календарю
static long ndate;
static float timzon = - 5.0 / 24.0; // восточное стандартное время
static float fгас;
flmoon {n >> 2, n & 3, &ndate, &frac);
frac = 24.0 * {frac + timzon);
if(fгас < 0.0) { // корректировка времени


ndate—;
frac += 24.0;
)
if(frac > 12.0) (ndate++; frac - = 12.0;)
else frac += 12.0;
return ndate; // юлианская дата события
}
int LunarEquivDate (int date, int n) (
// рассчитываем дату предыдущего n- го (n < 0) или
// будущего (n > 0) случая фазы луны, равной
// сегодняшней фазе
// date - ввод: текущая дата в формате ГГГММДД
// n - ввод: лунные циклы назад{- ) или вперед (+)
// return - вывод: дату предыдущего или будущего цикла в формате ГГГММДД
static long nstar, ndatel, ndate2, curdate, ntarg, nans;
static int mm, dd, yyyy;
curdate = julday((date/100)%100, date%10O, 1900+date/lOOOO);
while(curdate >= ndate2) {ndatel = LunarEventDates(++nstar); ndate2 = LunarEventDates(nstar + 1) ;}
while(curdate < ndatel) {ndatel - LunarEventDates{—nstar); ndate2 = LunarEventDates(nstar + 1);}
if(curdate < ndatel curdate >= ndate2
abs(ndate2 - ndatel - 7) > 2)
nrerror("LunarEquivDate: calculation error");
nans = LunarEventDates(nstar +4 * n);
nans += (curdate - ndatel);
caldatfnans, &mm, &dd, &yyyy) ;
return 10000*(yyyy- 1900) + 100*mm + dd;
}
void LunarAvg (float *a, float *v, float *dt, int mode, int m, int n) {


// Подсчитываем лунное (в зависимости от даты и фазы) скользящее среднее
// для каждого дня, основанное на предыдущих днях и (в некоторых случаях)
// на последующих днях с эквивалентной лунной фазой.
// Работаем на всех имеющихся данных.
//а - вывод: значения [1..n] лунного среднего
//v - ввод: исходный ценовой ряд данных [1..n]
// dt - ввод: соответствующие [1..n] данные
// mode - ввод: метод анализа:
// 1 = складной нож IS, все прошлые циклы 008
// 2 - фиксированный период в лунных циклах
//m - ввод: дата (для режима - 1) или период анализа (для режима = 2)
// n - ввод: количество дней во всех рядах
static int i, j, cnt;
static unsigned long k;
static float sum, sdate, tiny=l.OE- 20;
if(mode == 1) { // режим складного ножа
for(i = 1; i <= n; i++} { // для каждого текущего дня
sum = 0.0; cnt = 0;
for(j = 2; j < 1000; j++) { // двигаемся назад
sdate - LunarEquivDate {dt[i] , - j ) ; //к исходной дате
if(sdate < dt[3]) break; // переход к началу
hunt(dt, n, sdate, &k) ; // находим индекс
if (sdate > dt[k]) k++;
cnt++; sum += v[k] ; // накапливаем среднюю
}
for(j = 2; j < 1000; j ++) { // двигаемся вперед
sdate = LunarEquivDate {dt[i], j); //к исходной дате
if(sdate > m) break; // избегаем данных oos
hunt(dt, n, sdate, &k); // находим индекс
if(sdate > dt[k]) k++;
cnt++; sum +- v[k]; // накапливаем среднюю
}
a[i] = sum / (cnt + tiny); // заканчиваем среднюю
) // следующий день
}
else if(mode == 2) { // режим фиксированного периода анализа
for(i = 1; i <= n; i ++) { // для каждого текущего дня
sum =0.0; cnt = 0;
for(j =2;j<1000;j++) { // двигаемся назад
if(cnt >= m) break; // выполняем достаточные условия
sdate = LunarEquivDate(dt[i] , - j); // исходная дата
if (sdate < dt[3]) break; // идем к началу
hunt(dt, n, sdate, &k); // находим индекс
if{sdate > dt[k]} k++;
cnt++; sum +- v[k]; // накапливаем среднюю
}
for(j = 2; - j < 1000; j++) { // двигаемся вперед
if (cnt >= m) break; // выполняем достаточные условия
sdate = LunarEquivDate(dt[i] , j ) // исходная дата
hunt(dt, n, sdate, &k) ; // находим индекс
if (sdate > dt[k]} k++;
cnt++; sum += v[k]; // накапливаем среднюю
}
a[i] = sum / (cnt + tiny) // заканчиваем среднюю
} // следующий день
}
}
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 = xl3modOl.c
// parms — набор [1..MAXPRM] параметров
// dt — набор [l..nb] дат в формате ГГММДД
// орn — набор [1..nb] цен открытия
// hi - набор [l..nb] максимальных цен
// 1о — набор [1..nb] минимальных цен
// cls — набор [l..nb] цен закрытия
// vol - набор [l..nb] значений объема
// oi — набор [1..nb] значений открытого интереса
// dlrv — набор [1..nb] средних долларовых волатильностей
/ / nb — количество дней в наборе данных
// ts — ссылка на класс торгового симулятора
// eqcls — набор [1..nb] уровней капитала по ценам закрытия
// объявляем локальные переменные
static int rc, cb, ncontracts, maxhold, ordertype, signal;
static int avglen, disp, k, modeltype, matype, mktindx;
static float mmstp, ptlim, stpprice, limprice, tmp, thresh;
static float exitatr[MAXBAR+1] , savg[MAXBAR+1] ;
static float mal[MAXBAR+1], ma2[MAXBAR+1], stoch[MAXBAR+1];
static float *exitatrtab[MAXMKT+1], *savgtab[MAXMKT+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; // период максимального удержания позиции
ptlim = 4; // целевая прибыль в единицах волатильности
mmstp = 1; // защитная остановка в единицах волатильности



// Выполняем вычисления по всему объему данных, которые не подвержены
// воздействию каких- либо параметров. Выполняется один раз для каждого
// рынка, результаты сохраняются в таблицах для повторного использования.
// Таким образом, значительно снижается время выполнения программы.
mktindx = ts.modelf) ; // индекс рынка
if (exitatrtab[mktindx] == NOLL) { // размещен?
exitatrtab[mktindx] = vector(1, nb); // таблица exitatr
savgtab[mktindx] = vector{1, nb); // таблица savg
AvgTrueRangeS(exitatrtab[mktindx],
hi, lo, cls, 50, nb); //50- дневный средний истинный
// диапазон
float *pchg = vector(1, nb); // вспомогательный вектор
pchg[l] = 0.0;
for(cb =2; cb < = nb; cb++) {
tmp = cls [cb] - els [cb- 1]; // изменение цены
tmp /= exitatrtab[mktindx] [cb]; // нормирование
pchg [cb] = clip(tmp, - 2.0, 2.0); // обрезание
}
LunarAvg(savgtab[mktindx],
pchg, dt, 2, 60, nb); // лунная сезонность
free_vector(pchg, 1, nb);
printf {"Mkt: %d\n", mktindx}; // показывать прогресс
}
// выполняем вычисления для всех данных
memcpy(exitatr, exitatrtab[mktindx] , sizeof (float)*nb);
memcpy(savg, savgtab[mktindx], sizeof(float)*nb);
switch(modeltype) {
case 1: // данные для импульсной модели
MovAvg{savg,savg,matype,avglen,nb) ; // сглаживающее среднее
for(cb = 1; cb <= nb; cb++)
ma2 [cb] = fabs(savg[cb]} ;
MovAvg (mal, ma2, 1, 100, nb) ; // среднее отклонение
break;
case 2: case 3: case 4: // данные для моделей пересечения
for(cb = 2; cb <= nb; cb++)
savg[cb] += savg [cb- 1]; // интеграция
MovAvg(mal,savg,matype,avglen,nb); // сглаж. средн.
MovAvg(ma2,mal,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 = I; cb <= nb; cb++) {
// не открываем позиций до периода выборки
// ... то же самое, что установка MaxBarsBack в TradeStation
if(dt[cb] < IS_DATE) { egcls[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);
/ / не проводим сделок в последние 3 0 дней выборки.
// для того, чтобы оставить место в массивах для будущих сезонностей
if(cb > nb- 30) continue;
// считаем количество контрактов для позиции
// ... мы хотим торговать эквивалентом долларовой волатильности
// ... 2 новых контрактов на S&P- 500 от 12/31/98
neontracts = RoundToInteger(5673.О / dlrv[cb]};
if(ncontracts < 1) ncontracts = 1;
// избегаем устанавливать приказы на дни с ограниченной торговлей
if(hi[cb+1] == lo[cb+1]) continue;
// генерируем входные сигналы, цены стоп- и лимитных приказов
// для всех моделей
signal = 0 ;
switch(modeltype) {
case 1: // основная модель входа на основе импульса с порогом
k = cb + disp;


tmp = thresh * mal[k] ;
if(savg[k] > tmp && savg[k- l] <= tmp)
signal = 1;
else if(savg[k] < - tmp && savg[k- l] >= - 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 (rnal, ma2 , k) } {
if (stoch[cb] > 75.0) signal = - 1;
else if (stoch[cb] < 25.0} signal = 1;
}
break;
)
limprice = 0.5 * (hi [cb] + lo [cb] ) ;
stpprice = cls[cb] + 0.5 * signal * exitatr[cb] ;
// входим в сделку, используя определенный тип приказа
if (ts.position{) <= 0 && signal == 1) {
switch(ordertype) { // select desired order type
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.position{) >= 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);
} // обрабатываем следующий день
}


Собственно коду предшествует ряд функций, необходимых для расчета лунных циклов на любом рынке с адаптивным подходом. Функция Model следует стандартным принципам: после объявления параметры копируются в местные переменные для простоты обращения. Комментарии указывают, что контролируют параметры. В следующем блоке рассчитывается средний истинный интервал за 50 дней (exitatrtab), используемый в выходах и при нормализации, а также лунные сезонные последовательности (savgtab) — прогнозируемые изменения цены для каждого дня. Эти ряды рассчитываются один раз для каждого рынка и заносятся в таблицы; это допустимо, поскольку при повторных вызовах Model в последующих тестах никакие важные параметры не изменяются. Второй блок рассчитывает специфические для моделей временные последовательности, необходимые для получения сигналов входа.

Если modeltype = 1, используется простая импульсная модель;
если modeltype = 2, то модель на основе пересечения;
если modeltype = 3, то модель на основе пересечения с подтверждением,
и если modeltype = 4, то модель на основе пересечения с подтверждением и инверсией.

Среди возможных серий есть такие варианты, как сглаженная последовательность лунных импульсов, интегрированные импульсы (ценоподобный ряд), скользящие средние для моделей на пересечении и Медленный %К для подтверждений и инверсий. В зависимости от modeltype могут приобретать значение некоторые другие параметры. Один из них, avglen, управляет периодом всех скользящих средних: в моделина основе импульса он управляет длиной центрированного треугольного скользящего среднего, а в моделях на пересечении — длиной необходимых там средних.
Другой параметр, disp, выставляет смещение, т.е. степень сдвига вперед для компенсации запаздывания скользящих средних. Параметр thresh означает величину порога, используемого в импульсной модели для длинных и коротких позиций (короткие используют отрицательное значение thresh).

Переменная matype управляет видом скользящего среднего:
1 — простое,
2 — экспоненциальное,
6 — центрированное экспоненциальное,
7 — центрированное треугольное; существуют и другие виды средних, не использованные в анализе.

После расчета всех рядов данных запускается цикл, который перебирает рыночные цены день за днем для моделирования торговли. Этот цикл содержит код для обновления симулятора, определения количества контрактов, избежания дней с ограниченной торговлей и т.п. В следующем блоке, расположенном внутри блока перебора текущих дней, происходит генерация сигналов входа. Правила определяются параметром modeltype.

Последний блок управляет отдачей соответствующих приказов согласно параметру ordertype:
1 — вход по цене открытия,
2 — по лимитному приказу,
3 — по стоп- приказу.

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