Unit GeometricPatterns; Interface Implementation Uses DataNodes, VolumeWeightedData, VolumeWeightedDataNodes, Prices, GenericDataNodes, AlertBase, StandardPlaceHolders, VolumeBlockMinAndMax, Classes, Math, SysUtils; //////////////////////////////////////////////////////////////////////// // TGeometricPattern //////////////////////////////////////////////////////////////////////// Type TPatternDescription = Record Epoch : Integer; // This changes every time the pattern changes. This // value is valid even if the pattern as a whole is // not. Valid : Boolean; // We have seen a pattern of the expected type, // and its description is listed below. If this // is false, then nothing below is meaningful. Active : Boolean; // Starting from the right and going backward you // can see this pattern. No stray points exist to // the right of this pattern. Up : Boolean; // The pattern should be described in green. For // triangles this means that the longest line goes up. Prices : Array Of Double; // In order from left to right. StartTime, EndTime : TDateTime; // Rough estimates. These go from // the start of the first bar to the // end of the last bar. Quality : Double // The number of bars / 4. Roughtly, the number of // hours. End; TTGeometricPattern = Class Of TGeometricPattern; // This class wakes the listener every time the underlying volume bars // data node wakes it. The pattern may or may not have changed. It might // (unfortunately) have an extra wakeup or so durring initialization. TGeometricPattern = Class(TDataNodeWithStringKey) Private PreviousPointCount : Integer; PointsData : TVolumeBlockMinAndMax; LastEpoch : Integer; Procedure PossibleNewData; Protected FDescription : TPatternDescription; Constructor Create(Symbol : String); Virtual; Class Function CreateNew(Data : String) : TDataNodeWithStringKey; Override; Procedure NewPoint(LastPoint : Integer); Virtual; Abstract; Procedure NewEpoch; Public Class Procedure Find(Pattern : TTGeometricPattern; Symbol : String; OnChange : TThreadMethod; Out Node : TGeometricPattern; Out Link : TDataNodeLink); Property Description : TPatternDescription Read FDescription; Function GetPriceList : String; Procedure AfterConstruction; Override; End; Class Function TGeometricPattern.CreateNew(Data : String) : TDataNodeWithStringKey; Begin Result := Create(Data) End; Procedure TGeometricPattern.NewEpoch; Begin Inc(LastEpoch); FDescription.Epoch := LastEpoch End; Procedure TGeometricPattern.PossibleNewData; Var NewPointCount : Integer; Begin NewPointCount := Length(PointsData.TurningPoints); If NewPointCount < PreviousPointCount Then Begin FDescription.Valid := False; PreviousPointCount := 0 End Else While PreviousPointCount < NewPointCount Do Begin NewPoint(PreviousPointCount); Inc(PreviousPointCount) End; NotifyListeners End; Constructor TGeometricPattern.Create(Symbol : String); Var Link : TDataNodeLink; Begin Inherited Create; TVolumeBlockMinAndMax.Find(Symbol, PossibleNewData, PointsData, Link); AddAutoLink(Link); End; Procedure TGeometricPattern.AfterConstruction; Begin Inherited; DoInCorrectThread(PossibleNewData) End; Class Procedure TGeometricPattern.Find(Pattern : TTGeometricPattern; Symbol : String; OnChange : TThreadMethod; Out Node : TGeometricPattern; Out Link : TDataNodeLink); Var TempNode : TDataNodeWithStringKey; Begin Pattern.FindCommon(Pattern, Symbol, OnChange, TempNode, Link); Node := TempNode As Pattern End; Function TGeometricPattern.GetPriceList : String; Var I : Integer; Highest, Lowest : Double; Begin If FDescription.Valid Then If Length(FDescription.Prices) > 10 Then Begin Highest := FDescription.Prices[0]; Lowest := Highest; For I := 1 To High(FDescription.Prices) Do Begin Highest := Max(FDescription.Prices[I], Highest); Lowest := Min(FDescription.Prices[I], Lowest) End; Result := FormatPrice(Lowest) + ' - ' + FormatPrice(Highest) End Else If Length(FDescription.Prices) > 0 Then Begin Result := FormatPrice(FDescription.Prices[0]); For I := 1 To High(FDescription.Prices) Do Result := Result + ', ' + FormatPrice(FDescription.Prices[I]) End Else Result := '' End; //////////////////////////////////////////////////////////////////////// // TNarrowingTrianglePattern //////////////////////////////////////////////////////////////////////// Type TNarrowingTrianglePattern = Class(TGeometricPattern) Private Protected Procedure NewPoint(LastPoint : Integer); Override; Public End; Procedure TNarrowingTrianglePattern.NewPoint(LastPoint : Integer); Var HighPrice, LowPrice : Double; FirstPoint, I : Integer; Points : TVBMMTurningPoints; VolumeBlocks : TVolumeBlocks; Continues : Boolean; Begin SetLength(VolumeBlocks, 0); // Silly compiler warning. FDescription.Active := False; If LastPoint >= 4 Then Begin Points := PointsData.TurningPoints; If Points[LastPoint].Orientation = vboHigh Then Begin HighPrice := Points[LastPoint].Price; LowPrice := Points[Pred(LastPoint)].Price End Else Begin LowPrice := Points[LastPoint].Price; HighPrice := Points[Pred(LastPoint)].Price End; FirstPoint := LastPoint; // Silly compiler warning. For I := LastPoint - 2 DownTo 0 Do Begin Continues := False; With Points[I] Do If Orientation = vboHigh Then Begin If Price > HighPrice Then Begin HighPrice := Price; Continues := True End End Else Begin If Price < LowPrice Then Begin LowPrice := Price; Continues := True End End; If Continues Then FirstPoint := I Else Break End; If (LastPoint - FirstPoint) >= 4 Then Begin NewEpoch; FDescription.Active := True; FDescription.Valid := True; FDescription.Up := Points[FirstPoint].Orientation = vboLow; SetLength(FDescription.Prices, Succ(LastPoint - FirstPoint)); For I := FirstPoint To LastPoint Do FDescription.Prices[I - FirstPoint] := Points[I].Price; VolumeBlocks := PointsData.AllBlocks; FDescription.StartTime := VolumeBlocks[Points[FirstPoint].Index].StartTime; FDescription.EndTime := VolumeBlocks[Points[LastPoint].Index].EndTime; FDescription.Quality := Succ(Points[LastPoint].Index - Points[FirstPoint].Index) / 4.0 End End Else SetLength(Points, 0) // Silly compiler warning. End; //////////////////////////////////////////////////////////////////////// // TBroadeningTrianglePattern //////////////////////////////////////////////////////////////////////// Type TBroadeningTrianglePattern = Class(TGeometricPattern) Private Protected Procedure NewPoint(LastPoint : Integer); Override; Public End; Procedure TBroadeningTrianglePattern.NewPoint(LastPoint : Integer); Var HighPrice, LowPrice : Double; FirstPoint, I : Integer; Points : TVBMMTurningPoints; VolumeBlocks : TVolumeBlocks; Continues : Boolean; Begin SetLength(VolumeBlocks, 0); // Silly compiler warning. FDescription.Active := False; If LastPoint >= 4 Then Begin Points := PointsData.TurningPoints; If Points[LastPoint].Orientation = vboHigh Then Begin HighPrice := Points[LastPoint].Price; LowPrice := Points[Pred(LastPoint)].Price End Else Begin LowPrice := Points[LastPoint].Price; HighPrice := Points[Pred(LastPoint)].Price End; FirstPoint := LastPoint; // Silly compiler warning. For I := LastPoint - 2 DownTo 0 Do Begin Continues := False; With Points[I] Do If Orientation = vboHigh Then Begin If Price < HighPrice Then Begin HighPrice := Price; Continues := True End End Else Begin If Price > LowPrice Then Begin LowPrice := Price; Continues := True End End; If Continues Then FirstPoint := I Else Break End; If (LastPoint - FirstPoint) >= 4 Then Begin NewEpoch; FDescription.Active := True; FDescription.Valid := True; FDescription.Up := Points[LastPoint].Orientation = vboLow; SetLength(FDescription.Prices, Succ(LastPoint - FirstPoint)); For I := FirstPoint To LastPoint Do FDescription.Prices[I - FirstPoint] := Points[I].Price; VolumeBlocks := PointsData.AllBlocks; FDescription.StartTime := VolumeBlocks[Points[FirstPoint].Index].StartTime; FDescription.EndTime := VolumeBlocks[Points[LastPoint].Index].EndTime; FDescription.Quality := Succ(Points[LastPoint].Index - Points[FirstPoint].Index) / 4.0 End End Else SetLength(Points, 0) // Silly compiler warning. End; //////////////////////////////////////////////////////////////////////// // TPatternWithEquals //////////////////////////////////////////////////////////////////////// Type TPatternWithEquals = Class(TGeometricPattern) Private VolatilityData : TGenericDataNode; Protected Function GetMarginOfError(Bars : Integer) : Double; Constructor Create(Symbol : String); Override; Public End; Constructor TPatternWithEquals.Create(Symbol : String); Var Factory : IGenericDataNodeFactory; Link : TDataNodeLink; Begin Inherited; Factory := TGenericDataNodeFactory.FindFactory('TickVolatility').Duplicate; Factory.SetValue(SymbolNameKey, Symbol); Factory.Find(Nil, VolatilityData, Link); AddAutoLink(Link) End; Function TPatternWithEquals.GetMarginOfError(Bars : Integer) : Double; Begin If VolatilityData.IsValid Then Result := VolatilityData.GetDouble * Sqrt(Bars) / Sqrt(44) Else Result := 0 End; //////////////////////////////////////////////////////////////////////// // TRectanglePattern //////////////////////////////////////////////////////////////////////// Type TRectanglePattern = Class(TPatternWithEquals) Private Protected Procedure NewPoint(LastPoint : Integer); Override; Public End; Procedure TRectanglePattern.NewPoint(LastPoint : Integer); Var HighestHigh, LowestHigh, HighestLow, LowestLow : Double; CurrentMarginOfError : Double; FirstPoint, I : Integer; Points : TVBMMTurningPoints; VolumeBlocks : TVolumeBlocks; Continues : Boolean; Begin SetLength(VolumeBlocks, 0); // Silly compiler warning. FDescription.Active := False; If LastPoint >= 4 Then Begin Points := PointsData.TurningPoints; VolumeBlocks := PointsData.AllBlocks; If Points[LastPoint].Orientation = vboHigh Then Begin HighestHigh := Points[LastPoint].Price; LowestLow := Points[Pred(LastPoint)].Price End Else Begin LowestLow := Points[LastPoint].Price; HighestHigh := Points[Pred(LastPoint)].Price End; LowestHigh := HighestHigh; HighestLow := LowestLow; FirstPoint := LastPoint; // Silly compiler warning. For I := LastPoint - 2 DownTo 0 Do Begin Continues := False; CurrentMarginOfError := GetMarginOfError(Points[LastPoint].Index - Points[I].Index); With Points[I] Do If Orientation = vboHigh Then Begin If (Price > HighestLow) And (Price - LowestHigh <= CurrentMarginOfError) And (HighestHigh - Price <= CurrentMarginOfError) Then Begin HighestHigh := Max(HighestHigh, Price); LowestHigh := Min(LowestHigh, Price); Continues := True End End Else Begin If (Price < LowestHigh) And (Price - LowestLow <= CurrentMarginOfError) And (HighestLow - Price <= CurrentMarginOfError) Then Begin HighestLow := Max(HighestLow, Price); LowestLow := Min(LowestLow, Price); Continues := True End End; If Continues Then If (LowestHigh - HighestLow) < Max(HighestLow - LowestLow, HighestHigh - LowestHigh) Then Continues := False; If Continues Then FirstPoint := I Else Break End; If (LastPoint - FirstPoint) >= 4 Then Begin NewEpoch; FDescription.Active := True; FDescription.Valid := True; FDescription.Up := Points[LastPoint].Orientation = vboLow; SetLength(FDescription.Prices, Succ(LastPoint - FirstPoint)); For I := FirstPoint To LastPoint Do FDescription.Prices[I - FirstPoint] := Points[I].Price; FDescription.StartTime := VolumeBlocks[Points[FirstPoint].Index].StartTime; FDescription.EndTime := VolumeBlocks[Points[LastPoint].Index].EndTime; FDescription.Quality := Succ(Points[LastPoint].Index - Points[FirstPoint].Index) / 4.0 End End Else SetLength(Points, 0) // Silly compiler warning. End; //////////////////////////////////////////////////////////////////////// // THeadAndShouldersPattern //////////////////////////////////////////////////////////////////////// Type THeadAndShouldersPattern = Class(TPatternWithEquals) Private Protected Procedure NewPoint(LastPoint : Integer); Override; Public End; Procedure THeadAndShouldersPattern.NewPoint(LastPoint : Integer); Var MarginOfError : Double; Points : TVBMMTurningPoints; VolumeBlocks : TVolumeBlocks; I : Integer; Valid : Boolean; Begin SetLength(VolumeBlocks, 0); // Silly compiler warning. FDescription.Active := False; Valid := False; If LastPoint >= 4 Then Begin Points := PointsData.TurningPoints; MarginOfError := GetMarginOfError(Points[LastPoint].Index - Points[LastPoint - 4].Index); If (Abs(Points[LastPoint].Price - Points[LastPoint - 4].Price) < MarginOfError) And (Abs(Points[Pred(LastPoint)].Price - Points[LastPoint - 3].Price) < MarginOfError) Then If Points[LastPoint].Orientation = vboHigh Then Valid := Points[LastPoint - 2].Price > MarginOfError + Max(Points[LastPoint].Price, Points[LastPoint - 4].Price) Else Valid := Points[LastPoint - 2].Price < Min(Points[LastPoint].Price, Points[LastPoint - 4].Price) - MarginOfError; If Valid Then Begin NewEpoch; FDescription.Active := True; FDescription.Valid := True; FDescription.Up := Points[LastPoint].Orientation = vboHigh; SetLength(FDescription.Prices, 5); For I := 0 To 4 Do FDescription.Prices[I] := Points[I + LastPoint - 4].Price; VolumeBlocks := PointsData.AllBlocks; FDescription.StartTime := VolumeBlocks[Points[LastPoint - 4].Index].StartTime; FDescription.EndTime := VolumeBlocks[Points[LastPoint].Index].EndTime; FDescription.Quality := Succ(Points[LastPoint].Index - Points[LastPoint - 4].Index) / 4.0 End End Else SetLength(Points, 0) // Silly compiler warning. End; //////////////////////////////////////////////////////////////////////// // TSimpleGeometricPatternAlert //////////////////////////////////////////////////////////////////////// Type TSimpleGeometricPatternType = (sgpTriangle, sgpBroadening, sgpRectangle, sgpHeadAndShoulders); TSimpleGeometricPatternAlert = Class(TAlert) Protected Constructor Create(Params : TParamList); Override; Private Pattern : TGeometricPattern; FPatternName : String; FUp : Boolean; Initialized : Boolean; LastEpoch : Integer; Procedure NewData; Published End; Var TriangleBottomName : String = 'Triangle bottom'; TriangleTopName : String = 'Triangle top'; BroadeningBottomName : String = 'Broadening bottom'; BroadeningTopName : String = 'Broadening top'; RectangleBottomName : String = 'Rectangle bottom'; RectangleTopName : String = 'Rectangle top'; HeadAndShouldersName : String = 'Head and shoulders'; InvertedHeadAndShouldersName : String = 'Inverted head and shoulders'; Constructor TSimpleGeometricPatternAlert.Create(Params : TParamList); Var Symbol : String; PatternType : TSimpleGeometricPatternType; PatternClass : TTGeometricPattern; Link : TDataNodeLink; Begin Assert(Length(Params)=3, 'Expected args: Symbol, type, up)'); Symbol := Params[0]; PatternType := Params[1]; FUp := Params[2]; Case PatternType Of sgpTriangle : Begin PatternClass := TNarrowingTrianglePattern; If FUp Then FPatternName := 'Triangle bottom' Else FPatternName := 'Triangle top' End; sgpBroadening : Begin PatternClass := TBroadeningTrianglePattern; If FUp Then FPatternName := 'Broadening bottom' Else FPatternName := 'Broadening top' End; sgpRectangle : Begin PatternClass := TRectanglePattern; If FUp Then FPatternName := 'Rectangle bottom' Else FPatternName := 'Rectangle top' End; sgpHeadAndShoulders : Begin PatternClass := THeadAndShouldersPattern; If FUp Then FPatternName := 'Head and shoulders' Else FPatternName := 'Inverted head and shoulders' End Else Raise Exception.CreateFmt('Invalid pattern type: %d', [Ord(PatternType)]) End; Inherited Create; TGeometricPattern.Find(PatternClass, Symbol, NewData, Pattern, Link); AddAutoLink(Link); DoInCorrectThread(NewData) End; Procedure TSimpleGeometricPatternAlert.NewData; Var Msg : String; LastPattern : TPatternDescription; Begin LastPattern := Pattern.Description; With LastPattern Do Begin If Initialized And (Epoch <> LastEpoch) And Valid And (Up = FUp) Then Begin Msg := FPatternName + '. Prices: ' + Pattern.GetPriceList + '. Started ' + DurationString(StartTime, GetSubmitTime) + ' ago. Last turn ' + DurationString(EndTime, GetSubmitTime) + ' ago.'; Report(Msg, Quality) End; Initialized := True; LastEpoch := Epoch End End; //////////////////////////////////////////////////////////////////////// // TDoubleTopResistance //////////////////////////////////////////////////////////////////////// Type TDoubleTopResistance = Class(TPatternWithEquals) Private Protected Procedure NewPoint(LastPoint : Integer); Override; Public End; Procedure TDoubleTopResistance.NewPoint(LastPoint : Integer); Const PreferredOrientation = vboHigh; Var MarginOfError : Double; HighestPossible, LowestPossible : Double; FirstPoint, I, J : Integer; MatchingPointCount : Integer; FirstPointFound : Boolean; Points : TVBMMTurningPoints; VolumeBlocks : TVolumeBlocks; Begin FDescription.Active := False; Points := PointsData.TurningPoints; If (Points[LastPoint].Orientation = PreferredOrientation) And (LastPoint >= 3) Then Begin FirstPointFound := False; VolumeBlocks := PointsData.AllBlocks; FirstPoint := LastPoint; HighestPossible := 0; LowestPossible := 0; MatchingPointCount := 0; For I := 0 To Pred(LastPoint) Do If Points[I].Orientation = PreferredOrientation Then Begin If Not FirstPointFound Then Begin MarginOfError := GetMarginOfError(Points[LastPoint].Index - Points[I].Index); HighestPossible := Points[LastPoint].Price + MarginOfError / 2; LowestPossible := Points[LastPoint].Price - MarginOfError / 2 End; If Points[I].Price > HighestPossible Then FirstPointFound := False Else If Points[I].Price >= LowestPossible Then Begin If FirstPointFound Then Inc(MatchingPointCount) Else Begin FirstPointFound := True; FirstPoint := I; MatchingPointCount := 2 End End End Else If FirstPointFound Then If Points[I].Price >= LowestPossible Then FirstPointFound := False; If FirstPointFound And (Points[LastPoint].Index - Points[FirstPoint].Index >= 21) Then Begin NewEpoch; FDescription.Active := True; FDescription.Valid := True; FDescription.Up := PreferredOrientation = vboHigh; SetLength(FDescription.Prices, MatchingPointCount); J := 0; For I := FirstPoint To LastPoint Do If (Points[I].Orientation = PreferredOrientation) And (Points[I].Price <= HighestPossible) And (Points[I].Price >= LowestPossible) Then Begin FDescription.Prices[J] := Points[I].Price; Inc(J) End; Assert(J = Length(FDescription.Prices)); FDescription.StartTime := VolumeBlocks[Points[FirstPoint].Index].StartTime; FDescription.EndTime := VolumeBlocks[Points[LastPoint].Index].EndTime; FDescription.Quality := Succ(Points[LastPoint].Index - Points[FirstPoint].Index) / 4.0 End End Else SetLength(VolumeBlocks, 0) // Silly compiler warning. End; //////////////////////////////////////////////////////////////////////// // TDoubleBottomSupport //////////////////////////////////////////////////////////////////////// Type TDoubleBottomSupport = Class(TPatternWithEquals) Private Protected Procedure NewPoint(LastPoint : Integer); Override; Public End; Procedure TDoubleBottomSupport.NewPoint(LastPoint : Integer); Const PreferredOrientation = vboLow; Var MarginOfError : Double; HighestPossible, LowestPossible : Double; FirstPoint, I, J : Integer; MatchingPointCount : Integer; FirstPointFound : Boolean; Points : TVBMMTurningPoints; VolumeBlocks : TVolumeBlocks; Begin FDescription.Active := False; Points := PointsData.TurningPoints; If (Points[LastPoint].Orientation = PreferredOrientation) And (LastPoint >= 3) Then Begin FirstPointFound := False; VolumeBlocks := PointsData.AllBlocks; FirstPoint := LastPoint; HighestPossible := 0; LowestPossible := 0; MatchingPointCount := 0; For I := 0 To Pred(LastPoint) Do If Points[I].Orientation = PreferredOrientation Then Begin If Not FirstPointFound Then Begin MarginOfError := GetMarginOfError(Points[LastPoint].Index - Points[I].Index); HighestPossible := Points[LastPoint].Price + MarginOfError / 2; LowestPossible := Points[LastPoint].Price - MarginOfError / 2 End; If Points[I].Price < LowestPossible Then FirstPointFound := False Else If Points[I].Price <= HighestPossible Then Begin If FirstPointFound Then Inc(MatchingPointCount) Else Begin FirstPointFound := True; FirstPoint := I; MatchingPointCount := 2 End End End Else If FirstPointFound Then If Points[I].Price <= HighestPossible Then FirstPointFound := False; If FirstPointFound And (Points[LastPoint].Index - Points[FirstPoint].Index >= 21) Then Begin NewEpoch; FDescription.Active := True; FDescription.Valid := True; FDescription.Up := PreferredOrientation = vboHigh; SetLength(FDescription.Prices, MatchingPointCount); J := 0; For I := FirstPoint To LastPoint Do If (Points[I].Orientation = PreferredOrientation) And (Points[I].Price <= HighestPossible) And (Points[I].Price >= LowestPossible) Then Begin FDescription.Prices[J] := Points[I].Price; Inc(J) End; Assert(J = Length(FDescription.Prices)); FDescription.StartTime := VolumeBlocks[Points[FirstPoint].Index].StartTime; FDescription.EndTime := VolumeBlocks[Points[LastPoint].Index].EndTime; FDescription.Quality := Succ(Points[LastPoint].Index - Points[FirstPoint].Index) / 4.0 End End Else SetLength(VolumeBlocks, 0) // Silly compiler warning. End; //////////////////////////////////////////////////////////////////////// // TDoubleTopAlert //////////////////////////////////////////////////////////////////////// Type TDoubleTopAlert = Class(TAlert) Protected Constructor Create(Params : TParamList); Override; Private Pattern : TGeometricPattern; FUp : Boolean; Initialized : Boolean; LastEpoch : Integer; Procedure NewData; Published End; Constructor TDoubleTopAlert.Create(Params : TParamList); Var Symbol : String; PatternClass : TTGeometricPattern; Link : TDataNodeLink; Begin Assert(Length(Params)=2, 'Expected args: Symbol, up)'); Symbol := Params[0]; FUp := Params[1]; If FUp Then PatternClass := TDoubleTopResistance Else PatternClass := TDoubleBottomSupport; Inherited Create; TGeometricPattern.Find(PatternClass, Symbol, NewData, Pattern, Link); AddAutoLink(Link); DoInCorrectThread(NewData) End; Procedure TDoubleTopAlert.NewData; Var Msg : String; LastPattern : TPatternDescription; Begin LastPattern := Pattern.Description; With LastPattern Do Begin If Initialized And (Epoch <> LastEpoch) And Valid Then Begin Case Length(Prices) Of 2 : Msg := 'Double'; 3 : Msg := 'Triple'; 4 : Msg := 'Quadruple'; Else Msg := Format('%d', [Length(Prices)]) End; If FUp Then Msg := Msg + ' top.' Else Msg := Msg + ' bottom.'; Msg := Msg + ' Prices: ' + Pattern.GetPriceList; Msg := Msg + '. Started ' + DurationString(StartTime, GetSubmitTime) + ' ago. Last turn ' + DurationString(EndTime, GetSubmitTime) + ' ago.'; Report(Msg, Quality) End; Initialized := True; LastEpoch := Epoch End End; //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Procedure AdjustReferenceCount(Var S : String); Begin SetLength(S, Length(S)) End; Initialization AdjustReferenceCount(TriangleBottomName); AdjustReferenceCount(TriangleTopName); AdjustReferenceCount(BroadeningBottomName); AdjustReferenceCount(BroadeningTopName); AdjustReferenceCount(RectangleBottomName); AdjustReferenceCount(RectangleTopName); AdjustReferenceCount(HeadAndShouldersName); AdjustReferenceCount(InvertedHeadAndShouldersName); TGenericDataNodeFactory.StoreFactory('TriangleBottom', TGenericDataNodeFactory.CreateWithArgs(TSimpleGeometricPatternAlert, StandardSymbolPlaceHolder, sgpTriangle, True)); TGenericDataNodeFactory.StoreFactory('TriangleTop', TGenericDataNodeFactory.CreateWithArgs(TSimpleGeometricPatternAlert, StandardSymbolPlaceHolder, sgpTriangle, False)); TGenericDataNodeFactory.StoreFactory('BroadeningBottom', TGenericDataNodeFactory.CreateWithArgs(TSimpleGeometricPatternAlert, StandardSymbolPlaceHolder, sgpBroadening, True)); TGenericDataNodeFactory.StoreFactory('BroadeningTop', TGenericDataNodeFactory.CreateWithArgs(TSimpleGeometricPatternAlert, StandardSymbolPlaceHolder, sgpBroadening, False)); TGenericDataNodeFactory.StoreFactory('RectangleBottom', TGenericDataNodeFactory.CreateWithArgs(TSimpleGeometricPatternAlert, StandardSymbolPlaceHolder, sgpRectangle, True)); TGenericDataNodeFactory.StoreFactory('RectangleTop', TGenericDataNodeFactory.CreateWithArgs(TSimpleGeometricPatternAlert, StandardSymbolPlaceHolder, sgpRectangle, False)); TGenericDataNodeFactory.StoreFactory('HeadAndShoulders', TGenericDataNodeFactory.CreateWithArgs(TSimpleGeometricPatternAlert, StandardSymbolPlaceHolder, sgpHeadAndShoulders, True)); TGenericDataNodeFactory.StoreFactory('InvertedHeadAndShoulders', TGenericDataNodeFactory.CreateWithArgs(TSimpleGeometricPatternAlert, StandardSymbolPlaceHolder, sgpHeadAndShoulders, False)); TGenericDataNodeFactory.StoreFactory('DoubleTop', TGenericDataNodeFactory.CreateWithArgs(TDoubleTopAlert, StandardSymbolPlaceHolder, True)); TGenericDataNodeFactory.StoreFactory('DoubleBottom', TGenericDataNodeFactory.CreateWithArgs(TDoubleTopAlert, StandardSymbolPlaceHolder, False)); End.