Реклама

понедельник, 1 ноября 2010 г.

Пишем робота Урок №3

Реализуем класс для расчета индикатора momentum (используем библиотеки и AfterConstruction, наследуемый от TObject)


Сегодня мы займемся реализацией биржевого робота. Точнее будем выполнять задачу, поставленную на уроке "Постановка задачи". Для начала нужно скачать с сайта www.easyprog.ru нужные библиотеки (PASSBaseObj и PASSIndicators). Теперь создадим в Delphi новый проект, скопируем туда закаченные библиотеки и добавим их в проект*:

В раздел uses добавим подключенные к проекту модули PASSIndicators, PASSBaseObj:
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, PASSIndicators, PASSBaseObj;
Для для реализации расчета по заданной в уроке "Математическая модель" формуле нам понадобиться создать новый класс индикатора - TPASSMomentum.  Объявим его:
TPASSMomentum=class(TPASSIndicator)
private
           FDT:integer; // длительность периода в интервалах
           FPriceFieldType:string; //тип ценового поля (Open, High, Low, Close);
           procedure UpdateParameters; virtual;
           procedure AfterConstruction; override;
protected
           FParameters:TPASSParameters; // значения параметров индикатора.
           FPriceSource:TPASSPriceSource; // источник котировок.
public
          constructor Create(ADT:integer; APriceFieldType:string);
          function GetResultByFieldName(AFieldName:string):double; override;
          function GetResultByFieldNameAndIndex(AFieldName:string; Index:LongInt):double; override;
          function GetResultByFieldNum(AFieldNum:integer):double; override;
          function GetResultByFieldNumAndIndex(AFieldNum:integer; Index:LongInt):double; override;
          procedure First; override;
          procedure Last; override;
          function Next:boolean; override;
          function Prev:boolean; override;
          function GetIndicatorName:string; override;
end;
Данный класс мы объявили как дочерний от класса TPASSIndicator - это базовый класс индикатора. В модуле PASSIndicators есть еще класса TPASSAverageIndicator, объявленный как дочерний от PASSIndicators,  он служит родительским классом для TPASSMASimple и TPASSADX, которые реализует индикаторы Moving Average. ADX соответственно. Но индикатор momentum, не является индикатором скользящего среднего и для своего расчета не требует буфера расчета среднего значений котировок, поэтому мы используем в качестве родительского класса TPASSIndicator.
Класс TPASSIndicator имеет абстрактный метод UpdateParameters, поэтому мы обязаны переопределить его в нашем классе. Это мы делаем в секции private, так как данная процедура вызывается только из методов класса индикатора и предназначена для переписывания значений параметров из списка параметров в приватные поля класса. Почему именно так? Дело в том, что при создании библиотек PASSBaseObj и PASSIndicators предполагалось, что они будут использованы для серьезного приложения, похожего на Metatrader или Metastock. А приватные поля используются только главным образом для быстродействия, так как если обращаться через имя параметра, то нужно будет искать его в списке, что замедлит работу программы. Это замедление почувствуется, когда нужно будет делать много расчетов во вложенных циклах.
В процедуре AfterConstruction, которая наследуется от TObject, являющийся предком всех классов в библиотеках, будет просто вызвана процедура  UpdateParameters, поскольку в TPASSIndicator это не сделано. Конечно, правильнее было бы переопределить AfterConstruction непосредственно в TPASSIndicator, но рефреймингом кода мы займемся потом, а сейчас наша задача поскорее создать индикатор и исследовать его.
Так мы же определяем конструктор класса, что бы создать объект класса на основании параметров. И переопределяем все абстрактные методы класса  TPASSIndicator . Кроме того, мы поля FParameters и  FPriceSource из секции private переносим в секцию protected, просто объявив их как protected в дочернем классе.  Это нужно для того, что бы эти поля стали доступные в дочернем классе но были по прежнему недоступные извне.
А теперь займемся реализацией. Начнем с UpdateParameters
procedure TPASSMomentum.UpdateParameters;
var i,cn:integer;
begin
  cn:=FParameters.Count-1;
  for i:=0 to cn do
    begin
      if UpperCase(FParameters[i].Name)='DT' then FDT:=FParameters[i].Value else
      begin
          if UpperCase(FParameters[i].Name)='PRICEFIELDTYPE' then FPriceFieldType:=FParameters[i].Value else
                raise Exception.Create('No correct parameter name '+FParameters[i].Name);
      end;
    end;
end;   
Здесь мы просто тупым перебором перебираем параметры и устанавливаем соответствующие приватные поля. В списке есть параметр, которое не соответствует ни одному приватному полю, то это нонсенс. Такого быть не должно. Сразу вызываем исключение.
Далее реализуем метод AfterConstruction и сам конструктор:
procedure TPASSMomentum.AfterConstruction;
begin
    UpdateParameters;
end;

constructor TPASSMomentum.Create(ADT:integer; APriceFieldType:string);
begin
  inherited Create;
  FParameters.Add('DT',ADT);
  FParameters.Add('PriceFieldType',APriceFieldType);
end;
Методы GetResultByFieldName и GetResultByFieldNameAndIndex предназначены для получение значения индикатора по имени функции (у индикаторов может быть несколько функций, например у ADX есть функции DI+, DI- и само ADX). У нашего же индикатора только одна функция - Value. Реализуем получение значения по имени этой функции:
function TPASSMomentum.GetResultByFieldName(AFieldName:string):double;
begin
   if UpperCase(AFieldName)<>'VALUE' then Raise Exception.Create('TPASSMomentum.GetResultByFieldName: Неверное имя поля  - '+AFieldName);
   Result:=GetResultByFieldNum(0);
end;

function TPASSMomentum.GetResultByFieldNameAndIndex(AFieldName:string; Index:LongInt):double;
begin
   if UpperCase(AFieldName)<>'VALUE' then Raise Exception.Create('TPASSMomentum.GetResultByFieldNameAndIndex: Неверное имя поля  - '+AFieldName);
   Result:=GetResultByFieldNumAndIndex(0,index);
end;
А теперь, наконец то добрались и до формулы:
function TPASSMomentum.GetResultByFieldNum(AFieldNum:integer):double;
var Pend,Pbeg:double;
begin
   if FDT=0 then Raise  Exception.Create('TPASSMomentum.GetResultByFieldNum: нулевое значение параметра DT');
   if FPriceSource.CurrentItemIndex<=FDT then raise Exception.Create('TPASSMomentum.GetResultByFieldNum: ошибка выхода за диапазон');
   if AFieldNum<>0 then Raise Exception.Create('TPASSMomentum.GetResultByFieldNum: Неверный номер поля - '+IntToStr(AFieldNum));
   Pend:=FPriceSource.GetDataByFieldName(FPriceFieldType);
   Pbeg:=FPriceSource.GetDataByFieldNameAndIndex('PriceFieldType',FPriceSource.CurrentItemIndex-FDT);
   if Pbeg=0 then raise Exception.Create('TPASSMomentum.GetResultByFieldNum: Обнаружена нулевая котировка по полю '+
        FPriceFieldType+' номер свечи '+IntToStr(FPriceSource.CurrentItemIndex-FDT)+' дата и время свечи '+
        DateTimeToStr(FPriceSource.GetDataByFieldNameAndIndex('DateTime',FPriceSource.CurrentItemIndex-FDT)));
   Result:=(Pend-Pbeg)/Pbeg*100/FDT;
end;

function TPASSMomentum.GetResultByFieldNumAndIndex(AFieldNum:integer; Index:LongInt):double;
var Pend,Pbeg:double;
begin
   if AFieldNum<>0 then Raise Exception.Create('TPASSMomentum.GetResultByFieldNumAndIndex: Неверный номер поля - '+IntToStr(AFieldNum));
   if FDT=0 then raise Exception.Create('TPASSMomentum.GetResultByFieldNumAndIndex: нулевое значение параметра DT');
   if index<=FDT then Raise  Exception.Create('TPASSMomentum.GetResultByFieldNumAndIndex: ошибка выхода за диапазон');
   Pend:=FPriceSource.GetDataByFieldNameAndIndex(FPriceFieldType,Index);
   Pbeg:=FPriceSource.GetDataByFieldNameAndIndex(FPriceFieldType,Index-FDT);
   if Pbeg=0 then raise Exception.Create('TPASSMomentum.GetResultByFieldNumAndIndex: Обнаружена нулевая котировка по полю '+
        FPriceFieldType+' номер свечи '+IntToStr(index-FDT)+' дата и время свечи '+
        DateTimeToStr(FPriceSource.GetDataByFieldNameAndIndex('DateTime',index-FDT)));
   Result:=(Pend-Pbeg)/Pbeg*100/FDT;
end;   
Перед тем как произвести расчет по формуле мы проверяемы все возможные ошибочные значения переменных и выдаем как можно более информативные исключения, что бы потом можно было легко отлаживать программу.
Теперь реализуем оставшиеся методы:
procedure TPASSMomentum.Last;
begin
   FPriceSource.Last;
end;

procedure TPASSMomentum.First;
begin
   FPriceSource.CurrentItemIndex:=FDT+1;
end;

function TPASSMomentum.Next:boolean;
begin
   Result:=FPriceSource.NextItem;
end;

function TPASSMomentum.Prev:boolean;
begin
   Result:=FPriceSource.PrevItem;
end;

function TPASSMomentum.GetIndicatorName:string;
begin
  Result:='Momentum';
end;
Подведем итоги. На этом уроке мы разработали класс для расчета индикатора momentum. На следующем уроке будем его тестировать.
 

Скриншоты, помеченные знаком * , являются цитатами и иллюстрациями  в соответствии со ст. 1274 ГК РФ программного продукта "Delphi", авторское право на который принадлежит "Borland Software Corporation"

Источник : http://easyprog.ru/index.php?option=com_content&task=view&id=61&Itemid=44

Хвала и уважение автору МегаБаксу.