{**********************************************}
{   TOHLCSeries (derived from TCustomSeries)   }
{   Copyright (c) 1995-2020 by Steema Software }
{**********************************************}
unit OHLChart;
{$I TeeDefs.inc}

interface

Uses Classes,

     {$IFDEF D18}
     System.Generics.Collections,
     {$ENDIF}
     
     {$IFDEF FMX}
     FMX.Types,
     {$ELSE}
     Graphics,
     {$ENDIF}

     {$IFDEF D16}
     System.UITypes,
     {$ENDIF}

     {$IFDEF FMX}
     FMXTee.Procs, FMXTee.Engine, FMXTee.Chart, FMXTee.Series, FMXTee.Canvas
     {$ELSE}
     TeeProcs, Chart, Series, TeEngine, TeCanvas
     {$ENDIF};

{ WARNING:
  NOTE FOR TeeChart Pro users since version 4.

  The logic in OHLC series has been changed.
  Now the default "Y" values correspond to the Close values,
  before they were referred to Open values.
  This change should be transparent to your code unless you're
  accessing directly the "Y" values.
}

{ Used in financial applications, OHLC stands for Open,High,Low & Close.
  These are the prices a particular financial product has in a given time
  period.

  TOHLCSeries extends the basic TCustomSeries adding new lists for
  High, Low & Open prices, and preserving the default Y values for Close
  prices.

  Overrides the basic list functions (Add, Clear & Delete) plus the
  FillSampleValues method, used in design mode to show some fictional
  values to the user.

  Publishes the High, Low & Open values lists and "overrides" the XValues
  property to be DateValues and the YValues to be CloseValues.
}

type
  TOHLCSeries=class(TCustomSeries)
  private { assumed YValues = CloseValues }
    FHighValues  : TChartValueList;
    FLowValues   : TChartValueList;
    FOpenValues  : TChartValueList;

    Function GetCloseValues:TChartValueList;
    Function GetDateValues:TChartValueList;
    Procedure SetCloseValues(Value:TChartValueList);
    Procedure SetDateValues(Value:TChartValueList);
    Procedure SetHighValues(Value:TChartValueList);
    Procedure SetLowValues(Value:TChartValueList);
    Procedure SetOpenValues(Value:TChartValueList);
  protected
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    function CalcMinMaxValue(IsMin:Boolean):TChartValue; override;  //TF02014524
  public
    Constructor Create(AOwner: TComponent); override;

    Function AddOHLC( Const ADate:TDateTime;
                      Const AOpen,AHigh,ALow,AClose:Double;
                      Const ALabel:String='';
                      AColor:TColor=clTeeColor ):Integer; overload;
    Function AddOHLC(Const AOpen,AHigh,ALow,AClose:Double):Integer; overload;

    // returns random values for Open,Close,High and Low prices. Used for demos
    class Procedure GetRandomOHLC(AOpen:Double; Var AClose,AHigh,ALow:Double; Const YRange:Double);

    Function IsValidSourceOf(Value:TChartSeries):Boolean; override;
    Function MaxYValue:TChartValue; override;
    Function MinYValue:TChartValue; override;
    Function NumSampleValues:Integer; override;
  published
    property CloseValues:TChartValueList read GetCloseValues write SetCloseValues;
    property DateValues:TChartValueList read GetDateValues write SetDateValues;
    property HighValues:TChartValueList read FHighValues write SetHighValues;
    property LowValues:TChartValueList read FLowValues write SetLowValues;
    property OpenValues:TChartValueList read FOpenValues write SetOpenValues;
  end;

  TFinancialFunction=class(TTeeFunction)
  protected
    procedure HideSeries(const ASeries:TChartSeries);
    Function IsValidSource(const Value:TChartSeries):Boolean; override;
  public
    class function GallerySource:TChartSeriesClass; override;
  end;

  TOBVFunction=class(TFinancialFunction)
  private
    FVolume : TChartSeries;
    procedure SetVolume(const Value: TChartSeries);
  protected
    class Function GetEditorClass:String; override;
    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    procedure AddPoints(Source:TChartSeries); override;
  published
    property Volume:TChartSeries read FVolume write SetVolume;
  end;

  TPointFigureSeries=class(TOHLCSeries)
  private
    FBoxSize    : Double;
    FDown       : TSeriesPointer;
    FInversions : Array of Integer;
    FReversal   : Double;
    FUp         : TSeriesPointer;

    Procedure DrawColumn( const Pointer:TSeriesPointer; FromValue:Double;
                          const ToValue:Double; tmpX:Integer);
    procedure SetBoxSize(const Value: Double);
    procedure SetDown(const Value: TSeriesPointer);
    procedure SetReversal(const Value: Double);
    procedure SetUp(const Value: TSeriesPointer);
  protected
    Function CalcMaxColumns(Draw:Boolean=False):Integer;
    procedure DrawAllValues; override;
    class Function GetEditorClass:String; override;
    Procedure SetParentChart(Const Value:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    Function CountLegendItems:Integer; override;
    Function GetInversionColumn(Index: Integer):Integer;
    Function LegendItemColor(LegendIndex:Integer):TColor; override;
    Function LegendString( LegendIndex:Integer;
                           LegendTextStyle:TLegendTextStyle ):String; override;
    Function MaxXValue:TChartValue; override;
    Function MinXValue:TChartValue; override;
  published
    property BoxSize:Double read FBoxSize write SetBoxSize;
    property DownSymbol:TSeriesPointer read FDown write SetDown;
    property ReversalAmount:Double read FReversal write SetReversal;
    property UpSymbol:TSeriesPointer read FUp write SetUp;

    property Active;
    property Brush;
    property Cursor;
    property Depth;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property Pen;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;
    property XValues;
    property YValues;

    { events }
    property AfterDrawValues;
    property BeforeDrawValues;
    property OnAfterAdd;
    property OnBeforeAdd;
    property OnClearValues;
    property OnClick;
    property OnDblClick;
    property OnGetMarkText;
    property OnMouseEnter;
    property OnMouseLeave;
  end;

  TSARFunction=class(TFinancialFunction)
  private
    FAF : Double; // Acceleration Factor
    FMS : Double; // Max Step

    function CalculateSAR(const ACount:Integer;
                          const FirstClose:TChartValue;
                          const HighValues,
                          LowValues:TChartValues):TDoubleArray;

    procedure SetAF(const Value: Double);
    procedure SetMS(const Value: Double);
  protected
    class Function GetEditorClass:String; override;
  public
    Constructor Create(AOwner: TComponent); override;

    procedure AddPoints(Source: TChartSeries); override;
  published
    property AccelerationFactor:Double read FAF write SetAF;
    property MaxStep:Double read FMS write SetMS;
  end;

var
  TeeVolumeSeriesClass:TChartSeriesClass=nil;
  TeeCandleSeriesClass:TChartSeriesClass=nil;

implementation

Uses SysUtils, Math,
     {$IFDEF FMX}
     {$IFDEF D17}
     System.UIConsts,
     {$ENDIF}
     FMXTee.ProConstants
     {$ELSE}
     TeeProCo
     {$ENDIF};

type
  TValueListAccess=class(TChartValueList);

{ TOHLCSeries }

Constructor TOHLCSeries.Create(AOwner: TComponent);
Begin
  inherited;

  TValueListAccess(XValues).InitDateTime(True);
  XValues.Name:=TeeMsg_ValuesDate;
  YValues.Name:=TeeMsg_ValuesClose;

  FHighValues :=TChartValueList.Create(Self,TeeMsg_ValuesHigh);
  FLowValues  :=TChartValueList.Create(Self,TeeMsg_ValuesLow);
  FOpenValues :=TChartValueList.Create(Self,TeeMsg_ValuesOpen);
end;

function TOHLCSeries.CalcMinMaxValue(IsMin: Boolean): TChartValue;
var t : Integer;
    tmpFirst : Boolean;
    tmpValue : TChartValue;
begin
  result:=0;
  tmpFirst:=True;

  for t:=0 to Count-1 do
  begin
    if IsPainted(t) then
    begin
      if IsMin then
      begin
        tmpValue:=Math.Min(CloseValues.Value[t], HighValues.Value[t]);
        tmpValue:=Math.Min(tmpValue, LowValues.Value[t]);
        tmpValue:=Math.Min(tmpValue, OpenValues.Value[t]);
      end
      else
      begin
        tmpValue:=Math.Max(CloseValues.Value[t], HighValues.Value[t]);
        tmpValue:=Math.Max(tmpValue, LowValues.Value[t]);
        tmpValue:=Math.Max(tmpValue, OpenValues.Value[t]);
      end;

      if tmpFirst then
      begin
        result:=tmpValue;
        tmpFirst:=False;
      end
      else
      if IsMin then
         result:=Math.Min(result,tmpValue)
      else
         result:=Math.Max(result,tmpValue);
    end;
  end;
end;

Function TOHLCSeries.GetDateValues:TChartValueList;
Begin
  result:=XValues; { overrides default XValues }
end;

Procedure TOHLCSeries.SetDateValues(Value:TChartValueList);
begin
  SetXValues(Value); { overrides default XValues }
end;

Function TOHLCSeries.GetCloseValues:TChartValueList;
Begin
  result:=YValues; { overrides default YValues }
end;

Procedure TOHLCSeries.SetCloseValues(Value:TChartValueList);
begin
  SetYValues(Value); { overrides default YValues }
end;

Procedure TOHLCSeries.SetHighValues(Value:TChartValueList);
Begin
  SetChartValueList(FHighValues,Value);
end;

Procedure TOHLCSeries.SetLowValues(Value:TChartValueList);
Begin
  SetChartValueList(FLowValues,Value);
end;

Procedure TOHLCSeries.SetOpenValues(Value:TChartValueList);
Begin
  SetChartValueList(FOpenValues,Value);
end;

Function TOHLCSeries.AddOHLC( Const ADate:TDateTime;
                                Const AOpen,AHigh,ALow,AClose:Double;
                                Const ALabel:String; AColor:TColor ):Integer;
Begin
  HighValues.TempValue:=AHigh;
  LowValues.TempValue:=ALow;
  OpenValues.TempValue:=AOpen;
  result:=AddXY(ADate,AClose,ALabel,AColor);
end;

Function TOHLCSeries.AddOHLC(Const AOpen,AHigh,ALow,AClose:Double):Integer;
begin
  DateValues.DateTime:=False;
  HighValues.TempValue:=AHigh;
  LowValues.TempValue:=ALow;
  OpenValues.TempValue:=AOpen;
  result:=Add(AClose);
end;

Function TOHLCSeries.MaxYValue:TChartValue;
Begin
  result:= CalcMinMaxValue(false);
End;

Function TOHLCSeries.MinYValue:TChartValue;
Begin
  result:= CalcMinMaxValue(true);
End;

// Returns random OHLC values
class Procedure TOHLCSeries.GetRandomOHLC(AOpen:Double; Var AClose,AHigh,ALow:Double; Const YRange:Double);
var tmpY     : Integer;
    tmpFixed : Double;
Begin
  tmpY:=Abs(Round(YRange/400.0));
  AClose:=AOpen+RandomValue(Round(YRange/25.0))-(YRange/50.0); { imagine a close price... }

  { and imagine the high and low session price }
  tmpFixed:=3*Round(Abs(AClose-AOpen)/10.0);

  if AClose>AOpen then
  Begin
    AHigh:=AClose;
    ALow:=AOpen;
  end
  else
  begin
    AHigh:=AOpen;
    ALow:=AClose;
  end;

  AHigh:=AHigh+tmpFixed+RandomValue(tmpY);
  ALow:=ALow-tmpFixed-RandomValue(tmpY);
end;

Procedure TOHLCSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
Var AOpen  : Double;
    AHigh  : Double;
    ALow   : Double;
    AClose : Double;
    t      : Integer;
    s      : TSeriesRandomBounds;
Begin
  s:=RandomBounds(NumValues);

  with s do
  begin
    AOpen:=MinY+RandomValue(Round(DifY)); { starting open price }

    t:=1;

    while t<=NumValues do
    Begin
      // Generate random figures
      GetRandomOHLC(AOpen,AClose,AHigh,ALow,DifY);

      AddOHLC(tmpX,AOpen,AHigh,ALow,AClose);
      Inc(t);

      tmpX:=tmpX+StepX;  { <-- next point X value }

      // Tomorrow, the market will open at today's close plus/minus something }
      AOpen:=AClose+RandomValue(10)-5;
    end;
  end;
end;

Function TOHLCSeries.NumSampleValues:Integer;
begin
  result:=40;
end;

Function TOHLCSeries.IsValidSourceOf(Value:TChartSeries):Boolean;
begin
  result:=Value is TOHLCSeries;
end;

{ TFinancialFunction }

type
  TChartSeriesAccess=class(TChartSeries);

Procedure TFinancialFunction.HideSeries(const ASeries:TChartSeries);
begin
  ASeries.ShowInLegend:=False;
  TChartSeriesAccess(ASeries).DontSerialize:=True;
  ASeries.ShowInEditor:=False;
end;

function TFinancialFunction.IsValidSource(const Value: TChartSeries): Boolean;
begin
  result:=Value is TOHLCSeries;
end;

class function TFinancialFunction.GallerySource:TChartSeriesClass;
begin
  result:=TeeCandleSeriesClass;
end;

{ TOBVFunction }
constructor TOBVFunction.Create(AOwner: TComponent);
begin
  inherited;
  InternalSetPeriod(1);
  CanUsePeriod:=False;
  SingleSource:=True;
  HideSourceList:=True;
end;

procedure TOBVFunction.AddPoints(Source: TChartSeries);
var t   : Integer;
    tmp : Double;
    OHLC : TOHLCSeries;
begin
  ParentSeries.Clear;

  if Assigned(FVolume) and (Source is TOHLCSeries) then
  begin
    OHLC:=TOHLCSeries(Source);

    for t:=0 to Source.Count-1 do
      if FVolume.Count>t then
      begin
        tmp:=FVolume.MandatoryValueList.Value[t];

        if OHLC.CloseValues.Value[t]>OHLC.OpenValues.Value[t] then
        begin
          if t>0 then
             tmp:=tmp+ParentSeries.MandatoryValueList.Last
        end
        else
          if t>0 then
             tmp:=ParentSeries.MandatoryValueList.Last-tmp;

        ParentSeries.AddXY(OHLC.DateValues.Value[t],tmp);
      end
      else break;
  end;
end;

class function TOBVFunction.GetEditorClass: String;
begin
  result:='TOBVFuncEditor';
end;

procedure TOBVFunction.SetVolume(const Value: TChartSeries);
begin
  if FVolume<>Value then
  begin
    if Assigned(FVolume) then
       FVolume.RemoveFreeNotification(Self);

    FVolume:=Value;

    if Assigned(FVolume) then
       FVolume.FreeNotification(Self);

    ReCalculate;
  end;
end;

procedure TOBVFunction.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation=opRemove) and (AComponent=FVolume) then
     Volume:=nil;
end;

procedure TOBVFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  //inherited;

  if Chart.SeriesCount>1 then
     Chart[1].{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};

  Chart.AddSeries(TeeVolumeSeriesClass).FillSampleValues(Chart[0].Count);
  Chart.AddSeries(TFastLineSeries);

  Volume:=Chart[1];
  Volume.Title:=TeeMsg_GalleryVolume;

  Chart[2].FunctionType:=Self;
  Chart[2].DataSource:=Chart[0];

  SplitAxes;
  Volume.VertAxis:=ParentSeries.VertAxis;
end;

{ TPointFigureSeries }
Constructor TPointFigureSeries.Create(AOwner: TComponent);
begin
  inherited;
  FUp:=TSeriesPointer.Create(Self);
  FUp.Style:=psDiagCross;
  FUp.Brush.Color:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};

  FDown:=TSeriesPointer.Create(Self);
  FDown.Style:=psCircle;
  FDown.Brush.Color:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};

  FBoxSize:=1;
  FReversal:=3;
  TValueListAccess(XValues).InitDateTime(False);
end;

Destructor TPointFigureSeries.Destroy;
begin
  FreeAndNil(FDown);
  FreeAndNil(FUp);
  FInversions:=nil;
  inherited;
end;

Procedure TPointFigureSeries.DrawColumn( const Pointer:TSeriesPointer; FromValue:Double;
                                         const ToValue:Double; tmpX:Integer);
var tmpY : Integer;
begin
  repeat

    tmpY:=CalcYPosValue(FromValue);
    Pointer.PrepareCanvas(ParentChart.Canvas,Pointer.Color);
    Pointer.Draw(tmpX,tmpY);
    FromValue:=FromValue+BoxSize;

  until FromValue>ToValue;
end;

Function TPointFigureSeries.CalcMaxColumns(Draw:Boolean=False):Integer;
var tmpX        : Integer;
    tmpLow      : Double;
    tmpHigh     : Double;
    tmp         : Double;
    t           : Integer;
    tmpCol      : Integer;
    tmpDistance : Double;
    tmpIsDown   : Boolean;
    tmpColGroup : Integer;
begin
  SetLength(FInversions, Count);

  if Count>0 then
  begin
    tmpDistance:=ReversalAmount*BoxSize;

    tmpLow:=LowValues.Value[0];
    tmpHigh:=HighValues.Value[0];
    tmpCol:=0;
    tmpColGroup:=0;

    FInversions[0]:=tmpColGroup;

    if Draw then
    begin
      tmpX:=CalcXPosValue(tmpCol);
      DrawColumn(FDown,tmpLow,tmpHigh,tmpX);
    end
    else tmpX:=0;

    tmpIsDown:=True;

    for t:=1 to Count-1 do
    begin
      if tmpIsDown then
      begin
        tmp:=LowValues.Value[t];

        if tmp<=(tmpLow-BoxSize) then
        begin
          if Draw then
             DrawColumn(FDown,tmp,tmpLow-BoxSize,tmpX);

          tmpLow:=tmp;
        end
        else
        begin
          tmp:=HighValues.Value[t];

          if tmp>=(tmpLow+tmpDistance) then
          begin

            Inc(tmpCol);
            tmpHigh:=tmp;

            if Draw then
            begin
              tmpX:=CalcXPosValue(tmpCol);
              DrawColumn(FUp,tmpLow+BoxSize,tmpHigh,tmpX);
            end;

            tmpIsDown:=False;

            Inc(tmpColGroup);
          end;
        end;
      end
      else
      begin
        tmp:=HighValues.Value[t];
        if tmp>=(tmpHigh+BoxSize) then
        begin
          if Draw then
             DrawColumn(FUp,tmpHigh+BoxSize,tmp,tmpX);

          tmpHigh:=tmp;
        end
        else
        begin
          tmp:=LowValues.Value[t];

          if tmp<=(tmpHigh-tmpDistance) then
          begin
            Inc(tmpCol);
            tmpLow:=tmp;

            if Draw then
            begin
              tmpX:=CalcXPosValue(tmpCol);
              DrawColumn(FDown,tmpLow,tmpHigh-BoxSize,tmpX);
            end;

            tmpIsDown:=True;

            Inc(tmpColGroup);
          end;
        end;
      end;

      FInversions[t]:=tmpColGroup;
    end;

    result:=tmpCol+1;
  end
  else result:=0;
end;

procedure TPointFigureSeries.DrawAllValues;
var tmpBlend : TTeeBlend;
begin
  if Transparency>0 then
     tmpBlend:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Transparency)
  else
     tmpBlend:=nil;

  CalcMaxColumns(True);

  if Transparency>0 then
     ParentChart.Canvas.EndBlending(tmpBlend);
end;

function TPointFigureSeries.MaxXValue: TChartValue;
begin
  result:=CalcMaxColumns-1;
end;

function TPointFigureSeries.MinXValue: TChartValue;
begin
  result:=0;
end;

procedure TPointFigureSeries.SetBoxSize(const Value: Double);
begin
  if Value>0 then
    SetDoubleProperty(FBoxSize,Value);
end;

procedure TPointFigureSeries.SetDown(const Value: TSeriesPointer);
begin
  FDown.Assign(Value);
end;

procedure TPointFigureSeries.SetReversal(const Value: Double);
begin
  SetDoubleProperty(FReversal,Value);
end;

procedure TPointFigureSeries.SetUp(const Value: TSeriesPointer);
begin
  FUp.Assign(Value);
end;

class function TPointFigureSeries.GetEditorClass: String;
begin
  result:='TPointFigureEditor';
end;

function TPointFigureSeries.CountLegendItems: Integer;
begin
  result:=2;
end;

function TPointFigureSeries.LegendItemColor(LegendIndex: Integer): TColor;
begin
  if LegendIndex=0 then result:=FUp.Brush.Color
                   else result:=FDown.Brush.Color
end;

function TPointFigureSeries.LegendString(LegendIndex: Integer;
  LegendTextStyle: TLegendTextStyle): String;
begin
  if LegendIndex=0 then result:=TeeMsg_Up
                   else result:=TeeMsg_Down;
end;

procedure TPointFigureSeries.SetParentChart(const Value: TCustomAxisPanel);
begin
  inherited;

  if not (csDestroying in ComponentState) then
  begin
    if Assigned(FUp) then FUp.ParentChart:=ParentChart;
    if Assigned(FDown) then FDown.ParentChart:=ParentChart;
  end;
end;

function TPointFigureSeries.GetInversionColumn(Index: Integer): Integer;
begin
  if Assigned(FInversions) then result:=FInversions[Index]
                           else result:=-1;
end;

{
+ Info:
http://www.linnsoft.com/tour/techind/sar.htm
http://calderone.eresmas.com/parabolic_sar.htm
http://es.biz.yahoo.com/edu/tech/osciladores1014.html

http://www.trade10.com/parabolic_s&r.htm
http://www.market-analyst.com/kb/article.php/Parabolic_Stop_and_Reverse/
http://www.incrediblecharts.com/technical/parabolic_sar_construction.htm
http://trader.online.pl/

http://www.amibroker.com/library/formula.php?id=242
http://www.amibroker.com/library/formula.php?id=241
}

Constructor TSARFunction.Create(AOwner: TComponent);
begin
  inherited;

  CanUsePeriod:=False;
  SingleSource:=True;

  FAF:=0.02;
  FMS:=0.20;
end;

class Function TSARFunction.GetEditorClass:String;
begin
  result:='TSARFuncEditor';
end;

function Min(const x,y,z: Double): Double;
begin
  if x < y then result := x
           else result := y;

  if z < result then result := z;
end;

function Max(const x,y,z: Double): Double;
begin
  if x > y then result := x
           else result := y;

  if z > result then result := z;
end;

function TSARFunction.CalculateSAR(const ACount:Integer;
                                   const FirstClose:TChartValue;
                                   const HighValues,LowValues:TChartValues):TDoubleArray;
var i,
    reverse : Integer;
    af, ep,
    hp, lp: Double;
    LongPos : Boolean;
begin
  SetLength(result, ACount);

  result[0] := FirstClose;
  result[1] := HighValues[1];

  LongPos := true;
  af := FAF;
  ep := LowValues[0];
  hp := HighValues[0];
  lp := ep;

  for i:=2 to ACount-1 do
  begin
    if LongPos then
       result[i] := result[i-1] + af * (hp - result[i-1])
    else
       result[i] := result[i-1] + af * (lp - result[i-1]);

    Reverse := 0;

    // Check for reversal

    if LongPos then
    begin
      if LowValues[i] < result[i] then
      begin
        LongPos := False;
        reverse := 1;           // reverse position to short
        result[i] := hp;          // sar is high point in prev trade
        lp := LowValues[i];
        af := FAF;
      end;
    end
    else
    begin
      if HighValues[i] > result[i] then
      begin
        LongPos := True;
        reverse := 1;           // reverse position to long
        result[i] := lp;
        hp := HighValues[i];
        af := FAF;
      end;
    end;

    if Reverse = 0 then
    begin
      if LongPos then
      begin
        if HighValues[i] > hp then
        begin
          hp := HighValues[i];
          af := Min(af+FAF, FMS, FMS);
        end;

        result[i] := Min(result[i], LowValues[i-1], LowValues[i-2]);
      end
      else
      begin
        if LowValues[i] < lp then
        begin
          lp:= LowValues[i];
          af:= Min(af+FAF, FMS, FMS);
        end;

        result[i] := Max(result[i], HighValues[i-1], HighValues[i-2]);
      end;
    end;
  end;
end;

// http://www.amibroker.com/library/formula.php?id=241
procedure TSARFunction.AddPoints(Source: TChartSeries);

  procedure DoCalculate(const OHLC:TOHLCSeries);
  var SAR : TDoubleArray;
      t : Integer;
  begin
    SAR:=CalculateSAR(OHLC.Count,
                      OHLC.CloseValues.Value[0],
                      OHLC.HighValues.Value,
                      OHLC.LowValues.Value);

    for t:=2 to OHLC.Count-1 do
        AddFunctionXY(OHLC.YMandatory,
                      OHLC.NotMandatoryValueList.Value[t],
                      SAR[t]);
  end;

begin
  ParentSeries.Clear;

  if (Source is TOHLCSeries) and (Source.Count>2) then
     DoCalculate(TOHLCSeries(Source));
end;

procedure TSARFunction.SetAF(const Value:Double);
begin
  if FAF<>Value then
  begin
    FAF:=Value;
    ReCalculate;
  end;
end;

procedure TSARFunction.SetMS(const Value:Double);
begin
  if FMS<>Value then
  begin
    FMS:=Value;
    ReCalculate;
  end;
end;

const
  TeeMsg_SARFunction:String='SAR';

initialization
  RegisterTeeFunction( TOBVFunction,@TeeMsg_FunctionOBV,
                                    @TeeMsg_GalleryFinancial );
  RegisterTeeFunction( TSARFunction,@TeeMsg_SARFunction,
                                    @TeeMsg_GalleryFinancial);

  RegisterTeeSeries( TPointFigureSeries, @TeeMsg_GalleryPointFigure,
                                         @TeeMsg_GalleryFinancial, 1);
finalization
  UnRegisterTeeFunctions([ TOBVFunction, TSARFunction ]);
  UnRegisterTeeSeries([TPointFigureSeries]);
end.
