Программируем свой терминал для FOREX


 

     Приступаем к созданию собственного терминала. Что для этого необходимо и какие перспективы? Самое главное - это большое желание. Еще необходимо время и терпение. В итоге получим свою программу для анализа рынка, для тренировки, для успешной торговли и многого, чего нет ни в одной другой программе. Обычные терминалы, распространяемые ДЦ, естественно сделаны в интересах тех же ДЦ и, как наверное многие догадываются, помогают зарабатывать тем же ДЦ. Поэтому программа, созданная самим трейдером, будет отражать интересы трейдера и будет работать на его прибыль.

     Итак, приступим. Необходимый инструмент для создания - визуальная студия (Microsoft Visual C++ .NET ver. 7.0). Тем, кто уже в ней работает - прекрасно. Для остальных, кто решился приступить к творческому процессу, рекомендую приобрести этот пакет и установить.

Для начала - создадим пустой проект. File -> New -> Project. Появился диалог создания проектов. Из Project Types выберем Visual C++ Projects, из Templates - MFC Application. Name - пишем Monkey (в честь года обезьяны), Location - выбираем директорию, куда будет сохранен проект.

Жмем Ok. Попадаем в диалог Wizard. Здесь ничего не меняем, кроме вкладки Document Template Strings. Сделаем, чтобы программа узнавала свое расширение файла. Пишем в File extension    mon. Теперь программа все файлы с расширением *.mon будет воспринимать, как свои.

Внизу жмем Finish. Все, пустой проект готов (многодокументный, что нам очень пригодится). Теперь поразмыслим, чем его наполнить.

     Естественно, нас интересуют данные, которые для начала будем закачивать из файлов. В дальнейшем попытаемся подключиться к ДЦ и качать котировки с его сервера. Каждое окно будет отображать свою валютную пару, об отображении информации (свечи, индикаторы) поговорим позже. Также подумаем о тренажере - программа будет вырисовывать графики по таймеру, а мы будем без Интернета тренироваться, причем с возможностью проиграть месяц или год за пять минут. Как шахматисты разбирают свои партии, сделаем возможность повторных прогонов с любой скоростью. В общем, полет фантазии неограничен.

     Перейдем к доступным файлам котировок. Терминал Dealing Desk от FX Euroclub (http://www.fxeuroclub.ru) хранит котировки в текстовых файлах типа "EUR _USD_15.txt" со следующей структурой:

 

<TICKER>,<PER>,<DTYYYYMMDD>,<TIME>,<OPEN>,<HIGH>,<LOW>,<CLOSE>

EUR /USD,15,20030228,103000,1.0745,1.0752,1.0735,1.0745

EUR /USD,15,20030228,104500,1.0745,1.0753,1.0733,1.0748

EUR /USD,15,20031007,183000,1.1751,1.178,1.1749,1.1764

EUR /USD,15,20031007,184500,1.1764,1.1778,1.1751,1.1769

EUR /USD,15,20031007,190000,1.1769,1.1808,1.1763,1.177

 

Терминал "MetaTrader" от ДЦ "Альпари" хранит данные в своем формате, но может их вывести в *.csv. Например, минутки "eurusd1.csv" имеют структуру:

 

23.12.2003,11:59,1.2400,1.2403,1.2396,1.2401,19

23.12.2003,12:00,1.2401,1.2408,1.2399,1.2406,5

23.12.2003,12:01,1.2405,1.2409,1.2403,1.2406,13

23.12.2003,12:02,1.2405,1.2409,1.2403,1.2407,5

23.12.2003,12:03,1.2406,1.2410,1.2403,1.2408,13

 

Сайт http://www.forexite.com хранит историю минутных котировок за три года формата MetaStock (http://www.forexite.com/free_forex_quotes/forex_history_arhiv.html), они тоже в подходящем текстовом формате:

 

<TICKER>,<DTYYYYMMDD>,<TIME>,<OPEN>,<HIGH>,<LOW>,<CLOSE>

USDCAD,20030801,000300,1.4050,1.4050,1.4050,1.4050

USDCAD,20030801,000600,1.4051,1.4051,1.4051,1.4051

USDCAD,20030801,000700,1.4052,1.4052,1.4052,1.4052

USDCAD,20030801,000800,1.4053,1.4053,1.4053,1.4053

USDCAD,20030801,000900,1.4052,1.4052,1.4050,1.4050

 

Так что выбрать есть из чего. Тут существует выбор между количеством и качеством. Как видите, не все базы котировок содержат объем. Поэтому остановимся на двух - с объемом и с большой историей.

 

Теперь разберем – какие полученные классы за что отвечают. CMonkeyDoc работает с документом – это у нас будут закачиваемые из файла данные и их производные (индикаторы). В классе CMonkeyView реализуем вывод информации на экран. Для начала будем выводить в привычных всем свечах. Источником будут данные, хранящиеся в классе CMonkeyDoc. Реализуем ввод данных из формата MetaStock и файлы от Альпари.

 

     Опишем подобную структуру в классе CMonkeyDoc (файл CMonkeyDoc.h):

 

struct MyData

{//структура входных данных по барам

    SYSTEMTIME stTime;

      UINT nOpen;

      UINT nHigh;

      UINT nLow;

      UINT nClose;  

      UINT nVolume;

};

typedef CArray<MyData,MyData> CMyDataArray;

 

Будут считываться: время периода, цена открытия, максимальная за период, минимальная, цена закрытия и объем. Теперь конкретно по вводу данных. Заходим в класс CMonkeyDoc (файл CMonkeyDoc.cpp), метод Serialise:

 

void CMonkeyDoc::Serialize(CArchive& ar)

{

      if (ar.IsStoring())

      {//запись

            // TODO: add storing code here

      }

      else

      {//********** Чтение

            if (ar.m_strFileName.Right(3)=="csv")

            {//********** Ч т е н и е   *.csv

 

            }

            else

            {//********** Ч т е н и е   *.mon

 

            }

      }

}

 

     Cделаем,  чтобы принимаемые файлы различались, согласно заголовку: *.csv – файлы от Альпари с объемом, а *.mon – файлы формата MetaStock, без объема. Теперь реализуем каждый из этих вариантов чтения. Ниже - вариант чтения *.csv. Будем построчно считывать данные файла и преобразовывать их в наши переменные времени , 4-х цены и объема:

 

CString k_arch,datatim;

while( ar.ReadString(k_arch) )

{ //23.12.2003,11:59,1.2400,1.2403,1.2396,1.2401,19

      sscanf(k_arch,"%s %s %lf %lf %lf %lf %lf", s_dat,s_tim, &op_, &hi_, &lo_, &cl_, &vol_);

      datatim=s_dat;

      r1=atoi(datatim.Mid(6,4));

      r2=atoi(datatim.Mid(3,2));

      r3=atoi(datatim.Left(2));

      datatim=s_tim;

      r4=atoi(datatim.Left(2));

      r5=atoi(datatim.Mid(3,2));

      can_dt0[i].SetDateTime(r1,r2,r3,r4,r5,0);

      op0[i]=int(op_*10000.0);

      hi0[i]=int(hi_*10000.0);

      lo0[i]=int(lo_*10000.0);

      cl0[i]=int(cl_*10000.0);

      vol0[i]=int(vol_);

}

 

     Теперь реализуем вариант чтения *.mon. Аналогичное считывание, только без объема:

 

CString k_arch,datatim;

while( ar.ReadString(k_arch) )

{//USDCAD,20030801,000600,1.4051,1.4051,1.4051,1.4051

      sscanf(k_arch,"%s %s %s %lf %lf %lf %lf", sss_vp, s_dat, s_tim, &op_, &hi_, &lo_, &cl_);

      datatim = s_dat;

      r1=atoi(dattta.Left(4));

      r2=atoi(dattta.Mid(4,2));

      r3=atoi(dattta.Mid(6,2));

      datatim = s_tim;

      r4=atoi(dattta.Left(2));

      r5=atoi(dattta.Mid(2,2));

      can_dt0[i].SetDateTime(r1,r2,r3,r4,r5,0);// ist DATA

      op0[i]=int(op_*10000.0);

      hi0[i]=int(hi_*10000.0)+2;

      lo0[i]=int(lo_*10000.0)-2;

      cl0[i]=int(cl_*10000.0);

}//while !EOF

val_par=sss_vp;

for (int k=1;k<19;k++)

{//поиск номера вал пары

 

      if (a_vp[k]==val_par)

      {

            n_valpar=k;

            break;

      }

}//end поиск номера вал пары

 

Реализовано два варианта считывания данных. Теперь можно их выводить на экран в виде графика, обрабатывать статистически и т.д.

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

 

void CMonkeyDoc::Kratnost(int k_krat,int k_bar)

{

      int i,j,l;

      for (i=0; i<k_bar; i+=k_krat)

      {

            l=i/k_krat;

            hi[l]=hi0[i];

            lo[l]=lo0[i];    

            can_dt[l]=can_dt0[i];

            vol[l]=0;

            for (j=0;j<k_krat;j++) 

            {    

                  if (l*k_krat+j+1>k_bar) break;

                  if (j==0) op[l]=op0[i];

                  cl[l]=cl0[i+j];

                  hi[l]=max(hi[l],hi0[i+j]);

                  lo[l]=min(lo[l],lo0[i+j]);

                  vol[l]+=vol0[i+j];

            }

      } 

Новый полученный массив полностью соответствует архиву котировок искомого периода, причем кроме традиционных M5, M15, M30 и т.д. могут быть получены любые: M40, M180, M222 и т.д.

 

     Теперь рассмотрим общие вопросы вывода на экран. Массив данных у нас уже есть, он хранится в классе документа CMonkeyDoc.

Выводить график в виде традиционных свечей будем в классе CMonkeyView. Необходимо осуществить прокрутку экрана, масштабирование по горизонтали, автоматическое масштабирование по вертикали. Кроме того, необходимо предусмотреть часть экрана под индикаторы, которые могут выводиться не на основном графике, а в отдельном окне под графиком.

     На основном графике должна быть координатная сетка – вертикальная по цене с опорными линиями цен и горизонтальная сетка времени с опорными линиями времени. То есть прежде, чем вывести некоторую серию свечей, надо определить их временной диапазон (с времени первой до времени последней), а также их ценовой диапазон. В этой серии свечей необходимо определить максимальную цену (High) и минимальную (Low). Исходя из этого диапазона цен будет отображаться вертикальная ценовая сетка.

     Масштабирование по вертикали начинается с поиска максимума и минимума среди выводимых свечей:

 

for (kn=k1; kn < k2; kn++)

{// поиск максимума и минимума

      v_max=max(pDoc->hi[kn],UINT(v_max));

      if (pDoc->lo[kn]>0) v_min=min(pDoc->lo[kn],UINT(v_min));

}

 

 

     Ниже приведен код прорисовки свечей – цвет свечи определяется разностью цены закрытия и открытия:

 

for (kn=k1; kn < k2; kn++)//начало цикла

{//рисуем свечи

      y1=v_char_bot-int(pDoc->lo[kn]-v_min)/norm;

      y2=v_char_bot-int(pDoc->hi[kn]-v_min)/norm;

      pDC->Rectangle(xx+1,y1,xx+krat-1,y2); //рисуем тени свечи

      y1=v_char_bot-int(pDoc->op[kn]-v_min)/norm;

      y2=v_char_bot-int(pDoc->cl[kn]-v_min)/norm;

      if (pDoc->op[kn] > pDoc->cl[kn]) pDC->SelectObject (&b_dw); //свеча вниз

      if (pDoc->op[kn] < pDoc->cl[kn]) pDC->SelectObject (&b_up); //свеча вверх

      pDC->Rectangle(xx,y1,xx+krat,y2); //рисуем тело свечи

}

 

    Рассмотрим масштабирование графика по горизонтали. Коэффициент krat будет содержать масштаб. По нажатию клавиши “+” вызывается функция OnMshtPlus(), которая наращивает этот коэффициент, а по клавише “-” функция OnMshtMinus() уменьшает его. Соответственно график вырисовывается заново с разным масштабом.

 

void CMonkeyView::OnMshtPlus()// изменение масштаба

{

      if (krat < 18) {krat+=2;}

 

      Invalidate(FALSE);

}

 

void CMonkeyView::OnMshtMinus()

{

      if (krat > 2) {krat-=2;}

 

      Invalidate(FALSE);

}

     Получается следующая картинка:

График

     Рассмотрим перемещение графика влево и вправо. Для этого надо знать номер начального (для графика) бара и по клавишам “стрелка влево” и ”стрелка вправо” наращивать и уменьшать его.

 

void CMonkeyView::OnArRight()//перемещение

{

      CMonkeyDoc* pDoc = GetDocument();

      ASSERT_VALID(pDoc);

 

      if (pDoc->bar0<MaxBar-80/krat+1) pDoc->bar0+=80/krat;

      Invalidate(FALSE);

}

 

void CMonkeyView::OnArLeft()

{

      CMonkeyDoc* pDoc = GetDocument();

      ASSERT_VALID(pDoc);

 

      if (pDoc->bar0>80/krat) pDoc->bar0-=80/krat;

      else pDoc->bar0=0;

      Invalidate(FALSE);

}

 

     При этом не забываем проверять номер на крайние значения.