Программируем свой
терминал для 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);
}
При этом не забываем проверять номер на
крайние значения.