{********************************************}
{ TeeChart Statistical Functions             }
{ Copyright (c) 2001-2020 by Steema Software }
{ All Rights Reserved                        }
{********************************************}
unit StatChar;
{$I TeeDefs.inc}

interface

Uses {$IFNDEF LINUX}
     {$IFNDEF FMX}
     Windows,
     {$ENDIF}
     {$ENDIF}

     Classes,

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

     {$IFDEF FMX}
     FMX.Types,

     {$IFDEF D17}
     System.UIConsts,
     {$ENDIF}

     {$IFDEF D19}
     FMX.Graphics,
     {$ENDIF}

     {$ELSE}
     Graphics,
     {$ENDIF}

     {$IFDEF FMX}
     FMXTee.Procs, FMXTee.Canvas, FMXTee.Engine, FMXTee.Series,
     FMXTee.Series.Candle, FMXTee.Series.OHLC
     {$ELSE}
     TeeProcs, TeEngine, Series, TeCanvas, CandleCh, OHLChart
     {$ENDIF}
     ;

Type
  { Moving Average, Weigted, WeightedIndex }
  TMovingAverageFunction=class(TTeeMovingFunction)
  private
    FWeighted      : Boolean;
    FWeightedIndex : Boolean;
    Procedure SetWeighted(Value:Boolean);
    procedure SetWeightedIndex(const Value: Boolean);
  protected
    class function GetEditorClass: String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Function Calculate( const Series:TChartSeries;
                        FirstIndex,LastIndex:Integer):Double; override;
  published
    property Weighted:Boolean read FWeighted write SetWeighted default False;
    property WeightedIndex:Boolean read FWeightedIndex write SetWeightedIndex default False;
  end;

  { Exponential Moving Average }
  TExpMovAveFunction=class(TTeeFunction)
  private
    FIgnoreNulls : Boolean;
    procedure SetIgnoreNulls(const Value:Boolean);
  public
    Constructor Create(AOwner:TComponent); override;
    procedure AddPoints(Source:TChartSeries); override;
  published
    property IgnoreNulls:Boolean read FIgnoreNulls write SetIgnoreNulls
                                 default True;
  end;

  { Exponential Average }
  TExpAverageFunction = class(TTeeMovingFunction)
  private
    FWeight : Double;
    Procedure SetWeight(Const Value:Double);
  protected
    class function GetEditorClass: String; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Function Calculate( const Series:TChartSeries;
                        FirstIndex,LastIndex:Integer):Double; override;
  published
    property Weight:Double read FWeight write SetWeight;
  end;

  { Smoothed Moving Average }
  TSmoothedMovAvgFunction=class(TTeeMovingFunction)
  private
    IFirstMovAvg : TChartValue;
  protected
    Procedure DoCalculation( Source:TChartSeries;
                             NotMandatorySource:TChartValueList); override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Function Calculate( const Series:TChartSeries;
                        FirstIndex,LastIndex:Integer):Double; override;
  end;

  { Momemtum }
  TMomentumFunction=class(TTeeMovingFunction)
  public
    Constructor Create(AOwner:TComponent); override;
    Function Calculate( const Series:TChartSeries;
                        FirstIndex,LastIndex:Integer):Double; override;
  end;

  { Momemtum Divisor }
  TMomentumDivFunction=class(TTeeMovingFunction)
  protected
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Function Calculate( const Series:TChartSeries;
                        FirstIndex,LastIndex:Integer):Double; override;
  end;

  TFloatPointArray = array of TFloatPoint;

  TKDEClamp = (kdcMinMax,kdcManual);
  TKDEType = (kdtViolin,kdtBeeSwarm);

  TKDEOptions=class
    private
      FKDEType: TKDEType;
      FResolution: Integer;
      FBandwidth: Integer;
      FWidth: Integer;
      FClamp: TKDEClamp;
    protected
      procedure SetKDEType(Value:TKDEType);
      procedure SetResolution(Value:Integer);
      procedure SetBandwidth(Value:Integer);
      procedure SetWidth(Value:Integer);
      procedure SetClamp(Value:TKDEClamp);
    public
      property KDEType:TKDEType read FKDEType write SetKDEType;
      property Resolution:Integer read FResolution write SetResolution;
      property Bandwidth:Integer read FBandwidth write SetBandwidth;
      property Width:Integer read FWidth write SetWidth;
      property Clamp:TKDEClamp read FClamp write SetClamp;
    public
  end;

  { Kernel Density Estimator }
  TKDEFunction=class(TTeeFunction)
  private
    FSourceValues : TChartValueList;
    FKDEPoints : TFloatPointArray;
    FOptions : TKDEOptions;
    FPosition : Double;
    FClampMin : Double;
    FClampMax : Double;
  protected
    function KernelDensityEstimator(x : Array of Double; vals : TChartValueList) : TFloatPointArray;
    procedure SetPosition(aPos : Double);
    procedure SetClampMax(aVal : Double);
    procedure SetClampMin(aVal : Double);
    function GetOptions : TKDEOptions;
  public
    Constructor Create(AOwner:TComponent); override;
    procedure Clear; override;
    procedure AddPoints(Source:TChartSeries); overload; override;
    procedure AddListPoints(Source:TChartValueList); overload;
  published
    property Position:Double read FPosition write SetPosition;
    property ClampMax:Double read FClampMax write SetClampMax;
    property ClampMin:Double read FClampMin write SetClampMin;
    property Options:TKDEOptions read GetOptions write FOptions;
    property KDEPoints: TFloatPointArray read FKDEPoints write FKDEPoints;
    procedure CalculateKDE;
    procedure CalcClampRange;
    procedure CalculateValues(aValues : TChartValueList);
  end;

  { RMS, Root Mean Square }
  TRMSFunction = class(TTeeFunction)
  private
    FComplete  : Boolean;
    Procedure SetComplete(const Value: Boolean);
  protected
    Function CalculateValue(const Values:TChartValues):Double; override;
    class function GetEditorClass: String; override;
  published
    property Complete: Boolean read FComplete write SetComplete default False;
  end;

  // Standard Deviation, Complete
  TStdDeviationFunction=class(TTeeFunction)
  private
    FComplete : Boolean;
    Procedure SetComplete(Value:Boolean);
  protected
    Function CalculateValue(const Values:TChartValues):Double; override;
    class function GetEditorClass: String; override;
  published
    property Complete:Boolean read FComplete write SetComplete default False;
  end;

  { MACD, Moving Average Convergence }
  TMACDFunction=class(TTeeMovingFunction)
  private
    IHisto   : TVolumeSeries;
    IMoving1 : TExpMovAveFunction;
    IMoving2 : TExpMovAveFunction;
    IOther   : TFastLineSeries;
    ISeries1 : TChartSeries;
    ISeries2 : TChartSeries;

    function GetHistoPen: TChartPen;
    function GetMACDExpPen: TChartPen;
    function GetPeriod2: Double;
    function GetPeriod3:Integer;
    procedure SetHistoPen(const Value: TChartPen);
    procedure SetMACDExpPen(const Value: TChartPen);
    procedure SetPeriod2(const Value:Double);
    procedure SetPeriod3(Const Value:Integer);
    function GetMACDPen: TChartPen;
    procedure SetMACDPen(const Value: TChartPen);
  protected
    procedure Clear; override;
    class function GetEditorClass: String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    procedure AddPoints(Source: TChartSeries); override;

    property MACDExp:TFastLineSeries read IOther;
    property Histogram:TVolumeSeries read IHisto;
  published
    property HistogramPen:TChartPen read GetHistoPen write SetHistoPen;
    property MACDPen:TChartPen read GetMACDPen write SetMACDPen;
    property MACDExpPen:TChartPen read GetMACDExpPen write SetMACDExpPen;
    property Period2:Double read GetPeriod2 write SetPeriod2;
    property Period3:Integer read GetPeriod3 write SetPeriod3 default 9;
  end;

  { Stochastic }
  TStochasticFunction=class(TTeeMovingFunction)
  protected
    FNums : TDoubleArray;
    FDens : TDoubleArray;

    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    procedure AddPoints(Source: TChartSeries); override;
    Function Calculate( const Series:TChartSeries;
                        FirstIndex,LastIndex:Integer):Double; override;
    class function GallerySource:TChartSeriesClass; override;
  end;

  { Histogram Series }
  THistogramSeries=class(TCustomLineSeries)
  private
    FLinesPen   : TChartHiddenPen;
    FOrigin     : Double;
    FUseOrigin  : Boolean;

    IPrevious : TCoordinate;

    Procedure InternalCalcHoriz(Axis:TChartAxis; Var Min,Max:Integer);
    procedure SetLinesPen(const Value: TChartHiddenPen);
    Procedure SetOrigin(Const Value:Double);
    Procedure SetUseOrigin(Value:Boolean);
    Function VisiblePoints:Integer;
  protected
    Procedure AssignFormatting(Source:TChartSeries); override;
    function CalcRect(ValueIndex:Integer):TRect; virtual;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    Procedure CalcHorizMargins(Var LeftMargin,RightMargin:Integer); override;
    Procedure CalcVerticalMargins(Var TopMargin,BottomMargin:Integer); override;
    procedure DrawValue(ValueIndex:Integer); override;
    class Function GetEditorClass:String; override;
    Function GetOriginPos(ValueIndex:Integer):Integer;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;
    Function Clicked(x,y:Integer):Integer; override;
    Procedure CalcFirstLastVisibleIndex; override;
  published
    property Active;
    property ColorEachPoint;
    property ColorSource;
    property Cursor;
    property HorizAxis;
    property LinePen;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;

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

    property Brush;
    property LinesPen:TChartHiddenPen read FLinesPen write SetLinesPen;
    property Pen;
    property Transparency;
    property XValues;
    property UseYOrigin:Boolean read FUseOrigin write SetUseOrigin default False;
    property YValues;
    property YOrigin:Double read FOrigin write SetOrigin;
  end;

  THorizHistogramSeries=class(THistogramSeries)
  protected
    function CalcRect(ValueIndex:Integer):TRect; override;
  public
    Constructor Create(AOwner: TComponent); override;
  end;

  { Skewness }
  TSkewnessFunction=class(TTeeFunction)
  private
    FComplete : Boolean;

    Function Distrib(Series: TChartSeries; Exponent:Integer):Double;
    Procedure SetComplete(Value:Boolean);
  protected
    class function GetEditorClass: String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Function Calculate(const SourceSeries:TChartSeries; FirstIndex,LastIndex:Integer):Double; override;
  published
    property Complete:Boolean read FComplete write SetComplete default False;
  end;

  { Kurtosis }
  TKurtosisFunction=class(TSkewnessFunction)
  public
    Function Calculate(const SourceSeries:TChartSeries; FirstIndex,LastIndex:Integer):Double; override;
  end;

  { CLV Function }

  TCLVFunction=class(TFinancialFunction)
  private
    FAccumulate: Boolean;
    FVolume: TChartSeries;

    procedure SetAccumulate(const Value: Boolean);
    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 Accumulate:Boolean read FAccumulate write SetAccumulate default True;
    property Volume:TChartSeries read FVolume write SetVolume;
  end;

  { Money Flow }
  TCMFStyle =(cmfLine,cmfArea,cmfVolume,cmfHistogram);

  TMoneyFlowFunction=class(TCLVFunction)
  private
    FCMFStyle : TCMFStyle;
    FDownColor: TColor;
    FUpColor  : TColor;

    IVolume   : TVolumeSeries;
    IArea     : TAreaSeries;
    IHistogram: THistogramSeries;

    function GetAreaPen: TChartPen;
    function GetHistoPen: TChartPen;

    procedure SetAreaPen(const Value: TChartPen);
    procedure SetCMFStyle(const Value: TCMFStyle);
    Procedure SetDownColor(const Value:TColor);
    procedure SetHistoPen(const Value: TChartPen);
    Procedure SetUpColor(const Value:TColor);
  protected
    procedure Clear; override;
    class function GetEditorClass: String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    procedure AddPoints(Source: TChartSeries); override;

    property Area:TAreaSeries read IArea;
    property Histogram:THistogramSeries read IHistogram;
  published
    property Accumulate default False;
    property AreaPen:TChartPen read GetareaPen write SetAreaPen;
    property CMFStyle: TCMFStyle read FCMFStyle write SetCMFStyle default cmfLine;
    property DownColor:TColor read FDownColor write SetDownColor
                                 default {$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};
    property HistogramPen:TChartPen read GetHistoPen write SetHistoPen;
    property UpColor:TColor read FUpColor write SetUpColor
                                 default {$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};
  end;

  { RVI, Relative Vigor Index }
  TRVIFunction=class(TFinancialFunction)
  private
    ISignal : TFastLineSeries;

    function GetSignalPen: TChartPen;
    procedure SetSignalPen(const Value: TChartPen);
  protected
    procedure Clear; override;
    class Function GetEditorClass:String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure AddPoints(Source:TChartSeries); override;
    property Signal:TFastLineSeries read ISignal;
  published
    property SignalPen:TChartPen read GetSignalPen write SetSignalPen;
  end;

  { AO, Awesome Oscillator }
  TAOFunction=class(TFinancialFunction)
  protected
    class Function GetEditorClass:String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;

    procedure AddPoints(Source:TChartSeries); override;
  end;

  { AC, Accelerator/Decelerator Oscillator }
  TACFunction=class(TFinancialFunction)
  protected
    class Function GetEditorClass:String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;

    procedure AddPoints(Source:TChartSeries); override;
  end;

  { Alligator }
  TAlligatorFunction=class(TFinancialFunction)
  private
    ILips  : TFastLineSeries;
    ITeeth : TFastLineSeries;

    function GetLipsPen: TChartPen;
    function GetTeethPen: TChartPen;
    procedure SetLipsPen(const Value: TChartPen);
    procedure SetTeethPen(const Value: TChartPen);
  protected
    procedure Clear; override;
    class Function GetEditorClass:String; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure AddPoints(Source:TChartSeries); override;
    property Teeth:TFastLineSeries read ITeeth;
    property Lips:TFastLineSeries read ILips;
  published
    property TeethPen:TChartPen read GetTeethPen write SetTeethPen;
    property LipsPen:TChartPen read GetLipsPen write SetLipsPen;
  end;

  { Gator Oscillator }
  TGatorFunction=class(TFinancialFunction)
  private
    IBottom : TVolumeSeries;
  protected
    procedure Clear; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure AddPoints(Source:TChartSeries); override;
    property Bottom:TVolumeSeries read IBottom;
  end;

  { Financial Bollinger Bands }
  TBollingerFunction=class(TTeeFunction)
  private
    FExponential : Boolean;
    FDeviation   : Double;
    IOther       : TChartSeries;
    procedure SetDeviation(const Value: Double);
    procedure SetExponential(const Value: Boolean);
    function GetLowBandPen: TChartPen;
    procedure SetLowBandPen(const Value: TChartPen);
    function GetUpperBandPen: TChartPen;
    procedure SetUpperBandPen(const Value: TChartPen);
  protected
    procedure Clear; override;
    class function GetEditorClass: String; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;
    procedure AddPoints(Source:TChartSeries); override;
    property LowBand:TChartSeries read IOther;
  published
    property Deviation:Double read FDeviation write SetDeviation;
    property Exponential:Boolean read FExponential write SetExponential default True;
    property LowBandPen:TChartPen read GetLowBandPen write SetLowBandPen;
    property UpperBandPen:TChartPen read GetUpperBandPen write SetUpperBandPen;
  end;

  { Cross Points }
  TCrossPointsFunction=class(TTeeFunction)
  protected
    class function GetEditorClass: String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    procedure AddPoints(Source:TChartSeries); override;
  end;

  { Performance }
  TPerformanceFunction=class(TTeeMovingFunction)
  public
    Function Calculate(const SourceSeries:TChartSeries;
                       FirstIndex,LastIndex:Integer):Double; override;
  end;

  // Variance
  TVarianceFunction=class(TTeeFunction)
  protected
    Function CalculateValue(const Values:TChartValues):Double; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  end;

  // Perimeter
  TPerimeterFunction=class(TTeeFunction)
  public
    constructor Create(AOwner: TComponent); override;
    procedure AddPoints(Source:TChartSeries); override;
  end;

  // Percentage Change
  TPercentChangeFunction=class(TTeeMovingFunction)
  protected
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Function Calculate(const SourceSeries:TChartSeries;
                       FirstIndex,LastIndex:Integer):Double; override;
  end;

  // Slope
  TSlopeFunction=class(TTeeMovingFunction)
  public
    Function Calculate(const SourceSeries:TChartSeries;
                       FirstIndex,LastIndex:Integer):Double; override;
  end;

  TPVOFunction=class(TTeeFunction)
  private
    FExpMovAve1 : TExpMovAveFunction;
    FExpMovAve2 : TExpMovAveFunction;
    FExpSeries1 : TChartSeries;
    FExpSeries2 : TChartSeries;
    FPercent    : Boolean;

    function GetPeriod2: Integer;
    procedure SetPercent(const Value: Boolean);
    procedure SetPeriod2(const Value: Integer);
  protected
    class Function GetEditorClass:String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure AddPoints(Source:TChartSeries); override;
  published
    property Percentage:Boolean read FPercent write SetPercent default True;
    property Period2:Integer read GetPeriod2 write SetPeriod2 default 26;
  end;

  TCCIFunction=class(TFinancialFunction)
  private
    FAveSeries : TChartSeries;
    FConstant  : Double;
    FMovAve    : TMovingAverageFunction;
    FTypical   : TChartSeries;

    function IsConstStored: Boolean;
    procedure SetConstant(const Value: Double);
  protected
    class Function GetEditorClass:String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;
    procedure AddPoints(Source:TChartSeries); override;
  published
    property Constant:Double read FConstant write SetConstant stored IsConstStored;
  end;

  TCentroidTeeFunction=class(TTeeFunction)
  protected
    class function GallerySource:TChartSeriesClass; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    procedure AddPoints(Source:TChartSeries); override;
  end;

implementation

Uses SysUtils, Math,
     {$IFDEF FMX}
     FMXTee.Constants, FMXTee.ProConstants, FMXTee.Chart
     {$ELSE}
     Chart, TeeProCo, TeeConst
     {$ENDIF}
     ;

{ TMovingAverageFunction }

Procedure TMovingAverageFunction.SetWeighted(Value:Boolean);
begin
  if FWeighted<>Value then
  begin
    FWeighted:=Value;
    Recalculate;
  end;
end;

class function TMovingAverageFunction.GetEditorClass: String;
begin
  result:='TMovAveFuncEditor'; // Do not localize
end;

procedure TMovingAverageFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  Period:=10;
end;

Function TMovingAverageFunction.Calculate(const Series:TChartSeries;FirstIndex,LastIndex:Integer):Double;
var t         : Integer;
    tmp       : Integer;
    tmpSumX   : Double;
    tmpYValue : Double;
    tmpXValue : Double;
    tmpVList  : TChartValueList;
begin
  Assert(Period>0,'Period must be bigger than zero.');

  result:=0;
  tmpSumX:=0;
  tmpVList:=ValueList(Series);

  for t:=FirstIndex to LastIndex do
  begin
    tmpYValue:=tmpVList.Value[t];

    if FWeighted then
    Begin
      tmpXValue:=Series.XValues.Value[t];
      result:=result+tmpYValue*tmpXValue;
      tmpSumX:=tmpSumX+tmpXValue;
    end
    else
    if FWeightedIndex then
    begin
      tmpXValue:=t-FirstIndex+1;
      result:=result+tmpYValue*tmpXValue;
      tmpSumX:=tmpSumX+tmpXValue;
    end
    else
      result:=result+tmpYValue;
  end;

  if FWeighted or FWeightedIndex then
  begin
    if tmpSumX<>0 then
       result:=result/tmpSumX
    else
       result:=0;
  end
  else
  begin
    tmp:=LastIndex-FirstIndex+1;

    if tmp<>0 then
       result:=result/tmp;
  end;
end;

procedure TMovingAverageFunction.SetWeightedIndex(const Value: Boolean);
begin
  if FWeightedIndex<>Value then
  begin
    FWeightedIndex:=Value;
    Recalculate;
  end;
end;

{ TExpAverageFunction }
Constructor TExpAverageFunction.Create(AOwner: TComponent);
Begin
  inherited;
  FWeight:=0.2;
End;

class function TExpAverageFunction.GetEditorClass: String;
begin
  result:='TExpAveFuncEditor'; // Do not localize
end;

Procedure TExpAverageFunction.SetWeight(Const Value:Double);
Begin
  if (Value<0) or (Value>1) then
     raise Exception.Create(TeeMsg_ExpAverageWeight);

  if FWeight<>Value then
  begin
    FWeight:=Value;
    Recalculate;
  end;
End;

Function TExpAverageFunction.Calculate( const Series:TChartSeries;
                                        FirstIndex,LastIndex:Integer):Double;
Begin
  With ValueList(Series) do
  begin
    result:=Value[LastIndex];

    if LastIndex>0 then
       result:=Value[LastIndex-1]*(1.0-Weight)+result*Weight;
  end;
end;

// Helper proc to recalculate a function of a Series that is not connected
// to any chart.  (ParentChart = nil)
procedure ReCalculateSource(ASeries,ASource:TChartSeries);
begin
  ASeries.BeginUpdate;  // before Clear
  try
    ASeries.Clear;
    ASeries.FunctionType.AddPoints(ASource); // calculate function
  finally
    ASeries.EndUpdate; // propagate changes...
  end;
end;

{ TSmoothedMovAvgFunction }
{http://www.20minutetraders.com/learning/moving_averages/smooth-moving-average.php}

procedure TSmoothedMovAvgFunction.DoCalculation(Source:TChartSeries;
                                       NotMandatorySource:TChartValueList);
var tmpMovAvg : TChartSeries;
begin
  tmpMovAvg:=TChartSeries.Create(nil);
  try
    tmpMovAvg.SetFunction(TMovingAverageFunction.Create(nil));
    tmpMovAvg.FunctionType.Period:=Period;
    ReCalculateSource(tmpMovAvg, Source);

    if tmpMovAvg.Count=0 then
       IFirstMovAvg:=0
    else
       IFirstMovAvg:=tmpMovAvg.YValues.Value[0];

    inherited;

  finally
    tmpMovAvg.Free;
  end
end;

procedure TSmoothedMovAvgFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  Period:=10;
end;

Function TSmoothedMovAvgFunction.Calculate( const Series:TChartSeries;
                                           FirstIndex,LastIndex:Integer):Double;
var PrevSMMA : Double;
    PrevSum  : Double;
    Price    : Double;
begin
  Assert(Period>0,'Period must be bigger than zero.');

  if FirstIndex=0 then
     result:=IFirstMovAvg
  else
  begin
    PrevSMMA:=ParentSeries.YValues.Last;
    PrevSum:=PrevSMMA*Period;
    Price:=ValueList(Series).Value[FirstIndex+Round(Period)-1];
    result:=(PrevSum - PrevSMMA + Price) / Period;
  end;
end;

{ Momentum }

Constructor TMomentumFunction.Create(AOwner: TComponent);
begin
  inherited;
  InternalSetPeriod(2);
end;

Function TMomentumFunction.Calculate( const Series:TChartSeries;
                                      FirstIndex,LastIndex:Integer):Double;
Begin
  if FirstIndex=TeeAllValues then
  begin
    FirstIndex:=0;
    LastIndex:=Series.Count-1;
  end;

  With ValueList(Series) do
       result:=Value[LastIndex]-Value[FirstIndex];
End;

{ MomentumDivision }

Constructor TMomentumDivFunction.Create(AOwner: TComponent);
begin
  inherited;
  Period:=2;
end;

procedure TMomentumDivFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  SplitAxes;
end;

Function TMomentumDivFunction.Calculate( const Series:TChartSeries;
                                      FirstIndex,LastIndex:Integer):Double;
Begin
  if FirstIndex=TeeAllValues then
  begin
    FirstIndex:=0;
    LastIndex:=Series.Count-1;
  end;

  With ValueList(Series) do
  if Value[FirstIndex]=0 then result:=0
                         else result:=100.0*Value[LastIndex]/Value[FirstIndex];
End;

{ StdDeviation }
class function TStdDeviationFunction.GetEditorClass: String;
begin
  result:='TRMSFuncEditor'; // Do not localize
end;

Function TStdDeviationFunction.CalculateValue(const Values:TChartValues):Double;
var i,tmpCount: Integer;
    tmpSum    : Double;
    tmpSum2   : Double;
    Divisor   : Double;
begin
  tmpCount:=Length(Values);
  if tmpCount>1 then
  begin
    tmpSum:=0;
    tmpSum2:=0;
    for i:=0 to tmpCount-1 do
    begin
      tmpSum:=tmpSum+Values[i];
      tmpSum2:=tmpSum2+Sqr(Values[i]);
    end;
    
    if Complete then Divisor:=Sqr(tmpCount)
                else Divisor:=1.0*tmpCount*(tmpCount-1);

    result:=((1.0*tmpCount*tmpSum2) - Sqr(tmpSum)) / Divisor;

    if result<0 then result:=0
                else result:=Sqrt(result);
  end
  else
    result:=0;
end;

Procedure TStdDeviationFunction.SetComplete(Value:Boolean);
begin
  if FComplete<>Value then
  begin
    FComplete:=Value;
    Recalculate;
  end;
end;

{ THistogramSeries }
Constructor THistogramSeries.Create(AOwner: TComponent);
begin
  inherited;
  FLinesPen:=TChartHiddenPen.Create(CanvasChanged);
end;

Destructor THistogramSeries.Destroy;
begin
  FLinesPen.Free;
  inherited;
end;

Procedure THistogramSeries.CalcFirstLastVisibleIndex;
begin
  inherited;
  if FirstValueIndex>0 then
     Dec(FFirstVisibleIndex);

  if LastValueIndex<(Count-1) then
     Inc(FLastVisibleIndex);
end;

procedure THistogramSeries.SetLinesPen(const Value: TChartHiddenPen);
begin
  FLinesPen.Assign(Value);
end;

class Function THistogramSeries.GetEditorClass:String;
begin
  result:='THistogramSeriesEditor'; // Do not localize
end;

Function THistogramSeries.VisiblePoints:Integer;
begin
  result:=ParentChart.MaxPointsPerPage;
  if result=0 then
     result:=Count;
end;

Procedure THistogramSeries.CalcVerticalMargins(Var TopMargin,BottomMargin:Integer);
begin
  inherited;

  if YMandatory then
  begin
    if Pen.Visible then
       Inc(TopMargin,{$IFDEF FMX}Round{$ENDIF}(Pen.Width));
  end
  else
    InternalCalcHoriz(GetVertAxis,TopMargin,BottomMargin)
end;

Procedure THistogramSeries.InternalCalcHoriz(Axis:TChartAxis; Var Min,Max:Integer);
var tmp : Integer;
begin
  tmp:=VisiblePoints;

  if tmp>0 then
     tmp:=(Axis.IAxisSize div VisiblePoints) div 2;

  Inc(Min,tmp);
  Inc(Max,tmp);

  if Pen.Visible then
     Inc(Max,{$IFDEF FMX}Round{$ENDIF}(Pen.Width));
end;

Procedure THistogramSeries.CalcHorizMargins(Var LeftMargin,RightMargin:Integer);
begin
  inherited;

  if YMandatory then
     InternalCalcHoriz(GetHorizAxis,LeftMargin,RightMargin)
  else
  if Pen.Visible then
     Inc(RightMargin,{$IFDEF FMX}Round{$ENDIF}(Pen.Width));
end;

Procedure THistogramSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is THistogramSeries then
  With THistogramSeries(Source) do
  begin
    Self.Pen:=Pen;
    Self.LinesPen:=LinesPen;
    Self.Brush:=Brush;
  end;

  inherited;
end;

Procedure THistogramSeries.Assign(Source:TPersistent);
begin
  if Source is THistogramSeries then
  With THistogramSeries(Source) do
  begin
    Self.FOrigin    :=YOrigin;
    Self.FUseOrigin :=UseYOrigin;
  end;

  inherited;
end;

function THistogramSeries.CalcRect(ValueIndex:Integer):TRect;

  function CalcMidX(const UseNext:Boolean):Integer;
  begin
    if UseNext then
       result:=Round((CalcXPos(ValueIndex + 1) - CalcXPos(ValueIndex)) *0.5)
    else
       result:=Round((CalcXPos(ValueIndex) - CalcXPos(ValueIndex - 1)) *0.5);
  end;

var tmp : Integer;
begin
  if VisiblePoints>0 then
  begin
    if DrawValuesForward then // TF02014446
       tmp:=CalcMidX(ValueIndex=FirstDisplayedIndex)
    else
       tmp:=CalcMidX(ValueIndex<>FirstDisplayedIndex);

    with result do
    begin
      if ValueIndex=FirstDisplayedIndex then
      begin
        if DrawValuesForward then
        begin
          Left:=CalcXPos(ValueIndex)-tmp-1;
          Right:=Left+2*tmp+2
        end
        else
        begin
          Left:=CalcXPos(ValueIndex)+tmp+1;
          Right:=Left-2*tmp-2
        end;
      end
      else
      begin
        Left:=IPrevious;
        if DrawValuesForward then
           Right:=CalcXPos(ValueIndex)+tmp+1
        else
           Right:=CalcXPos(ValueIndex)-tmp-1
      end;

      IPrevious:=Right-1;

      Top:=CalcYPos(ValueIndex);

      With GetVertAxis do
      if Inverted then Bottom:=IStartPos
                  else Bottom:=GetOriginPos({$IFDEF FMX}System.{$ENDIF}Round(YOrigin));
    end;
  end
  else
    result:=TeeZeroRect;
end;

procedure THistogramSeries.DrawValue(ValueIndex:Integer);

  Function LastDisplayedIndex:Integer;
  begin
    if DrawValuesForward then result:=LastValueIndex
                         else result:=FirstValueIndex;
  end;

  Procedure VerticalLine(const X,Y0,Y1:TCoordinate);
  begin
    if ParentChart.View3D then
       ParentChart.Canvas.VertLine3D(X,Y0,Y1,MiddleZ)
    else
       ParentChart.Canvas.DoVertLine(X,Y0,Y1);
  end;

  Procedure HorizLine(const X0,X1,Y:TCoordinate);
  begin
    if ParentChart.View3D then
       ParentChart.Canvas.HorizLine3D(X0,X1,Y,MiddleZ)
    else
       ParentChart.Canvas.DoHorizLine(X0,X1,Y);
  end;

  function TryHoverPen(const APen:TTeePen):TTeePen;
  begin
    if HoverEnabled and (Selected.HoverIndex=ValueIndex) then
       result:=Selected.Hover.Pen
    else
       result:=APen;
  end;

var R        : TRect;
    tmp      : TCoordinate;
    tmpR     : TRect;
    tmpBlend : TTeeBlend;
    doLinesPen : Boolean;
    tmpPen   : TTeePen;
begin
  R:=CalcRect(ValueIndex);

  if not IsNull(ValueIndex) then
  begin
    With ParentChart.Canvas do
    begin
      if Pen.Style<>psClear then
         Pen.Style:=psClear;

      // rectangle
      if Self.Brush.Style<>bsClear then
      begin
        AssignBrush(Self.Brush,ValueColor[ValueIndex]);

        if GetVertAxis.Inverted then R.Top:=R.Top+1
                                else R.Bottom:=R.Bottom+1;

        if Transparency>0 then
        begin
          if SupportsAlpha then
             tmpBlend:=BeginBlending(TeeZeroRect,Transparency)
          else
          begin
            if ParentChart.View3D then tmpR:=CalcRect3D(R,MiddleZ)
                                  else tmpR:=R;

            tmpBlend:=BeginBlending(tmpR,Transparency);
          end;
        end
        else tmpBlend:=nil;

        if ParentChart.View3D then
           RectangleWithZ(TeeRect(R.Left,R.Top,R.Right-1,R.Bottom),MiddleZ)
        else
        begin
           R.Top:=R.Top+1;
           Rectangle(R);
           R.Top:=R.Top-1;
        end;

        if Transparency>0 then
           EndBlending(tmpBlend);

        if GetVertAxis.Inverted then R.Top:=R.Top-1
                                else R.Bottom:=R.Bottom-1;
      end;

      tmpPen:=TryHoverPen(Self.Pen);

      // border
      if tmpPen.Visible then
      begin
        AssignVisiblePen(tmpPen);

        With R do
        if YMandatory then
        begin
          if ValueIndex=FirstDisplayedIndex then
             VerticalLine(Left,Bottom,Top)
          else
          begin
            if DrawValuesForward then
               if IsNull(ValueIndex-1) then
                  VerticalLine(Left,Top,Bottom)
               else
                  VerticalLine(Left,Top,CalcYPos(ValueIndex-1))
            else
               if IsNull(ValueIndex+1) then
                  VerticalLine(Left,Top,Bottom)
               else
                  VerticalLine(Left,Top,CalcYPos(ValueIndex+1));
          end;

          HorizLine(Left,Right,Top);

          if (ValueIndex=LastDisplayedIndex) or IsNull(ValueIndex+1) then
             VerticalLine(Right-1,Top,Bottom);
        end
        else
        begin
          if ValueIndex=FirstDisplayedIndex then
             HorizLine(Left,Right,Bottom-1)
          else
          begin
            if DrawValuesForward then
               if (IsNull(ValueIndex-1)) then
                  HorizLine(Left,Right,Bottom-1)
               else
                  HorizLine(CalcXPos(ValueIndex-1),Right,Bottom-1)
            else
               if (IsNull(ValueIndex+1)) then
                  HorizLine(Left,Right,Bottom-1)
               else
                  HorizLine(CalcXPos(ValueIndex+1),Right,Bottom-1);
          end;

          VerticalLine(Right-Self.Pen.Width,Top,Bottom);

          if (ValueIndex=LastDisplayedIndex) or IsNull(ValueIndex+1) then
             HorizLine(Left,Right,Top);
        end
      end;

      // dividing line
      if DrawValuesForward then
         doLinesPen:=ValueIndex>FirstDisplayedIndex
      else
         doLinesPen:=ValueIndex<FirstDisplayedIndex;

      tmpPen:=TryHoverPen(LinesPen);

      if doLinesPen and tmpPen.Visible then
      begin
        if YMandatory then
        begin
          if DrawValuesForward then
            tmp:=CalcYPos(ValueIndex-1)
          else
            tmp:=CalcYPos(ValueIndex+1);

          if GetVertAxis.Inverted then
             tmp:=Math.Min(R.Top,tmp)
          else
             tmp:=Math.Max(R.Top,tmp);

          if not Self.Pen.Visible then tmp:=tmp-1;

          AssignVisiblePen(tmpPen);
          VerticalLine(R.Left,R.Bottom,tmp);
        end
        else
        begin
          if DrawValuesForward then
             tmp:=CalcXPos(ValueIndex-1)
          else
             tmp:=CalcXPos(ValueIndex+1);

          if GetHorizAxis.Inverted then
             tmp:=Math.Min(R.Right,tmp)
          else
             tmp:=Math.Max(R.Left,tmp);

          if not Self.Pen.Visible then tmp:=tmp-1;

          AssignVisiblePen(tmpPen);
          HorizLine(R.Left,tmp,R.Bottom-1);
        end;
      end;
    end;
  end;
end;

class procedure THistogramSeries.CreateSubGallery(
  AddSubChart: TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_Hollow);
  AddSubChart(TeeMsg_NoBorder);
  AddSubChart(TeeMsg_Lines);
  AddSubChart(TeeMsg_Transparency);
end;

class procedure THistogramSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  with THistogramSeries(ASeries) do
  Case Index of
    1: Brush.Style:=bsClear;
    2: Pen.Hide;
    3: LinesPen.Visible:=True;
    4: Transparency:=30;
  else inherited;
  end;
end;

function THistogramSeries.Clicked(x, y: Integer): Integer;
var t : Integer;
begin
  result:=TeeNoPointClicked;

  if (FirstValueIndex<>-1) and (LastValueIndex<>-1) then
  begin
    if Assigned(ParentChart) then
       ParentChart.Canvas.Calculate2DPosition(X,Y,MiddleZ);

    for t:=FirstValueIndex to LastValueIndex do
    if PointInRect(CalcRect(t),x,y) then
    begin
      result:=t;
      break;
    end;
  end;
end;

function THistogramSeries.GetOriginPos(ValueIndex: Integer): Integer;
begin
  if UseYOrigin then result:=CalcPosValue(YOrigin)
                else result:=GetVertAxis.IEndPos;
    end;

procedure THistogramSeries.SetOrigin(const Value: Double);
begin
  SetDoubleProperty(FOrigin,Value)
  end;

procedure THistogramSeries.SetUseOrigin(Value: Boolean);
begin
  SetBooleanProperty(FUseOrigin,Value)
end;

{ THorizHistogramSeries }
Constructor THorizHistogramSeries.Create(AOwner: TComponent);
begin
  inherited;
  SetHorizontal;
  XValues.Order:=loNone;
  YValues.Order:=loAscending;
end;

function THorizHistogramSeries.CalcRect(ValueIndex: Integer): TRect;
var tmp : Integer;
begin
  {tmp:=GetVertAxis.CalcPosValue(VisiblePoints) -
        GetVertAxis.CalcPosValue(FirstDisplayedIndex);

  tmp:=(tmp div VisiblePoints) div 2;}

  if DrawValuesForward then
  begin
    if ValueIndex = FirstDisplayedIndex then
      tmp:=Round((CalcYPos(ValueIndex) - CalcYPos(ValueIndex + 1)) / 2)
    else
      tmp:=Round((CalcYPos(ValueIndex - 1) - CalcYPos(ValueIndex)) / 2);
  end
  else
  begin
    if ValueIndex = FirstDisplayedIndex then
      tmp:=Round((CalcYPos(ValueIndex - 1) - CalcYPos(ValueIndex)) / 2)
    else
      tmp:=Round((CalcYPos(ValueIndex) - CalcYPos(ValueIndex + 1)) / 2);
  end;

  With result do
  begin
    if ValueIndex=FirstDisplayedIndex then
    begin
      if DrawValuesForward then
      begin
        Top:=CalcYPos(ValueIndex)-tmp+1;
        Bottom:=Top+2*tmp;
      end
      else
      begin
        Top:=CalcYPos(ValueIndex)+tmp+1;
        Bottom:=Top-2*tmp;
      end;

    end
    else
    begin
      Bottom:=IPrevious;
      if DrawValuesForward then
        Top:=CalcYPos(ValueIndex)-tmp
      else
        Top:=CalcYPos(ValueIndex)+tmp;
    end;

    IPrevious:=Top+1;

    Right:=CalcXPos(ValueIndex)+1;

    With GetHorizAxis do
    if Inverted then Left:=IEndPos
                else Left:=IStartPos;
  end;
  end;

{ TStochasticFunction }
constructor TStochasticFunction.Create(AOwner: TComponent);
begin
  inherited;
  SingleSource:=True;
  HideSourceList:=True;
end;

Destructor TStochasticFunction.Destroy;
begin
  FNums:=nil;
  FDens:=nil;
  inherited;
end;

procedure TStochasticFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  SplitAxes;
  ParentSeries.GetVertAxis.SetMinMax(0,100);
  ParentSeries.GetVertAxis.AxisValuesFormat:='0%';
end;

procedure TStochasticFunction.AddPoints(Source: TChartSeries);
begin
  FNums:=nil;
  FDens:=nil;
  SetLength(FNums,Source.Count);
  SetLength(FDens,Source.Count);
  inherited;
end;

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

function TStochasticFunction.Calculate(const Series: TChartSeries; FirstIndex,
  LastIndex: Integer): Double;
var Lows    : TChartValueList;
    Highs   : TChartValueList;
    tmpLow  : Double;
    tmpHigh : Double;
    t       : Integer;
begin
  result:=0;

  With Series do
  Begin
    Lows   :=GetYValueList(TeeMsg_ValuesLow);
    Highs  :=GetYValueList(TeeMsg_ValuesHigh);
    tmpLow :=Lows.Value[FirstIndex];
    tmpHigh:=Highs.Value[FirstIndex];

    for t:=FirstIndex to LastIndex do
    begin
      if Lows.Value[t] <tmpLow  then tmpLow :=Lows.Value[t];
      if Highs.Value[t]>tmpHigh then tmpHigh:=Highs.Value[t];
    end;

    FNums[LastIndex]:=ValueList(Series).Value[LastIndex]-tmpLow;
    FDens[LastIndex]:=tmpHigh-tmpLow;

    if tmpHigh<>tmpLow then
       result:=100.0*(FNums[LastIndex]/FDens[LastIndex]);
  end;
end;

{ TRMSFunction }
class function TRMSFunction.GetEditorClass: String;
begin
  result:='TRMSFuncEditor'; // Do not localize
end;

Function TRMSFunction.CalculateValue(const Values:TChartValues):Double;
var i,tmpCount: Integer;
    tmpSum2   : Double;
    Divisor   : Double;
begin
  tmpCount:=Length(Values);
  if tmpCount>1 then
  begin
    tmpSum2:=0;
    for i:=0 to tmpCount-1 do tmpSum2:=tmpSum2+Sqr(Values[i]);

    if Complete then Divisor:=tmpCount
                else Divisor:=tmpCount-1;

    result:=Sqrt(tmpSum2 / Divisor );
  end
  else
    result:=0;
end;

procedure TRMSFunction.SetComplete(const Value: Boolean);
begin
  if FComplete<>Value then
  begin
    FComplete:=Value;
    Recalculate;
  end;
end;

{ TMACDFunction }

type
  TChartSeriesAccess=class(TChartSeries);

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

Constructor TMACDFunction.Create(AOwner: TComponent);
begin
  inherited;
  SingleSource:=True;

  IMoving1:=TExpMovAveFunction.Create(nil);
  IMoving1.Period:=12;
  ISeries1:=TChartSeries.Create(nil);
  ISeries1.SetFunction(IMoving1);
  IMoving2:=TExpMovAveFunction.Create(nil);
  IMoving2.Period:=26;
  ISeries2:=TChartSeries.Create(nil);
  ISeries2.SetFunction(IMoving2);
  Period:=IMoving2.Period;

  IOther:=TFastLineSeries.Create(Self);
  HideSeries(IOther);
  IOther.SetFunction(TExpMovAveFunction.Create(nil));
  IOther.FunctionType.Period:=9;
  IOther.SeriesColor:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};

  IHisto:=TVolumeSeries.Create(Self);
  HideSeries(IHisto);
  IHisto.SeriesColor:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};
  IHisto.UseYOrigin:=True;
  IHisto.YOrigin:=0;
end;

destructor TMACDFunction.Destroy;
begin
  ISeries1.Free;
  ISeries2.Free;
  inherited;
end;

class function TMACDFunction.GetEditorClass: String;
begin
  result:='TMACDFuncEditor'; // Do not localize
end;

procedure TMACDFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  SplitAxes;
  MACDExp.VertAxis:=ParentSeries.VertAxis;
  Histogram.VertAxis:=ParentSeries.VertAxis;
end;

procedure TMACDFunction.AddPoints(Source: TChartSeries);

  Procedure PrepareSeries(const ASeries:TChartSeries);
  begin { copy properties from "ParentSeries" to ASeries }
    With ASeries do
    begin
      ParentChart     :=ParentSeries.ParentChart;
      CustomVertAxis  :=ParentSeries.CustomVertAxis;
      VertAxis        :=ParentSeries.VertAxis;
      XValues.DateTime:=ParentSeries.XValues.DateTime;
      AfterDrawValues :=ParentSeries.AfterDrawValues;
      BeforeDrawValues:=ParentSeries.BeforeDrawValues;
    end;
  end;

var t : Integer;
begin
  { calculate first line... }
  ParentSeries.Clear;

  With Source do
  if Count>1 then
  begin
    IMoving1.AddPoints(Source);
    IMoving2.Period:=Self.Period;
    IMoving2.AddPoints(Source);

    // Subtract = ISeries1 - ISeries2
    for t:=0 to Count-1 do
        ParentSeries.AddXY(XValues.Value[t],
                           ISeries1.YValues.Value[t]-ISeries2.YValues.Value[t]);

    ISeries1.Clear;
    ISeries2.Clear;
  end;

  // Calculate "MACDExp" second line, even if not visible.
  PrepareSeries(IOther);

  if ParentSeries.SeriesColor={$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF} then
     ParentSeries.SeriesColor:={$IFDEF FMX}claBlue{$ELSE}clBlue{$ENDIF};

  IOther.DataSource:=nil;
  IOther.DataSource:=ParentSeries;

  { calculate Histogram if Active... }
  if IHisto.Active then
  begin
    PrepareSeries(IHisto);

    IHisto.BeginUpdate;
    try
      IHisto.Clear;

      With IOther do
      for t:=0 to Count-1 do
          IHisto.AddXY(IOther.XValues.Value[t],
                       ParentSeries.YValues.Value[t]-IOther.YValues.Value[t]);
    finally
      IHisto.EndUpdate;
    end;
  end;
end;

function TMACDFunction.GetPeriod2: Double;
begin
  result:=IMoving1.Period
end;

procedure TMACDFunction.SetPeriod2(const Value: Double);
begin
  if IMoving1.Period<>Value then
  begin
    IMoving1.Period:=Value;

    if IMoving1.Period<1 then
       IMoving1.Period:=1;

    Recalculate;
  end;
end;

function TMACDFunction.GetPeriod3: Integer;
begin
  result:=Round(IOther.FunctionType.Period);
end;

procedure TMACDFunction.SetPeriod3(const Value: Integer);
begin
  IOther.FunctionType.Period:=Value;
end;

function TMACDFunction.GetHistoPen: TChartPen;
begin
  result:=Histogram.Pen;
end;

function TMACDFunction.GetMACDExpPen: TChartPen;
begin
  result:=MACDExp.Pen;
end;

procedure TMACDFunction.SetHistoPen(const Value: TChartPen);
begin
  Histogram.Pen:=Value;
end;

procedure TMACDFunction.SetMACDExpPen(const Value: TChartPen);
begin
  MACDExp.Pen:=Value;
end;

function TMACDFunction.GetMACDPen: TChartPen;
begin
  if Assigned(ParentSeries) then
     result:=ParentSeries.Pen
  else
     result:=nil;
end;

procedure TMACDFunction.SetMACDPen(const Value: TChartPen);
begin
  ParentSeries.Pen:=Value;
end;

procedure TMACDFunction.Clear;
begin
  inherited;
  IOther.Clear;
  IHisto.Clear;
end;

{ TExpMovAveFunction }
constructor TExpMovAveFunction.Create(AOwner: TComponent);
begin
  inherited;
  FIgnoreNulls:=True;
  CanUsePeriod:=False;
  SingleSource:=True;
  InternalSetPeriod(10);
end;

procedure TExpMovAveFunction.AddPoints(Source: TChartSeries);
var tmpV : TChartValueList;
    Old  : Double;
    t    : Integer;
    tmp  : Double;
    t0   : Integer;
begin
  ParentSeries.Clear;

  if Period>0 then
  With Source do
  if Count>1 then
  begin
    tmpV:=ValueList(Source);
    tmp:=2/(Self.Period+1);

    if FIgnoreNulls then
    begin
      Old:=tmpV.Value[0];
      ParentSeries.AddXY(NotMandatoryValueList.Value[0],Old);

      for t:=1 to Count-1 do
      begin
        Old:=(tmpV.Value[t]*tmp)+(Old*(1-tmp));
        ParentSeries.AddXY(NotMandatoryValueList.Value[t],Old);
      end;
    end
    else
    begin
      t0:=0;

      while IsNull(t0) and (t0<Count-1) do
            Inc(t0);

      if t0<Count then
      begin
        Old:=tmpV.Value[t0];
        ParentSeries.AddXY(NotMandatoryValueList.Value[t0],Old);

        for t:=t0+1 to Count-1 do
        if not IsNull(t) then 
        begin
          Old:=(tmpV.Value[t]*tmp)+(Old*(1-tmp));
          ParentSeries.AddXY(NotMandatoryValueList.Value[t],Old);
        end;
      end;
    end;
  end;
end;

procedure TExpMovAveFunction.SetIgnoreNulls(const Value:Boolean);
begin
  if FIgnoreNulls<>Value then
  begin
    FIgnoreNulls:=Value;
    Recalculate;
  end;
end;

{ TBollingerFunction }
constructor TBollingerFunction.Create(AOwner: TComponent);
begin
  inherited;
  SingleSource:=True;
  Exponential:=True;
  Deviation:=2;

  IOther:=TFastLineSeries.Create(Self);
  IOther.ShowInLegend:=False;
  TChartSeriesAccess(IOther).DontSerialize:=True;
  IOther.ShowInEditor:=False;

  InternalSetPeriod(10);
end;

class function TBollingerFunction.GetEditorClass: String;
begin
  result:='TBollingerFuncEditor'; // Do not localize
end;

procedure TBollingerFunction.AddPoints(Source:TChartSeries);
Var AList : TChartValueList;

  Function StdDev(const First,Last:Integer):Double;
  var ISum  : Double;
      ISum2 : Double;
      tmp   : Double;
      t     : Integer;
  begin
    ISum:=0;
    ISum2:=0;

    for t:=First to Last do
    begin
      tmp:=AList.Value[t];
      ISum:=ISum+tmp;
      ISum2:=ISum2+Sqr(tmp);
    end;

    ISum:=((Period*ISum2) - Sqr(ISum)) / Sqr(Period);

    if ISum>0 then result:=Sqrt(ISum)
              else result:=0; //{ Negative square root not allowed
  end;

  Procedure InternalAddPoints(const ASeries:TChartSeries; const ADeviation:Double);
  var Mov       : TTeeFunction;
      tmp       : TChartSeries;
      tmpValue  : Double;
      tmpPeriod : Integer;
      t         : Integer;
      tmpSource : String;
  begin
    if Exponential then Mov:=TExpMovAveFunction.Create(nil)
                   else Mov:=TMovingAverageFunction.Create(nil);

    Mov.Period:=Period;

    tmp:=TChartSeries.Create(nil);
    try
      if Source.ParentChart=nil then
         tmp.AssignValues(Source)
      else
      begin
        tmp.ParentChart:=Source.ParentChart;
        tmp.DataSource:=Source;
      end;

      tmpSource:=ParentSeries.MandatoryValueList.ValueSource;

      if tmpSource='' then
         tmpSource:=Source.MandatoryValueList.Name;

      tmp.MandatoryValueList.ValueSource:=tmpSource;
      tmp.SetFunction(Mov);

      ASeries.Clear;
      AList:=Source.GetYValueList(tmpSource);
      tmpPeriod:=Round(Period);

      for t:=tmpPeriod to Source.Count do
      begin
        tmpValue:=(ADeviation*StdDev(t-tmpPeriod,t-1));

        if Exponential then
           tmpValue:=tmp.YValues.Value[t-1]+tmpValue
        else
           tmpValue:=tmp.YValues.Value[t-tmpPeriod]+tmpValue;

        ASeries.AddXY(Source.XValues.Value[t-1],tmpValue);
      end;

      tmp.DataSource:=nil;
    finally
      tmp.Free;
    end;
  end;

begin
  With IOther do
  begin
    ParentChart:=ParentSeries.ParentChart;
    CustomVertAxis:=ParentSeries.CustomVertAxis;
    VertAxis:=ParentSeries.VertAxis;
    SeriesColor:=ParentSeries.SeriesColor;

    if ClassType=ParentSeries.ClassType then
       Pen.Assign(ParentSeries.Pen);

    XValues.DateTime:=ParentSeries.XValues.DateTime;
    AfterDrawValues:=ParentSeries.AfterDrawValues;
    BeforeDrawValues:=ParentSeries.BeforeDrawValues;
  end;

  if Period>0 then
  begin
    InternalAddPoints(ParentSeries,Deviation);
    InternalAddPoints(IOther,-Deviation);
  end;
end;

procedure TBollingerFunction.Clear;
begin
  inherited;
  IOther.Clear;
end;

procedure TBollingerFunction.SetDeviation(const Value: Double);
begin
  if FDeviation<>Value then
  begin
    FDeviation:=Value;
    ReCalculate;
  end;
end;

procedure TBollingerFunction.SetExponential(const Value: Boolean);
begin
  if FExponential<>Value then
  begin
    FExponential:=Value;
    ReCalculate;
  end;
end;

destructor TBollingerFunction.Destroy;
begin
  IOther.Free;
  inherited;
end;

function TBollingerFunction.GetLowBandPen: TChartPen;
begin
  result:=LowBand.Pen;
end;

procedure TBollingerFunction.SetLowBandPen(const Value: TChartPen);
begin
  LowBand.Pen:=Value;
end;

function TBollingerFunction.GetUpperBandPen: TChartPen;
begin
  if Assigned(ParentSeries) then
     result:=ParentSeries.Pen
  else
     result:=nil;
end;

procedure TBollingerFunction.SetUpperBandPen(const Value: TChartPen);
begin
  ParentSeries.Pen:=Value;
end;

{ TCrossPointsFunction }

Constructor TCrossPointsFunction.Create(AOwner: TComponent);
begin
  inherited;
  CanUsePeriod:=False;
end;

procedure TCrossPointsFunction.AddPoints(Source: TChartSeries);
var tmp1   : TChartValueList;
    tmp2   : TChartValueList;
    tmpX1  : TChartValueList;
    tmpX2  : TChartValueList;

  Function LinesCross(index1,index2:Integer; out x,y:Double):Boolean;
  begin
    result:=CrossingLines(
                tmpX1.Value[index1],tmp1.Value[index1],
                tmpX1.Value[index1+1],tmp1.Value[index1+1],
                tmpX2.Value[index2],tmp2.Value[index2],
                tmpX2.Value[index2+1],tmp2.Value[index2+1],
                x,y
                );
  end;

var Index1,
    Index2 : Integer;
    x,
    y : Double;
begin
  if ParentSeries.DataSources.Count>1 then
  begin
    Source:=TChartSeries(ParentSeries.DataSources[0]);
    tmp1:=ValueList(Source);
    tmpX1:=Source.NotMandatoryValueList;
    tmp2:=ValueList(TChartSeries(ParentSeries.DataSources[1]));
    tmpX2:=TChartSeries(ParentSeries.DataSources[1]).NotMandatoryValueList;

    ParentSeries.Clear;

    if (tmpX1.Count>1) and (tmpX2.Count>1) then
    begin
      index1:=0;
      index2:=0;

      repeat
        if LinesCross(index1,index2,x,y) then
           ParentSeries.AddXY(x,y);

        if tmpX2.Value[index2+1]<tmpX1.Value[index1+1] then
           Inc(Index2)
        else
           Inc(Index1);

      until (index1>=tmpX1.Count-1) or (index2>=tmpX2.Count-1); //TV52015960
    end;
  end;
end;

class function TCrossPointsFunction.GetEditorClass: String;
begin
  result:=''; // no options for this function
end;

procedure TCrossPointsFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
var tmpCount : Integer;
begin
  Chart[0].Title:=TeeMsg_Data+' 1';

  if Chart.SeriesCount<2 then
     Chart.AddSeries(TChartSeriesClass(Chart[0].ClassType));

  Chart[1].Title:=TeeMsg_Data+' 2';

  Chart[1].FillSampleValues(Chart[0].Count);

  Chart.AddSeries(TPointSeries).BeginUpdate;
  try
    Chart[2].DataSources.Add(Chart[0]);
    Chart[2].DataSources.Add(Chart[1]);
  finally
    Chart[2].EndUpdate;
  end;
  
  Chart[2].FunctionType:=Self;

  TPointSeries(Chart[2]).Pointer.Style:=psCircle;

  // Just in case the two source series have no "crossing" points, repeat:

  tmpCount:=0;

  while Chart[2].Count=0 do
  begin
    if tmpCount>10 then  // try 10 times, then stop
       break;

    Chart[1].FillSampleValues(Chart[0].Count);

    Inc(tmpCount);
  end;
end;

{ TPerformanceFunction }

function TPerformanceFunction.Calculate(const SourceSeries: TChartSeries;
  FirstIndex, LastIndex: Integer): Double;
begin
  if FirstIndex=TeeAllValues then
     LastIndex:=SourceSeries.Count-1;

  With ValueList(SourceSeries) do
  if Value[0]<>0 then
     result:=(Value[LastIndex]-Value[0])*100.0/Value[0]
  else
     result:=0;
end;


{ TVarianceFunction }
Function TVarianceFunction.CalculateValue(const Values:TChartValues):Double;
var tmpMean   : Double;
    tmpSum    : Double;
    i,tmpCount: Integer;
begin
  tmpCount:=Length(Values);
  if tmpCount>0 then
  begin
    tmpMean:=0;
    for i:=0 to tmpCount-1 do tmpMean:=tmpMean+Values[i];

    tmpMean:=tmpMean/tmpCount;

    tmpSum:=0;
    for i:=0 to tmpCount-1 do tmpSum:=tmpSum+Sqr(Values[i]-tmpMean);

    result:=tmpSum/tmpCount;
  end
  else
    result:=0;
end;

procedure TVarianceFunction.PrepareForGallery(const Chart: TCustomAxisPanel);
begin
  inherited;
  ParentSeries.VertAxis:=aRightAxis;
end;

{ TPerimeterFunction }
Constructor TPerimeterFunction.Create(AOwner: TComponent);
begin
  inherited;
  CanUsePeriod:=False;
  SingleSource:=True;
end;

procedure TPerimeterFunction.AddPoints(Source:TChartSeries);

  procedure CheckAxisScales(Axis:TChartAxis);
  begin
    with Axis do
    if (IAxisSize=0) or ((Maximum-Minimum)=0) then
        AdjustMaxMin;
  end;

var t : Integer;
    tmp : Integer;
    P : TPointArray;
    tmpCanvas : TCanvas3D;
begin
  ParentSeries.Clear;

  if (Source.Count>0) and (Source.ParentChart<>nil) then
  begin
    SetLength(P,Source.Count);
    try
      with Source do
      begin
        CheckAxisScales(GetHorizAxis);
        CheckAxisScales(GetVertAxis);

        tmpCanvas:=ParentChart.Canvas;

        for t:=0 to Count-1 do
            P[t]:=tmpCanvas.Calculate3DPosition(CalcXPos(t),CalcYPos(t),MiddleZ);

        TTeeCanvas.ConvexHull(P);
      end;

      with ParentSeries do
      begin
        XValues.Order:=loNone;

        tmp:=Length(P);
        for t:=0 to tmp-1 do
            AddXY(XScreenToValue({$IFDEF FMX}Round{$ENDIF}(P[t].X)),
                  YScreenToValue({$IFDEF FMX}Round{$ENDIF}(P[t].Y)));

        if tmp>0 then
            AddXY(XValues[0],YValues[0]);
      end;
    finally
      P:=nil;
    end;
  end;
end;

{ TPercentChangeFunction }

procedure TPercentChangeFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  SplitAxes;
  ParentSeries.GetVertAxis.AxisValuesFormat:='0%';
end;

function TPercentChangeFunction.Calculate(const SourceSeries: TChartSeries;
  FirstIndex, LastIndex: Integer): Double;
var tmp : TChartValue;
begin
  result:=0;

  if (FirstIndex>0) and (SourceSeries.Count>LastIndex) then
     with ValueList(SourceSeries) do
     begin
       tmp:=Value[Pred(FirstIndex)];

       if tmp<>0 then
          result:=100*(Value[LastIndex]-tmp)/tmp;
     end;
end;

{ TSlopeFunction }

Function TSlopeFunction.Calculate(const SourceSeries:TChartSeries;
                                  FirstIndex,LastIndex:Integer):Double;
var tmpX : TChartValue;
begin
  result:=0;

  if (FirstIndex>0) and (SourceSeries.Count>LastIndex) then
  begin
    with SourceSeries.NotMandatoryValueList do
         tmpX:=Value[LastIndex]-Value[Pred(FirstIndex)];

    if tmpX<>0 then
    with ValueList(SourceSeries) do
         result:=(Value[LastIndex]-Value[Pred(FirstIndex)])/tmpX;
  end;
end;

{ TMoneyFlowFunction }

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

  InternalSetPeriod(21);
  CanUsePeriod:=False;
  SingleSource:=True;
  HideSourceList:=True;
  Accumulate:=False;
  FCMFStyle:=cmfLine;

  FUpColor:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};
  FDownColor:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};

  IVolume:=TVolumeSeries.Create(Self);
  HideSeries(IVolume);
  IVolume.UseYOrigin:=True;
  IVolume.YOrigin:=0;
  IVolume.ColorEachPoint:=True;

  IArea:=TAreaSeries.Create(Self);
  HideSeries(IArea);
  IArea.UseYOrigin:=True;
  IArea.YOrigin:=0;
  IArea.AreaLinesPen.Visible:=False;
  IArea.ColorEachPoint:=True;

  IHistogram:=THistogramSeries.Create(Self);
  HideSeries(IHistogram);
  IHistogram.UseYOrigin:=True;
  IHistogram.YOrigin:=0;
  IHistogram.ColorEachPoint:=true;
end;

Destructor TMoneyFlowFunction.Destroy;
begin
  IHistogram.Free;
  IArea.Free;
  IVolume.Free;
  inherited;
end;

procedure TMoneyFlowFunction.Clear;
begin
  inherited;
  IVolume.Clear;
  IArea.Clear;
  IHistogram.Clear;
end;

procedure TMoneyFlowFunction.AddPoints(Source: TChartSeries);

  Procedure PrepareSeries(const ASeries:TChartSeries);
  begin { copy properties from "ParentSeries" to ASeries }
    With ASeries do
    begin
      ParentChart     :=ParentSeries.ParentChart;

      CustomVertAxis  :=ParentSeries.CustomVertAxis;
      VertAxis        :=ParentSeries.VertAxis;
      CustomHorizAxis :=ParentSeries.CustomHorizAxis;
      HorizAxis       :=ParentSeries.HorizAxis;

      XValues.DateTime:=ParentSeries.XValues.DateTime;
      AfterDrawValues :=ParentSeries.AfterDrawValues;
      BeforeDrawValues:=ParentSeries.BeforeDrawValues;
    end;
  end;

  procedure AddPointsTo(const ASeries:TChartSeries);
  var c : Integer;
      tmpColor : TColor;
  begin
    ASeries.BeginUpdate;
    try
      ASeries.Clear;

      for c:=0 to ParentSeries.Count-1 do
      begin
        if ParentSeries.YValues.Value[c] >= 0 then
           tmpColor:=FUpColor
        else
           tmpColor:=FDownColor;

        ASeries.AddXY(ParentSeries.XValue[c],ParentSeries.YValue[c],
                      ParentSeries.Labels[c],tmpColor);
      end;

    finally
      ASeries.EndUpdate;
    end;
  end;

var t,tt,c : Integer;
    tmpHigh  : Double;
    tmpClose : Double;
    tmpLow   : Double;
    CLV      : Double;
    CMF      : Double;
    tmpVolume : Boolean;
    sumAD,sumVol : Double;
    yposLast, xposLast,
    yposNow, xposNow : Double;
    P1,P2,P3 : TPoint;
begin
  tmpVolume:=Assigned(Volume);

  if tmpVolume then
  begin
    with Source as TOHLCSeries do
    for t:=0 to Count-1 do
    begin
      tt := Max(0, (t - Round(Period)));

      sumAD := 0;
      sumVol := 0;

      while tt <= t do
      begin
        tmpHigh:=HighValues.Value[tt];
        tmpClose:=CloseValues.Value[tt];
        tmpLow:=LowValues.Value[tt];

        if (tmpHigh-tmpLow)=0 then
           CLV:=1
        else
           CLV:=( (tmpClose-tmpLow)-(tmpHigh-tmpClose))/(tmpHigh-tmpLow);

        if tmpVolume and (Volume.Count>tt) then
          CLV:=CLV*Volume.MandatoryValueList.Value[tt];

        sumAD := sumAD + CLV;
        if tmpVolume and (Volume.Count>tt) then
          sumVol := sumVol + Volume.MandatoryValueList.Value[tt];

        Inc(tt);
      end;

      if sumVol=0 then
         CMF := sumAD
      else
         CMF := sumAD / sumVol;

      ParentSeries.AddXY(DateValues.Value[t],CMF);
    end;

    Case Ord(CMFStyle) of
      0: begin
           ParentSeries.Visible:=True;
           IVolume.Visible:=False;
           IArea.Visible:=False;
           IHistogram.Visible:=False;
         end;
      1: begin
           PrepareSeries(IArea);
           ParentSeries.Visible:=False;
           IVolume.Visible:=False;
           IHistogram.Visible:=False;
           IArea.Visible:=true;

           IArea.BeginUpdate;
           try
             IArea.Clear;

             // TODO Review polygon is not drawn.
             for c:=0 to ParentSeries.Count-1 do
             begin
               if ParentSeries.YValues.Value[c] >= 0 then
               begin
                  IArea.AddXY(ParentSeries.XValue[c],ParentSeries.YValue[c],
                     ParentSeries.Labels[c],UpColor);

                  if (IArea.YValue[IArea.Count-2] < 0) then
                  begin
                    yposLast :=IArea.CalcYPosValue(IArea.YValue[IArea.Count-2]);
                    xposLast :=IArea.CalcXPosValue(IArea.XValue[IArea.Count-2]);
                    yposNow := 0;
                    xposNow := IArea.XValue[IArea.Count-2] + ((IArea.XValue[IArea.Count-2] -
                       IArea.XValue[IArea.Count-1]) / 2);
                    p1.x := Round(xposLast);
                    p1.y := Round(yposLast);

                    p2.x := Round(xposNow);
                    p2.y := Round(yposNow);

                    p3.x := Round(xposLast);
                    p3.y := Round(yposNow);

                    ParentSeries.ParentChart.Canvas.Brush.Color := UpColor;
                    ParentSeries.ParentChart.Canvas.Polygon([P1,P2,P3]);
                  end;

               end
               else
               begin
                  IArea.AddXY(ParentSeries.XValue[c],ParentSeries.YValue[c],
                     ParentSeries.Labels[c],DownColor);

                  if (IArea.YValue[IArea.Count-2] < 0) then
                  begin
                    yposLast :=IArea.CalcYPosValue(IArea.YValue[IArea.Count-2]);
                    xposLast :=IArea.CalcXPosValue(IArea.XValue[IArea.Count-2]);
                    yposNow := 0;
                    xposNow := IArea.XValue[IArea.Count-2] + ((IArea.XValue[IArea.Count-2] -
                       IArea.XValue[IArea.Count-1]) / 2);
                    p1.x := round(xposLast);
                    p1.y := round(yposLast);

                    p2.x := round(xposNow);
                    p2.y := round(yposNow);

                    p3.x := round(xposLast);
                    p3.y := round(yposNow);

                    ParentSeries.ParentChart.Canvas.Brush.Color := DownColor;
                    ParentSeries.ParentChart.Canvas.Polygon([P1,P2,P3]);
                  end;
               end;
             end;
           finally
             IArea.EndUpdate;
           end;
         end;
      2: begin
           PrepareSeries(IVolume);
           IArea.Visible:=False;
           IHistogram.Visible:=False;
           ParentSeries.Visible:=False;
           IVolume.Visible:=True;
           AddPointsTo(IVolume);
         end;
      3: begin
           PrepareSeries(IHistogram);
           IArea.Visible:=False;
           IHistogram.Visible:=True;
           IVolume.Visible:=False;
           ParentSeries.Visible:=False;
           AddPointsTo(IHistogram);
         end;
    end;
  end;
end;

class function TMoneyFlowFunction.GetEditorClass: String;
begin
  result:='TMoneyFlowFuncEditor'; // Do not localize
end;

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

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

  Chart.AddSeries(TVolumeSeries).FillSampleValues(Chart[0].Count);

  Chart.AddSeries(TFastLineSeries).VertAxis:=aRightAxis;
  Chart.Axes.Left.EndPosition:=70;
  Chart.Axes.Right.StartPosition:=70;

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

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

  ParentSeries:=Chart[2];

  Volume.VertAxis:=ParentSeries.VertAxis;
  Histogram.VertAxis:=ParentSeries.VertAxis;
  Area.VertAxis:=ParentSeries.VertAxis;
end;

procedure TMoneyFlowFunction.SetCMFStyle(const Value: TCMFStyle);
begin
  if FCMFStyle<>Value then
  begin
    FCMFStyle:=Value;
    ReCalculate;
  end;
end;

procedure TMoneyFlowFunction.SetDownColor(const Value: TColor);
begin
  if FDownColor<>Value then
  begin
    FDownColor:=Value;
    ReCalculate;
  end;
end;

procedure TMoneyFlowFunction.SetUpColor(const Value: TColor);
begin
  if FUpColor<>Value then
  begin
    FUpColor:=Value;
    ReCalculate;
  end;
end;

function TMoneyFlowFunction.GetAreaPen: TChartPen;
begin
  result:=Area.Pen;
end;

function TMoneyFlowFunction.GetHistoPen: TChartPen;
begin
  result:=Histogram.Pen;
end;

procedure TMoneyFlowFunction.SetAreaPen(const Value: TChartPen);
begin
  Area.Pen:=Value;
end;

procedure TMoneyFlowFunction.SetHistoPen(const Value: TChartPen);
begin
  Histogram.Pen:=Value;
end;

{ TRVIFunction }

Constructor TRVIFunction.Create(AOwner: TComponent);
begin
  inherited;
  InternalSetPeriod(10);
  SingleSource:=True;
  HideSourceList:=True;

  ISignal:=TFastLineSeries.Create(Self);
  HideSeries(ISignal);
  ISignal.SeriesColor:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};
  ISignal.SetFunction(TMovingAverageFunction.Create(Self));
  ISignal.FunctionType.Period:=4;
  ISignal.DataSource:=TChartSeries.Create(Self);
end;

procedure TRVIFunction.Clear;
begin
  inherited;
  ISignal.Clear;
end;

procedure TRVIFunction.AddPoints(Source: TChartSeries);

  Procedure PrepareSeries(const ASeries:TChartSeries);
  begin
    With ASeries do
    begin
      ParentChart:=ParentSeries.ParentChart;
      CustomVertAxis:=ParentSeries.CustomVertAxis;
      VertAxis:=ParentSeries.VertAxis;
      XValues.DateTime:=ParentSeries.XValues.DateTime;
      AfterDrawValues:=ParentSeries.AfterDrawValues;
      BeforeDrawValues:=ParentSeries.BeforeDrawValues;
    end;
  end;

  Function CalcRVI(Index:Integer):TChartValue;
  var Num :Double;
      Den :Double;
  begin
    With TOHLCSeries(Source) do
    begin
      Num:=CloseValues[index] - OpenValues[index];
      Den:=HighValues[index] - LowValues[index];
    end;

    if Den=0 then Result:=Den
             else Result:=Num/Den;
  end;

var t           : Integer;
    tmpSeries   : TChartSeries;
    tmpSeries2  : TChartSeries;
begin
  if (Period<2) or not IsValidSource(Source) then Exit;

  PrepareSeries(ISignal);

  if Source.Count>=Period then
  begin
    tmpSeries:=ISignal.DataSource as TChartSeries;
    tmpSeries.BeginUpdate;
    try
      tmpSeries.Clear;

      for t:=0 to Source.Count-1 do
          tmpSeries.AddXY(Source.XValues.Value[t], CalcRVI(t));
    finally
      tmpSeries.EndUpdate;
    end;

    tmpSeries2:=TChartSeries.Create(nil);
    try
      //RVI line
      tmpSeries2.SetFunction(TMovingAverageFunction.Create(nil));
      tmpSeries2.FunctionType.Period:=Period;
      ReCalculateSource(tmpSeries2, tmpSeries);

      with tmpSeries2 do
      for t:=0 to Count-1 do
          ParentSeries.AddArray(XValues.Value[t], YValues.Value[t]);
    finally
      tmpSeries2.Free;
    end;
  end;
end;

Destructor TRVIFunction.Destroy;
begin
  {$IFDEF D18}
  ISignal.DataSource.DisposeOf;
  {$ELSE}
  ISignal.DataSource.Free;
  {$ENDIF}

  ISignal.Free;
  inherited;
end;

class function TRVIFunction.GetEditorClass: String;
begin
  result:='TRVIFuncEditor'; // Do not localize
end;

procedure TRVIFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  SplitAxes;
  Signal.VertAxis:=ParentSeries.VertAxis;
end;

function TRVIFunction.GetSignalPen: TChartPen;
begin
  result:=Signal.Pen;
end;

procedure TRVIFunction.SetSignalPen(const Value: TChartPen);
begin
  Signal.Pen:=Value;
end;

{ TAOFunction }

Constructor TAOFunction.Create(AOwner: TComponent);
begin
  inherited;
  InternalSetPeriod(1);
  SingleSource:=True;
  HideSourceList:=True;
end;

procedure TAOFunction.AddPoints(Source: TChartSeries);

  Function MedianPrice(Index:Integer):TChartValue;
  begin
    With TOHLCSeries(Source) do
      Result:=(HighValues.Value[Index] + LowValues.Value[Index]) * 0.5;
  end;

var t           : Integer;
    MedPrice    : TChartSeries;
    ma5         : TChartSeries;
    ma34        : TChartSeries;
    ao          : Double;
    tmpX        : Double;
    col         : TColor;
    i5          : Integer;
    i34         : Integer;
begin
  if (Period<1) or not IsValidSource(Source) then Exit;

  ParentSeries.Color:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};

  if ParentSeries is TVolumeSeries then
    With TVolumeSeries(ParentSeries) do
    begin
      UseYOrigin:=true;
      YOrigin:=0;
    end;

  if Source.Count>=34 then
  begin
    MedPrice:=TChartSeries.Create(nil);

    ma5:=TChartSeries.Create(nil);
    ma5.SetFunction(TMovingAverageFunction.Create(nil));
    ma5.FunctionType.Period:=5;

    ma34:=TChartSeries.Create(nil);
    ma34.SetFunction(TMovingAverageFunction.Create(nil));
    ma34.FunctionType.Period:=34;

    try
      for t:=0 to Source.Count-1 do
          MedPrice.AddXY(Source.XValues.Value[t], MedianPrice(t));

      ReCalculateSource(ma5,MedPrice);
      ReCalculateSource(ma34,MedPrice);

      for t:=0 to Source.Count -1 do
      begin
        tmpX:=Source.XValues.Value[t];
        i5:=ma5.XValues.Locate(tmpX);
        i34:=ma34.XValues.Locate(tmpX);

        if ((i5<>-1) and (i34<>-1)) then
        begin
          ao:=ma5.YValue[i5] - ma34.YValue[i34];

          With ParentSeries do
          begin
            col:=Color;

            if Count>1 then
              if ao > YValue[Count-1] then col:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF}
                                      else col:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF}
            else
              if Count=1 then
              begin
                if ao >= YValue[0] then col:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF}
                                   else col:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};

                ValueColor[0]:=col;
              end;

            AddXY(tmpX, ao, '', col);
          end;
        end;
      end;

    finally
      ma34.Free;
      ma5.Free;
      MedPrice.Free;
    end;
  end;
end;

class function TAOFunction.GetEditorClass: String;
begin
  result:='TRMSFuncEditor'; // Do not localize
end;

Procedure TAOFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  if Chart.SeriesCount>1 then
     Chart[1].{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};

  CreateNewSeries(Chart.Owner,Chart,TVolumeSeries,TAOFunction);
  ParentSeries:=Chart[1];
  ParentSeries.DataSource:=Chart[0];

  SplitAxes;
end;

{ TACFunction }

Constructor TACFunction.Create(AOwner: TComponent);
begin
  inherited;
  InternalSetPeriod(1);
  SingleSource:=True;
  HideSourceList:=True;
end;

procedure TACFunction.AddPoints(Source: TChartSeries);
var t    : Integer;
    ma5  : TChartSeries;
    ao   : TChartSeries;
    tmpX : Double;
    col  : TColor;
    i5   : Integer;
    iao  : Integer;
    ac   : Double;
begin
  if (Period<1) or not IsValidSource(Source) then Exit;

  ParentSeries.Color:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};

  if ParentSeries is TVolumeSeries then
    With TVolumeSeries(ParentSeries) do
    begin
      UseYOrigin:=true;
      YOrigin:=0;
    end;

  if Source.Count>=34 then
  begin
    ao:=TChartSeries.Create(nil);
    ao.SetFunction(TAOFunction.Create(nil));

    ma5:=TChartSeries.Create(nil);
    ma5.SetFunction(TMovingAverageFunction.Create(nil));
    ma5.FunctionType.Period:=5;

    try
      ReCalculateSource(ao,Source);
      ReCalculateSource(ma5,ao);

      for t:=0 to Source.Count -1 do
      begin
        tmpX:=Source.XValues.Value[t];
        i5:=ma5.XValues.Locate(tmpX);
        iao:=ao.XValues.Locate(tmpX);

        if ((i5<>-1) and (iao<>-1)) then
        begin
          ac:=ao.YValue[iao] - ma5.YValue[i5];

          With ParentSeries do
          begin
            col:=Color;

            if Count>1 then
              if ac > YValue[Count-1] then col:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF}
                                      else col:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF}
            else
              if Count=1 then
              begin
                if ac >= YValue[0] then col:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF}
                                   else col:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};

                ValueColor[0]:=col;
              end;

            AddXY(tmpX, ac, '', col);
          end;
        end;
      end;

    finally
      ma5.Free;
      ao.Free;
    end;
  end;
end;

class function TACFunction.GetEditorClass: String;
begin
  result:='TRMSFuncEditor'; // Do not localize
end;

Procedure TACFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  if Chart.SeriesCount>1 then
     Chart[1].{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};

  CreateNewSeries(Chart.Owner,Chart,TVolumeSeries,TACFunction);
  ParentSeries:=Chart[1];
  ParentSeries.DataSource:=Chart[0];

  SplitAxes;
end;

{ TAlligatorFunction }
{ http://ta.mql4.com/indicators/bills/alligator }

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

  SingleSource:=True;
  HideSourceList:=True;

  ITeeth:=TFastLineSeries.Create(Self);
  ITeeth.Color:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};
  HideSeries(ITeeth);

  ILips:=TFastLineSeries.Create(Self);
  ILips.Color:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};
  HideSeries(ILips);
end;

procedure TAlligatorFunction.AddPoints(Source: TChartSeries);

  Procedure PrepareSeries(const ASeries:TChartSeries);
  begin
    With ASeries do
    begin
      ParentChart:=ParentSeries.ParentChart;
      CustomVertAxis:=ParentSeries.CustomVertAxis;
      VertAxis:=ParentSeries.VertAxis;
      XValues.DateTime:=ParentSeries.XValues.DateTime;
      AfterDrawValues:=ParentSeries.AfterDrawValues;
      BeforeDrawValues:=ParentSeries.BeforeDrawValues;
    end;
  end;

  function CreateMedianPrice:TChartSeries;

    function MedianPrice(const AIndex:Integer):TChartValue;
    begin
      with TOHLCSeries(Source) do
           result:=(HighValues.Value[AIndex] + LowValues.Value[AIndex]) * 0.5;
    end;

  var t : Integer;
      tmpOHLC : Boolean;
      tmpValue : TChartValue;
  begin
    result:=TChartSeries.Create(nil);

    tmpOHLC:=Source is TOHLCSeries;

    for t:=0 to Source.Count-1 do
    begin
      if tmpOHLC then
         tmpValue:=MedianPrice(t)
      else
         tmpValue:=Source.MandatoryValueList.Value[t];

      result.AddXY(Source.NotMandatoryValueList.Value[t], tmpValue);
    end
  end;

  Procedure AlligatorLine(const Destination,MedPrice: TChartSeries; P, Shift: Integer);
  var tmpSMMA : TChartSeries;
      i       : Integer;
  begin
    Destination.Clear;

    if Destination <> ParentSeries then
    begin
      PrepareSeries(Destination);
      HideSeries(Destination);
    end;

    tmpSMMA:=TChartSeries.Create(nil);
    try
      tmpSMMA.SetFunction(TSmoothedMovAvgFunction.Create(nil));
      tmpSMMA.FunctionType.Period:=P;
      ReCalculateSource(tmpSMMA, MedPrice);

      for i:=0 to tmpSMMA.Count-1 do
          Destination.AddXY(tmpSMMA.XValues.Value[i] + Shift, tmpSMMA.YValues.Value[i]);
    finally
      tmpSMMA.Free;
    end;
  end;

var MedPrice : TChartSeries;
begin
  ParentSeries.Color:={$IFDEF FMX}claBlue{$ELSE}clBlue{$ENDIF}; // ?

  if Source.Count>=13 then
  begin
    MedPrice:=CreateMedianPrice;
    try
      //ALLIGATORS JAW = SMMA (MEDIAN PRICE, 13, 8)
      AlligatorLine(ParentSeries,MedPrice, 13, 8);

      //ALLIGATORS TEETH = SMMA (MEDIAN PRICE, 8, 5)
      AlligatorLine(ITeeth,MedPrice, 8, 5);

      //ALLIGATORS LIPS = SMMA (MEDIAN PRICE, 5, 3)
      AlligatorLine(ILips,MedPrice, 5, 3);
    finally
      MedPrice.Free;
    end;
  end;
end;

Destructor TAlligatorFunction.Destroy;
begin
  {$IFDEF D18}
  Teeth.DisposeOf;
  Lips.DisposeOf;
  {$ELSE}
  Teeth.Free;
  Lips.Free;
  {$ENDIF}

  inherited;
end;

procedure TAlligatorFunction.Clear;
begin
  inherited;
  ITeeth.Clear;
  ILips.Clear;
end;

class function TAlligatorFunction.GetEditorClass: String;
begin
  result:='TAlligatorFuncEditor'; // Do not localize
end;

function TAlligatorFunction.GetTeethPen: TChartPen;
begin
  result:=Teeth.Pen;
end;

procedure TAlligatorFunction.SetTeethPen(const Value: TChartPen);
begin
  TeethPen:=Value;
end;

function TAlligatorFunction.GetLipsPen: TChartPen;
begin
  result:=Lips.Pen;
end;

procedure TAlligatorFunction.SetLipsPen(const Value: TChartPen);
begin
  LipsPen:=Value;
end;

{ TGatorFunction }
{ http://ta.mql4.com/indicators/bills/gator }

Constructor TGatorFunction.Create(AOwner: TComponent);
begin
  inherited;
  SingleSource:=True;
  HideSourceList:=True;

  IBottom:=TVolumeSeries.Create(Self);
  HideSeries(IBottom);
end;

Destructor TGatorFunction.Destroy;
begin
  IBottom.Free;
  inherited;
end;

procedure TGatorFunction.Clear;
begin
  inherited;
  IBottom.Clear;
end;

procedure TGatorFunction.AddPoints(Source: TChartSeries);

  Procedure PrepareVolume(const ASeries:TChartSeries);
  begin
    if ASeries is TVolumeSeries then
      With TVolumeSeries(ASeries) do
      begin
        UseYOrigin:=True;
        YOrigin:=0;
      end;
  end;

  Procedure PrepareSeries(const ASeries:TChartSeries);
  begin
    With ASeries do
    begin
      ParentChart:=ParentSeries.ParentChart;

      CustomVertAxis:=ParentSeries.CustomVertAxis;
      VertAxis:=ParentSeries.VertAxis;
      CustomHorizAxis:=ParentSeries.CustomHorizAxis;
      HorizAxis:=ParentSeries.HorizAxis;

      XValues.DateTime:=ParentSeries.XValues.DateTime;
      AfterDrawValues:=ParentSeries.AfterDrawValues;
      BeforeDrawValues:=ParentSeries.BeforeDrawValues;
      Color:=ParentSeries.Color;
    end;

    PrepareVolume(ASeries);
  end;

  Function CalcColor(ASeries:TChartSeries; const Val: Double; Index: Integer):TColor;
  var col: TColor;
      tmp: Double;
  begin
    col:=ASeries.Color;
    tmp:=Abs(val);

    With ASeries do
      if Count>1 then
        if tmp > Abs(YValue[Count-1]) then col:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF}
                                      else col:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF}
      else
        if Count=1 then
        begin
          if tmp >= Abs(YValue[0]) then col:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF}
                                   else col:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};

          ValueColor[0]:=col;
        end;

    Result:=col;
  end;

var t         : Integer;
    alligator : TChartSeries;
    alliFunc  : TAlligatorFunction;
    valX      : Double;
    valTop    : Double;
    valBottom : Double;
begin
  ParentSeries.Color:={$IFDEF FMX}claGreen{$ELSE}clGreen{$ENDIF};
  PrepareVolume(ParentSeries);

  PrepareSeries(IBottom);

  if Source.Count>=13 then
  begin
    alliFunc:=TAlligatorFunction.Create(nil);
    alligator:=TChartSeries.Create(nil);

    try
      alligator.SetFunction(alliFunc);
      ReCalculateSource(alligator, Source);

      for t:=0 to alligator.Count -1 do
      begin
        valX:=alligator.XValues.Value[t];
        valTop:=Abs(alligator.YValue[t] - alliFunc.Teeth.YValue[t]); //Top = Abs(jaw-teeth)
        valBottom:=-Abs(alliFunc.Teeth.YValue[t] - alliFunc.Lips.YValue[t]); //Bottom = -Abs(teeth-lips)

        ParentSeries.AddXY(valX, valTop, '', CalcColor(ParentSeries, valTop, t));
        IBottom.AddXY(valX, valBottom, '', CalcColor(IBottom, valBottom, t))
      end;

    finally
      alliFunc.Free;
      alligator.Free;
    end;
  end;
end;

Procedure TGatorFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  if Chart.SeriesCount>1 then
     Chart[1].{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};
     
  CreateNewSeries(Chart.Owner,Chart,TVolumeSeries,TGatorFunction);

  ParentSeries:=Chart[1];
  ParentSeries.DataSource:=Chart[0];

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

{ TSkewnessFunction }

Function TSkewnessFunction.Distrib(Series: TChartSeries; Exponent:Integer):Double;

  function SumDifs:Double;
  var t       : Integer;
      tmpMean : Double;
      tmp     : Double;
  begin
    result:=0;

    with Series,MandatoryValueList do
    if Count>0 then
       tmpMean:=Total/Count
    else
       tmpMean:=0;

    for t:=0 to Series.Count-1 do
    begin
      tmp:=(Series.MandatoryValueList.Value[t]-tmpMean);
      result:=result+Power(tmp,Exponent);
    end;
  end;

var tmpStdDeviation: Double;
begin
  with TStdDeviationFunction.Create(nil) do
  try
    tmpStdDeviation:=Calculate(Series,-1,-1);
  finally
    Free;
  end;

  result:=(Pred(Series.Count)*Power(tmpStdDeviation,Exponent));

  if result<>0 then
     result:=SumDifs/result;
end;

function TSkewnessFunction.Calculate(const SourceSeries: TChartSeries;
  FirstIndex, LastIndex: Integer): Double;
begin
  result:=Distrib(SourceSeries, 3);
end;

class function TSkewnessFunction.GetEditorClass: String;
begin
  result:='TRMSFuncEditor'; // Do not localize
end;

procedure TSkewnessFunction.SetComplete(Value: Boolean);
begin
  if FComplete<>Value then
  begin
    FComplete:=Value;
    Recalculate;
  end;
end;

procedure TSkewnessFunction.PrepareForGallery(const Chart: TCustomAxisPanel);
begin
  inherited;
  ParentSeries.VertAxis:=aRightAxis;
end;

{ TKurtosisFunction }

function TKurtosisFunction.Calculate(const SourceSeries: TChartSeries;
  FirstIndex, LastIndex: Integer): Double;
begin
  result:=Distrib(SourceSeries, 4);
end;

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

procedure TCLVFunction.AddPoints(Source: TChartSeries);
var t : Integer;
    tmpHigh  : Double;
    tmpClose : Double;
    tmpLow   : Double;
    CLV      : Double;
    tmpVolume : Boolean;
begin
  if not (Source is TOHLCSeries) then Exit;

  tmpVolume:=Assigned(FVolume);

  ParentSeries.Clear;

  with TOHLCSeries(Source) do
  for t:=0 to Count-1 do
  begin
    tmpHigh:=HighValues.Value[t];
    tmpClose:=CloseValues.Value[t];
    tmpLow:=LowValues.Value[t];

    if (tmpHigh-tmpLow)=0 then
       CLV:=1
    else
       CLV:=( (tmpClose-tmpLow)-(tmpHigh-tmpClose))/(tmpHigh-tmpLow);

    if tmpVolume and (FVolume.Count>t) then
       CLV:=CLV*FVolume.MandatoryValueList.Value[t];

    if Accumulate then
       if t>0 then
          CLV:=CLV+ParentSeries.MandatoryValueList.Last;

    ParentSeries.AddXY(DateValues.Value[t],CLV);
  end;
end;

class function TCLVFunction.GetEditorClass: String;
begin
  result:='TCLVFuncEditor'; // Do not localize
end;

procedure TCLVFunction.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 TCLVFunction.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;

  if (Operation=opRemove) and (AComponent=FVolume) then
     Volume:=nil;
end;

procedure TCLVFunction.SetAccumulate(const Value: Boolean);
begin
  if FAccumulate<>Value then
  begin
    FAccumulate:=Value;
    ReCalculate;
  end;
end;

procedure TCLVFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  SplitAxes;
end;

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

  FPercent:=True;

  FExpSeries1:=TChartSeries.Create(nil);
  FExpSeries2:=TChartSeries.Create(nil);

  FExpMovAve1:=TExpMovAveFunction.Create(nil);
  FExpMovAve1.Period:=Period;
  FExpMovAve1.ParentSeries:=FExpSeries1;
  FExpMovAve2:=TExpMovAveFunction.Create(nil);
  FExpMovAve2.Period:=26;
  FExpMovAve2.ParentSeries:=FExpSeries2;
end;

Destructor TPVOFunction.Destroy;
begin
  FExpSeries2.Free;
  FExpSeries1.Free;
  inherited;
end;

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

  if Period>0 then
  begin
    FExpMovAve1.Period:=Period;
    FExpMovAve1.AddPoints(Source);
    FExpMovAve2.AddPoints(Source);

    for t:=0 to Source.Count-1 do
    begin
      tmp:=FExpSeries1.MandatoryValueList.Value[t];

      if FPercent then
      begin
        if tmp<>0 then
           tmp:=(tmp-FExpSeries2.MandatoryValueList.Value[t])/tmp
        else
           tmp:=FExpSeries2.MandatoryValueList.Value[t];
      end
      else tmp:=tmp-FExpSeries2.MandatoryValueList.Value[t];

      ParentSeries.AddXY(Source.NotMandatoryValueList.Value[t],100.0*tmp);
    end;
  end;
end;

class function TPVOFunction.GetEditorClass: String;
begin
  result:='TPVOFuncEditor';
end;

procedure TPVOFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  SplitAxes;
  ParentSeries.GetVertAxis.SetMinMax(-100,100);
  ParentSeries.GetVertAxis.AxisValuesFormat:='0%';
end;

function TPVOFunction.GetPeriod2: Integer;
begin
  result:=Round(FExpMovAve2.Period);
end;

procedure TPVOFunction.SetPeriod2(const Value: Integer);
begin
  FExpMovAve2.Period:=Value;
  Recalculate;
end;

procedure TPVOFunction.SetPercent(const Value: Boolean);
begin
  FPercent:=Value;
  Recalculate;
end;

{ TCCIFunction }
Constructor TCCIFunction.Create(AOwner: TComponent);
begin
  inherited;
  InternalSetPeriod(20);
  CanUsePeriod:=True;
  SingleSource:=True;
  HideSourceList:=True;
  FConstant:=0.015;

  FTypical:=TChartSeries.Create(nil);
  FMovAve:=TMovingAverageFunction.Create(nil);
  FAveSeries:=TChartSeries.Create(nil);
  FAveSeries.SetFunction(FMovAve);
end;

Destructor TCCIFunction.Destroy;
begin
  FAveSeries.Free;
  FTypical.Free;
  inherited;
end;

procedure TCCIFunction.AddPoints(Source: TChartSeries);
var tmpPeriod : Integer;

  Function MeanDeviation(Index:Integer):TChartValue;
  var tmpTotal : Double;
      t        : Integer;
  begin
    tmpTotal:=0;

    for t:=Index-tmpPeriod+1 to Index do
        tmpTotal:=tmpTotal+Abs(FAveSeries.MandatoryValueList.Value[Index-tmpPeriod+1]-
                               FTypical.MandatoryValueList.Value[t]);

    result:=tmpTotal/tmpPeriod;
  end;

  function AverageValue(const AIndex:Integer):TChartValue;
  const Third=1/3.0;
  begin
    with TOHLCSeries(Source) do
         result:=(HighValues.Value[AIndex]+
                  LowValues.Value[AIndex]+
                  CloseValues.Value[AIndex]
                 )*Third;
  end;

  Procedure CalculateMovAve;
  var t : Integer;
  begin
    FMovAve.Period:=tmpPeriod;

    FTypical.BeginUpdate;
    try
      FTypical.Clear;

      if Source is TOHLCSeries then
         for t:=0 to Source.Count-1 do
             FTypical.Add(AverageValue(t))
      else
         for t:=0 to Source.Count-1 do
             FTypical.Add(Source.MandatoryValueList.Value[t]);

    finally
      FTypical.EndUpdate;
    end;

    FAveSeries.BeginUpdate;
    try
      FAveSeries.Clear;

      for t:=tmpPeriod-1 to FTypical.Count-1 do
          FAveSeries.Add(FMovAve.Calculate(FTypical,t-tmpPeriod+1,t));
    finally
      FAveSeries.EndUpdate;
    end;
  end;

var t   : Integer;
    tmp : Double;
    tmpConstant : Double;
    tmpMean     : Double;
begin
  ParentSeries.Clear;

  tmpPeriod:=Round(Period);
  if (tmpPeriod>0) and (Source.Count >= tmpPeriod) then
  begin
    CalculateMovAve;

    tmpConstant:=Constant;

    if tmpConstant=0 then
       tmpConstant:=1;

    for t:=tmpPeriod-1 to Source.Count-1 do
    begin
      tmpMean:=MeanDeviation(t);

      if tmpMean<>0 then // Skip zero mean deviation
      begin
        tmp:=( FTypical.MandatoryValueList.Value[t]-
               FAveSeries.MandatoryValueList.Value[t-tmpPeriod+1] ) /
                  ( tmpConstant*tmpMean );

        ParentSeries.AddXY(Source.NotMandatoryValueList.Value[t],tmp);
      end;
    end;
  end;
end;

class function TCCIFunction.GetEditorClass: String;
begin
  result:='TCCIFuncEditor'; // Do not localize
end;

procedure TCCIFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;
  SplitAxes;
end;

function TCCIFunction.IsConstStored: Boolean;
begin
  result:=FConstant<>0.015;
end;

procedure TCCIFunction.SetConstant(const Value: Double);
begin
  if FConstant<>Value then
  begin
    FConstant := Value;
    Recalculate;
  end;
end;

{ TCentroidTeeFunction }

class function TCentroidTeeFunction.GallerySource:TChartSeriesClass;
begin
  result:=TPointSeries;
end;

procedure TCentroidTeeFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
var tmp : TChartSeries;
    t   : Integer;
begin
  inherited;

  tmp:=ParentSeries;
  ChangeSeriesType(tmp,TPointSeries);
  ParentSeries:=tmp;

  TPointSeries(ParentSeries).Pointer.Style:=psCircle;
  TPointSeries(ParentSeries).Pointer.Size:=8;

  tmp:=Chart[Max(0,Chart.SeriesCount-2)];

  tmp.BeginUpdate;

  tmp.Clear;

  for t:=0 to 99 do
      tmp.AddXY(Random(1000),Random(1000));

  tmp.EndUpdate;

  tmp.GetHorizAxis.SetMinMax(tmp.XValues.MinValue-100,tmp.XValues.MaxValue+100);
  tmp.GetVertAxis.SetMinMax(tmp.YValues.MinValue-100,tmp.YValues.MaxValue+100);

  TPointSeries(tmp).Pointer.Size:=3;
  TPointSeries(tmp).Pointer.Pen.Hide;
  TPointSeries(tmp).Pointer.Style:=psCircle;
end;

procedure TCentroidTeeFunction.AddPoints(Source:TChartSeries);
var P : TPointFloat;
    X,Y: TChartValueList;
begin
  ParentSeries.Clear;

  if Source.Count>0 then
  begin
    X:=Source.XValues;
    Y:=Source.YValues;

    P.X:=(X.MaxValue+X.MinValue)*0.5;
    P.Y:=(Y.MaxValue+Y.MinValue)*0.5;

    if ParentSeries.YMandatory then ParentSeries.AddXY(P.X,P.Y)
                               else ParentSeries.AddXY(P.Y,P.X);
  end;
end;

{ TKDEFunction }

procedure TKDEFunction.AddPoints(Source: TChartSeries);
begin
  //inherited;
  FSourceValues :=  Source.MandatoryValueList;
end;

procedure TKDEFunction.AddListPoints(Source: TChartValueList);
begin
  FSourceValues :=  Source;
end;

procedure TKDEFunction.CalcClampRange;
begin
  if ParentSeries <> nil then
  Begin
    self.ParentSeries.GetVertAxis.MinimumOffset := 10;
    self.ParentSeries.GetVertAxis.MaximumOffset := 10;
    self.ParentSeries.GetVertAxis.AdjustMaxMin;

    FClampMin:= self.ParentSeries.GetVertAxis.Minimum;
    FClampMax:= self.ParentSeries.GetVertAxis.Maximum;
  End;
  end;

procedure TKDEFunction.CalculateKDE;
begin
  if (assigned(FSourceValues) and (FSourceValues.Count>0)) then
    CalculateValues(FSourceValues);
end;

procedure TKDEFunction.CalculateValues(aValues: TChartValueList);
Var ARange : Double;
    increment, kdeMin, kdeMax : double;
    axLabels: Array of Double;

  procedure BuildSteps;
  var i : Integer;
      tmpVal : Double;

  begin
    FSourceValues.Order := loAscending;
    FSourceValues.Sort;

    if ParentSeries <> nil then
    Begin
      CalcClampRange;
    End
    else
    Begin
      kdeMin := FSourceValues.MinValue;
      kdeMax := FSourceValues.MaxValue;
    End;

    if FOptions.FClamp = kdcManual then
    Begin
      kdeMin := ClampMin;
      kdeMax := ClampMax;
    end
    else
    Begin
      if (FOptions.FClamp = kdcMinMax) then
      Begin
        kdeMin := FSourceValues.MinValue;
        kdeMax := FSourceValues.MaxValue;
      End;
    End;

    ARange := (kdeMax-kdeMin);

    if FOptions.FResolution>0 then
    Begin
      setLength(axLabels, FOptions.FResolution);
      increment := ARange / FOptions.FResolution;
    End;

    tmpVal := kdeMin;
    for i := 0 to Length(axLabels) - 1 do
    Begin
      axLabels[i] := tmpVal;
      tmpVal := tmpVal + increment;
    end;
  end;

begin
 BuildSteps;
 KDEPoints := KernelDensityEstimator(axLabels,FSourceValues);
end;

procedure TKDEFunction.Clear;
begin
  inherited;
  Options := nil;
  Options.FResolution := 100;
  Options.FBandwidth := 20;
  Options.FClamp := kdcMinMax;
  KDEPoints := nil;
end;

constructor TKDEFunction.Create(AOwner: TComponent);
begin
  inherited;

  Options.FResolution := 100;
  Options.FBandwidth := 20;
  Options.FClamp := kdcMinMax;
end;

function TKDEFunction.GetOptions: TKDEOptions;
begin
  if FOptions = nil then
    FOptions := TKDEOptions.Create;

  result:= FOptions;
end;

function TKDEFunction.KernelDensityEstimator(x: array of Double;
  vals: TChartValueList): TFloatPointArray;
  var aX,aY: Double;
      j,jj, counter, hitBucket : Integer;
      tmpResult,tmpResulti : TFloatPointArray;
      tmpList : Array of Double; //TeeValueList;
      buckets : TFloatPointArray;

  function Kernel(u : double) : double;
  begin
    u := u/self.FOptions.FBandwidth;
    if (Abs(u) <= 1) then
      result:= 0.75 * (1 - u * u) / self.FOptions.FBandwidth
    else
      result := 0;
  end;

  function GetNearestValueIndex(const A: array of Double; Value: Double): Integer;
  var
     I : Integer;
     distance, minDistance: Double;
  begin
     Result := -1;
     minDistance := MaxInt;
     for I := Low(A) to High(A) do
     begin
       distance := Abs(Value - A[I]);
       if (distance < minDistance) then
       begin
         minDistance := distance;
         Result := I;
         if (minDistance = 0) then
           Break;
       end;
     end;
  end;


  Begin
    SetLength(tmpList, vals.Count);
    if Options.KDEType = kdtViolin then
      SetLength(tmpResult,Length(x)*2)
    else
    Begin
      SetLength(tmpResult,Length(x));
      SetLength(buckets,Length(x));
    End;
    SetLength(tmpResulti,Length(x));
	  counter := 0;
    if Options.KDEType = kdtViolin then
    Begin
      for j := 0 to Length(x) - 1 do
      Begin
         if Options.KDEType = kdtViolin then
         Begin
           aX:=x[j];
           for jj := 0 to vals.Count-1 do
           Begin
            tmpList[jj] := Kernel(aX - vals[jj]);
           End;
           aY:= Mean(tmpList);
           tmpResult[j].Y := aX;
           tmpResult[j].X := Position-aY;
           tmpResulti[j].Y := aX;
           tmpResulti[j].X := Position+aY;
         End;
         counter := j+1;
      end;
    End
    else
    Begin    //BeeSwarm
       for jj := 0 to vals.Count-1 do
       Begin
         hitBucket := GetNearestValueIndex(x,vals[jj]);

         if hitBucket <> -1 then
         Begin
           buckets[hitbucket].X := buckets[hitbucket].X+1;
           buckets[hitbucket].Y := x[hitBucket];
         End;
       End;
    End;

    if Options.KDEType = kdtViolin then
    Begin
      for j := 0 to length(tmpResulti) do
      Begin
        tmpResult[counter+j].Y := tmpResulti[counter-1-j].Y;
        tmpResult[counter+j].X := tmpResulti[counter-1-j].X;
      End;

      result := tmpResult;
    End
    else
      result:= buckets;

end;

procedure TKDEFunction.SetClampMax(aVal: Double);
begin
  FClampMax := aVal;
end;

procedure TKDEFunction.SetClampMin(aVal: Double);
begin
  FClampMin := aVal;
end;

procedure TKDEFunction.SetPosition(aPos: Double);
begin
  FPosition := aPos;
end;

{ TKDEOptions }

procedure TKDEOptions.SetBandwidth(Value: Integer);
begin
  FBandwidth:= Value;
end;

procedure TKDEOptions.SetClamp(Value: TKDEClamp);
begin
  FClamp:=Value;
end;

procedure TKDEOptions.SetResolution(Value: Integer);
begin
  FResolution:= Value;
end;

procedure TKDEOptions.SetKDEType(Value: TKDEType);
begin
  FKDEType:= Value;
end;

procedure TKDEOptions.SetWidth(Value: Integer);
begin
  FWidth:= Value;
end;

initialization
  RegisterTeeFunction( TMovingAverageFunction, @TeeMsg_FunctionMovingAverage,
                                               @TeeMsg_GalleryMath );
  RegisterTeeFunction( TExpMovAveFunction,     @TeeMsg_FunctionExpMovAve,
                                               @TeeMsg_GalleryMath );
  RegisterTeeFunction( TExpAverageFunction,    @TeeMsg_FunctionExpAverage,
                                               @TeeMsg_GalleryMath );
  RegisterTeeFunction( TSmoothedMovAvgFunction,@TeeMsg_FunctionSMMA,
                                               @TeeMsg_GalleryMath );
  RegisterTeeFunction( TMomentumFunction,      @TeeMsg_FunctionMomentum,
                                               @TeeMsg_GalleryMath  );
  RegisterTeeFunction( TMomentumDivFunction,   @TeeMsg_FunctionMomentumDiv,
                                               @TeeMsg_GalleryMath  );
  RegisterTeeFunction( TStdDeviationFunction,  @TeeMsg_FunctionStdDeviation,
                                               @TeeMsg_GalleryStats  );
  RegisterTeeFunction( TRMSFunction,           @TeeMsg_FunctionRMS,
                                               @TeeMsg_GalleryStats );
  RegisterTeeFunction( TSkewnessFunction,      @TeeMsg_FunctionSkewness,
                                               @TeeMsg_GalleryStats  );
  RegisterTeeFunction( TKurtosisFunction,      @TeeMsg_FunctionKurtosis,
                                               @TeeMsg_GalleryStats  );
  RegisterTeeFunction( TMACDFunction,          @TeeMsg_FunctionMACD,
                                               @TeeMsg_GalleryFinancial  );
  RegisterTeeFunction( TMoneyFlowFunction,     @TeeMsg_FunctionMoneyFlow,
                                               @TeeMsg_GalleryFinancial  );
  RegisterTeeFunction( TRVIFunction,           @TeeMsg_FunctionRVI,
                                               @TeeMsg_GalleryFinancial  );
  RegisterTeeFunction( TAOFunction,            @TeeMsg_FunctionAO,
                                               @TeeMsg_GalleryFinancial  );
  RegisterTeeFunction( TACFunction,            @TeeMsg_FunctionAC,
                                               @TeeMsg_GalleryFinancial  );
  RegisterTeeFunction( TAlligatorFunction,     @TeeMsg_FunctionAlligator,
                                               @TeeMsg_GalleryFinancial  );
  RegisterTeeFunction( TGatorFunction,         @TeeMsg_FunctionGator,
                                               @TeeMsg_GalleryFinancial  );
  RegisterTeeFunction( TStochasticFunction,    @TeeMsg_FunctionStochastic,
                                               @TeeMsg_GalleryFinancial  );
  RegisterTeeFunction( TBollingerFunction,     @TeeMsg_FunctionBollinger,
                                               @TeeMsg_GalleryFinancial );
  RegisterTeeFunction( TCrossPointsFunction,   @TeeMsg_FunctionCross,
                                               @TeeMsg_GalleryExtended );
  RegisterTeeFunction( TPerformanceFunction,   @TeeMsg_FunctionPerf,
                                               @TeeMsg_GalleryStats );
  RegisterTeeFunction( TVarianceFunction,      @TeeMsg_FunctionVariance,
                                               @TeeMsg_GalleryStats );
  RegisterTeeFunction( TPerimeterFunction,     @TeeMsg_FunctionPerimeter,
                                               @TeeMsg_GalleryExtended );
  RegisterTeeFunction( TPercentChangeFunction, @TeeMsg_FunctionPercentage,
                                               @TeeMsg_GalleryStats );
  RegisterTeeFunction( TSlopeFunction,         @TeeMsg_FunctionSlope,
                                               @TeeMsg_GalleryStats );

  RegisterTeeSeries( THistogramSeries,         @TeeMsg_GalleryHistogram,
                                               @TeeMsg_GalleryStats,1 );
  RegisterTeeSeries( THorizHistogramSeries,    @TeeMsg_GalleryHorizHistogram,
                                               @TeeMsg_GalleryStats,1 );
  RegisterTeeFunction( TCLVFunction,           @TeeMsg_FunctionCLV,
                                               @TeeMsg_GalleryFinancial );
  RegisterTeeFunction( TPVOFunction,           @TeeMsg_FunctionPVO,
                                               @TeeMsg_GalleryFinancial );
  RegisterTeeFunction( TCCIFunction,           @TeeMsg_FunctionCCI,
                                               @TeeMsg_GalleryFinancial );

  RegisterTeeFunction( TCentroidTeeFunction,   @TeeMsg_FunctionCentroid,
                                               @TeeMsg_GalleryStats, 1);

finalization
  UnRegisterTeeFunctions([ TMovingAverageFunction,
                           TExpMovAveFunction,
                           TExpAverageFunction,
                           TSmoothedMovAvgFunction,
                           TMomentumFunction,
                           TMomentumDivFunction,
                           TStdDeviationFunction,
                           TRMSFunction,
                           TSkewnessFunction,
                           TKurtosisFunction,
                           TMACDFunction,
                           TMoneyFlowFunction,
                           TRVIFunction,
                           TAOFunction,
                           TACFunction,
                           TAlligatorFunction,
                           TGatorFunction,
                           TStochasticFunction,
                           TBollingerFunction,
                           TCrossPointsFunction,
                           TPerformanceFunction,
                           TVarianceFunction,
                           TPerimeterFunction,
                           TPercentChangeFunction,
                           TSlopeFunction,
                           TCLVFunction,
                           TPVOFunction,
                           TCCIFunction,
                           TCentroidTeeFunction ]);

  UnRegisterTeeSeries([THistogramSeries, THorizHistogramSeries]);
end.
