Unit ConsolidationPatterns; { These are alerts releated to consolidations are confirmed running. Consolidation means that the price is moving less than expected, based on historical volatility. Consolidation defines a channel, as the highest and lowest price seen in the consolidation pattern. Running (Confirmed) means just the opposite. The price has picked a direction, and has moved in that direction faster than can be explained by random movements, based on historical volatility. Breakout / Breakdown (Confirmed) is a Consolidation transitioning directly into a Running (Confirmed). These are seperated into seperate alerts from the running alerts just so the user can more easily focus on these. These share the exact same logic, with an if statement deciding which alerts are running vs which are breakout / breakdown. (Fast) Breakout / Breakdown looks for the bid or ask to move beyond the channel. } Interface Implementation Uses DataNodes, AlertBase, VolumeWeightedData, NormalVolumeBreakBars, StandardPlaceHolders, Prices, GenericL1DataNode, GenericTosDataNode, GenericDataNodes, SimpleMarketData, Math, Classes, SysUtils; //////////////////////////////////////////////////////////////////////// // Global //////////////////////////////////////////////////////////////////////// Function ComputeVariance(High, Low, Volatility : Double; Count : Integer) : Double; Begin // Someone needs to check for divide by 0 before before here. // Count = 0 is completely meaningless. // Volatility = 0 should return + infinity. Result := (High - Low) / Sqrt(Count) / Volatility End; Function RunningQuality(Variance : Double) : Double; Begin // This was created just to make the manual for Running Up Confirmed // and Running Down Confirmed look better. Result := Variance * 8 - 9 End; //////////////////////////////////////////////////////////////////////// // TChannel //////////////////////////////////////////////////////////////////////// Type TChannelRecord = Record Consolidated : Boolean; Interesting : Boolean; // If consolidated is false, the rest of this is meaningless // and may not be set at all. Quality : Double; // 2 is min for consolidation, 5 is min for strong consolidation, 10 is max. HighPrice, LowPrice : Double; Shares : Integer; BarCount : Integer; StartTime, ReportTime : TDateTime; End; // This data node notifies its listeners exactly once every time // the underlying volume bar data node updates, regardless of any // values in the input or output of this data node. // It does not notify it's listeners at any other time. Type TChannel = Class(TDataNodeWithStringKey) Private BarData : TNormalVolumeBreakBars; VolatilityData : TGenericDataNode; FCurrent, FPrevious, FBeforeThat : TChannelRecord; Procedure UpdateCurrentState; Procedure NewBarData; Constructor Create(Symbol : String); Protected Class Function CreateNew(Data : String) : TDataNodeWithStringKey; Override; Public Class Procedure Find(Symbol : String; OnChange : TThreadMethod; Out Node : TChannel; Out Link : TDataNodeLink); Property Current : TChannelRecord Read FCurrent; Property Previous : TChannelRecord Read FPrevious; Property BeforeThat : TChannelRecord Read FBeforeThat; Class Function MinQuality : Double; Class Function MinTightQuality : Double; End; Class Function TChannel.MinQuality : Double; Begin // MaxError = 0.4 Result := 2.0 End; Class Function TChannel.MinTightQuality : Double; Begin // MaxTightError = 0.25 Result := 5.0 End; Procedure TChannel.UpdateCurrentState; Const ConsolidationMinLength = 7; Var Bars : TVolumeBlocks; Variance, LowestVariance, Quality : Double; HighestHigh, LowestLow : Double; BestLength, CurrentLength, CurrentBar : Integer; Begin FCurrent.Consolidated := False; FCurrent.Interesting := False; SetLength(Bars, 0); // Avoid compiler warning! If Not VolatilityData.IsValid Then Exit; If (VolatilityData.GetDouble < MinVolatility) Then Exit; Bars := BarData.GetBlocks; If Length(Bars) < ConsolidationMinLength Then Exit; HighestHigh := -MaxDouble; LowestLow := MaxDouble; LowestVariance := MaxDouble; BestLength := 0; For CurrentLength := 1 To Length(Bars) Do Begin CurrentBar := Length(Bars) - CurrentLength; HighestHigh := Max(HighestHigh, Bars[CurrentBar].High); LowestLow := Min(LowestLow, Bars[CurrentBar].Low); If CurrentLength >= ConsolidationMinLength Then Begin Variance := (HighestHigh - LowestLow) / Sqrt(CurrentLength) / VolatilityData.GetDouble; If Variance < LowestVariance Then Begin LowestVariance := Variance; BestLength := CurrentLength End End End; If BestLength > 0 Then Begin Quality := 10 - 20 * LowestVariance; If Quality >= MinQuality Then Begin FCurrent.Quality := Quality; HighestHigh := -MaxDouble; LowestLow := MaxDouble; For CurrentLength := 1 To BestLength Do Begin CurrentBar := Length(Bars) - CurrentLength; HighestHigh := Max(HighestHigh, Bars[CurrentBar].High); LowestLow := Min(LowestLow, Bars[CurrentBar].Low) End; FCurrent.HighPrice := HighestHigh; FCurrent.LowPrice := LowestLow; FCurrent.StartTime := Bars[Length(Bars) - BestLength].StartTime; FCurrent.ReportTime := GetSubmitTime; FCurrent.Shares := BestLength * BarData.BlockSize; FCurrent.BarCount := BestLength; FCurrent.Consolidated := True; FCurrent.Interesting := FCurrent.HighPrice > FCurrent.LowPrice End End End; Procedure TChannel.NewBarData; Begin FBeforeThat := FPrevious; FPrevious := FCurrent; UpdateCurrentState; NotifyListeners End; Constructor TChannel.Create(Symbol : String); Var Factory : IGenericDataNodeFactory; Link : TDataNodeLink; Begin Inherited Create; TNormalVolumeBreakBars.Find(Symbol, NewBarData, BarData, Link); AddAutoLink(Link); Factory := TGenericDataNodeFactory.FindFactory('TickVolatility').Duplicate; Factory.SetValue(SymbolNameKey, Symbol); Factory.Find(Nil, VolatilityData, Link); AddAutoLink(Link) // It would be better to check for history here. We load all // historical bar data, which could have been collecting since // before this data node was created. Ideally we would initialize // ourselves by checking for consolidations immediately, and // checking what they would have been one bar ago. End; Class Function TChannel.CreateNew(Data : String) : TDataNodeWithStringKey; Begin Result := Create(Data) End; Class Procedure TChannel.Find(Symbol : String; OnChange : TThreadMethod; Out Node : TChannel; Out Link : TDataNodeLink); Var TempNode : TDataNodeWithStringKey; Begin FindCommon(TChannel, Symbol, OnChange, TempNode, Link); Node := TempNode As TChannel End; //////////////////////////////////////////////////////////////////////// // TConsolidation //////////////////////////////////////////////////////////////////////// Type TConsolidation = Class(TAlert) Public Function IsValid : Boolean; Override; Protected Constructor Create(Params : TParamList); Override; Private ChannelData : TChannel; ShouldSkip : Integer; LastDisplayed : TChannelRecord; Procedure NewData; End; Function TConsolidation.IsValid : Boolean; Begin Result := True End; Constructor TConsolidation.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Begin Assert(Length(Params) = 1); Symbol := Params[0]; Inherited Create; TChannel.Find(Symbol, NewData, ChannelData, Link); AddAutoLink(Link) End; Procedure TConsolidation.NewData; Var AlertType, AlertDirection : String; Begin If ChannelData.Current.Consolidated Then Begin If ShouldSkip <= 0 Then Begin If ChannelData.Current.Quality >= ChannelData.MinTightQuality Then AlertType := 'Strong consolidation' Else AlertType := 'Consolidation'; If LastDisplayed.Consolidated Then If ChannelData.Current.Quality < LastDisplayed.Quality Then AlertDirection := ' (Decaying)' Else AlertDirection := ' (Improving)' Else AlertDirection := ''; Report( Format('%s%s. $%s-%s. Shares: %.0n. Time: %s.', [AlertType, AlertDirection, FormatPrice(ChannelData.Current.LowPrice), FormatPrice(ChannelData.Current.HighPrice), ChannelData.Current.Shares + 0.0, DurationString(ChannelData.Current.StartTime, ChannelData.Current.ReportTime)]), ChannelData.Current.Quality); LastDisplayed := ChannelData.Current; ShouldSkip := Max(0, Round(Ceil(Log2(ChannelData.Current.BarCount)))-3) End Else Dec(ShouldSkip) End Else Begin ShouldSkip := 0; LastDisplayed.Consolidated := False End End; //////////////////////////////////////////////////////////////////////// // TRunningConfirmed //////////////////////////////////////////////////////////////////////// Type TRunningConfirmed = Class(TAlert) Protected Constructor Create(Params : TParamList); Override; Private Up : Boolean; BreakoutRequired : Boolean; PreviouslyReported : Boolean; BarData : TNormalVolumeBreakBars; ChannelData : TChannel; TickVolatilityData : TGenericDataNode; PastReports : Array Of Record BarNumber : Integer; Variance : Double End; Procedure NewBarData; Function GetVariance(High, Low : Double; Count : Integer = 1) : Double; End; Function TRunningConfirmed.GetVariance(High, Low : Double; Count : Integer) : Double; Begin Try Result := ComputeVariance(High, Low, TickVolatilityData.GetDouble, Count) Except On Exception Do Begin // Hope to catch this somewhere sooner, but the result is // the same. Invalid volatility (or count) means no alert. Result := 0 End End End; Constructor TRunningConfirmed.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Factory : IGenericDataNodeFactory; Begin Assert(Length(Params) = 3, 'Expected params: (Symbol, Up, BreakoutRequired)'); Symbol := Params[0]; Up := Params[1]; BreakoutRequired := Params[2]; Inherited Create; TChannel.Find(Symbol, NewBarData, ChannelData, Link); AddAutoLink(Link); TNormalVolumeBreakBars.Find(Symbol, Nil, BarData, Link); AddAutoLink(Link); Factory := TGenericDataNodeFactory.FindFactory('TickVolatility').Duplicate; Factory.SetValue(SymbolNameKey, Symbol); Factory.Find(Nil, TickVolatilityData, Link); AddAutoLink(Link) End; Procedure TRunningConfirmed.NewBarData; Procedure ReportChannelBreak(Channel : TChannelRecord; Variance : Double); Var BreakDirection : String; Begin If Up Then BreakDirection := 'out' Else BreakDirection := 'down'; Report(Format('Channel break%s from $%s-%s, %s.', [BreakDirection, FormatPrice(Channel.LowPrice), FormatPrice(Channel.HighPrice), DurationString(Channel.StartTime, GetSubmitTime)]), RunningQuality(Variance)) End; Procedure AddReport(Position : Integer; Variance : Double); Var NewIndex : Integer; Begin NewIndex := Length(PastReports); Repeat If NewIndex = 0 Then Break; If Variance < PastReports[Pred(NewIndex)].Variance Then Break; Dec(NewIndex) Until False; SetLength(PastReports, Succ(NewIndex)); PastReports[NewIndex].BarNumber := Position; PastReports[NewIndex].Variance := Variance End; Function BeatsPreviousReports(Variance : Double; Position : Integer) : Boolean; Var I : Integer; Begin For I := High(PastReports) DownTo Low(PastReports) Do Begin If PastReports[I].BarNumber < Position Then Begin Result := True; Exit End; If PastReports[I].Variance >= Variance Then Begin Result := False; Exit End End; Result := True End; Const MinVariance = 1.25; MinStrongVariance = 1.75; Var I, Count, LastBar : Integer; Bars : TVolumeBlocks; ReportedLastTime : Boolean; HighsVariance, LowsVariance : Double; Variance : Double; HighestVariance : Double; HighestVariancePosition : Integer; Speed : String; Begin SetLength(Bars, 0); ReportedLastTime := PreviouslyReported; PreviouslyReported := False; If Not TickVolatilityData.IsValid Then Exit; If TickVolatilityData.GetDouble < MinVolatility Then Exit; Bars := BarData.GetBlocks; LastBar := Pred(Length(Bars)); If LastBar <= 0 Then Exit; If Not ReportedLastTime Then Begin If ChannelData.Current.Consolidated Then Exit; If ChannelData.Previous.Consolidated Then Begin If Up Then Variance := Min(GetVariance(Bars[LastBar].High, Bars[Pred(LastBar)].High), GetVariance(Bars[LastBar].Low, Bars[Pred(LastBar)].Low)) Else Variance := Min(GetVariance(Bars[Pred(LastBar)].High, Bars[LastBar].High), GetVariance(Bars[Pred(LastBar)].Low, Bars[LastBar].Low)); If Variance > MinVariance Then Begin If BreakoutRequired Then ReportChannelBreak(ChannelData.Previous, Variance); PreviouslyReported := True; Exit End End Else If ChannelData.BeforeThat.Consolidated Then Begin If Up Then Variance := Min(GetVariance(Bars[LastBar].High, Bars[LastBar-2].High, 2), GetVariance(Bars[LastBar].Low, Bars[LastBar-2].Low, 2)) Else Variance := Min(GetVariance(Bars[LastBar-2].High, Bars[LastBar].High), GetVariance(Bars[LastBar-2].Low, Bars[LastBar].Low)); If Variance > MinVariance Then Begin If BreakoutRequired Then ReportChannelBreak(ChannelData.Previous, Variance); PreviouslyReported := True; Exit End End End; If Not BreakoutRequired Then Begin Count := 0; HighestVariance := 0; HighsVariance := 0; // Only for the compiler LowsVariance := 0; // Only for the compiler HighestVariancePosition := 0; // Only for the compiler For I := Pred(LastBar) DownTo 0 Do Begin Inc(Count); If Up Then Begin If (Bars[LastBar].High <= Bars[I].High) Or (Bars[LastBar].Low <= Bars[I].Low) Then Break; HighsVariance := GetVariance(Bars[LastBar].High, Bars[I].High, Count); LowsVariance := GetVariance(Bars[LastBar].Low, Bars[I].Low, Count) End Else // Down Begin If (Bars[LastBar].High >= Bars[I].High) Or (Bars[LastBar].Low >= Bars[I].Low) Then Break; HighsVariance := GetVariance(Bars[I].High, Bars[LastBar].High, Count); LowsVariance := GetVariance(Bars[I].Low, Bars[LastBar].Low, Count); End; Variance := Min(HighsVariance, LowsVariance); If Variance > HighestVariance Then If BeatsPreviousReports(Variance, I) Then Begin HighestVariance := Variance; HighestVariancePosition := I End End; If HighestVariance > MinVariance Then Begin If HighestVariance >= MinStrongVariance Then Speed := ' briskly' Else Speed := ''; If Up Then Report('Running up' + Speed + ': ' + FormatPrice(Bars[LastBar].High - Bars[HighestVariancePosition].Low, True) + ' in ' + DurationString(Bars[HighestVariancePosition].StartTime, Bars[LastBar].EndTime) + '. Confirmed by volume.', RunningQuality(HighestVariance)) Else Report('Running down' + Speed + ': ' + FormatPrice(Bars[LastBar].Low - Bars[HighestVariancePosition].High, True) + ' in ' + DurationString(Bars[HighestVariancePosition].StartTime, Bars[LastBar].EndTime) + '. Confirmed by volume.', RunningQuality(HighestVariance)); AddReport(Pred(LastBar), HighestVariance) End End End; //////////////////////////////////////////////////////////////////////// // TChannelBreakout //////////////////////////////////////////////////////////////////////// Type TChannelBreakout = Class(TAlert) Private L1Data : TGenericL1DataNode; TosData : TGenericTosDataNode; ChannelData : TChannel; Primed : Boolean; Resistance : Double; ChannelValid : Boolean; LastReport : TDateTime; Procedure NewL1Data; Procedure NewTosData; Procedure NewChannelData; Procedure ReportNow; Protected Constructor Create(Params : TParamList); Override; End; Constructor TChannelBreakout.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Begin Assert(Length(Params)=1, 'Expected params: (Symbol)'); Symbol := Params[0]; Inherited Create; TGenericL1DataNode.Find(Symbol, NewL1Data, L1Data, Link); AddAutoLink(Link); TGenericTosDataNode.Find(Symbol, NewTosData, TosData, Link); AddAutoLink(Link); TChannel.Find(Symbol, NewChannelData, ChannelData, Link); AddAutoLink(Link) End; Procedure TChannelBreakout.NewL1Data; Var Current : PL1Data; Begin If (Not Primed) Or (Not ChannelValid) Or (Not L1Data.IsValid) Then Exit; Current := L1Data.GetCurrent; With Current^ Do If (BidPrice = 0) Or (AskPrice = 0) Or (BidPrice >= AskPrice) Then Exit; If Current.BidPrice > Resistance Then ReportNow End; Procedure TChannelBreakout.NewTosData; Var Current : PL1Data; Last : PTosData; ChannelTop : Double; Begin If Primed Or (Not ChannelValid) Or (Not L1Data.IsValid) Or (Not TosData.IsValid) Then Exit; Current := L1Data.GetCurrent; With Current^ Do If (BidPrice = 0) Or (AskPrice = 0) Or (BidPrice >= AskPrice) Then Exit; Last := TosData.GetLast; ChannelTop := ChannelData.FCurrent.HighPrice; If (Current.AskPrice <= ChannelTop) And (Last.Price < ChannelTop) Then Primed := True End; Procedure TChannelBreakout.NewChannelData; Var Current, Previous : TChannelRecord; Begin LastReport := 0; Current := ChannelData.Current; If Not Current.Interesting Then ChannelValid := False Else Begin ChannelValid := True; Previous := ChannelData.Previous; If Previous.Interesting Then Resistance := Max(Current.HighPrice, Previous.HighPrice) Else Resistance := Current.HighPrice; Primed := True; NewL1Data End End; Procedure TChannelBreakout.ReportNow; Var Channel : TChannelRecord; Begin If (LastReport = 0) Or (GetSubmitTime - LastReport > 1.0 / 24 / 60) Then Begin Channel := ChannelData.Current; Report(Format('Channel breakout from $%s-%s, %s.', [FormatPrice(Channel.LowPrice), FormatPrice(Channel.HighPrice), DurationString(Channel.StartTime, GetSubmitTime)]), Channel.Quality) End; LastReport := GetSubmitTime; Primed := False End; //////////////////////////////////////////////////////////////////////// // TChannelBreakdown //////////////////////////////////////////////////////////////////////// Type TChannelBreakdown = Class(TAlert) Private L1Data : TGenericL1DataNode; TosData : TGenericTosDataNode; ChannelData : TChannel; Primed : Boolean; Support : Double; ChannelValid : Boolean; LastReport : TDateTime; Procedure NewL1Data; Procedure NewTosData; Procedure NewChannelData; Procedure ReportNow; Protected Constructor Create(Params : TParamList); Override; End; Constructor TChannelBreakdown.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Begin Assert(Length(Params)=1, 'Expected params: (Symbol)'); Symbol := Params[0]; Inherited Create; TGenericL1DataNode.Find(Symbol, NewL1Data, L1Data, Link); AddAutoLink(Link); TGenericTosDataNode.Find(Symbol, NewTosData, TosData, Link); AddAutoLink(Link); TChannel.Find(Symbol, NewChannelData, ChannelData, Link); AddAutoLink(Link) End; Procedure TChannelBreakdown.NewL1Data; Var Current : PL1Data; Begin If (Not Primed) Or (Not ChannelValid) Or (Not L1Data.IsValid) Then Exit; Current := L1Data.GetCurrent; With Current^ Do If (BidPrice = 0) Or (AskPrice = 0) Or (BidPrice >= AskPrice) Then Exit; If Current.AskPrice < Support Then ReportNow End; Procedure TChannelBreakdown.NewTosData; Var Current : PL1Data; Last : PTosData; ChannelBottom : Double; Begin If Primed Or (Not ChannelValid) Or (Not L1Data.IsValid) Or (Not TosData.IsValid) Then Exit; Current := L1Data.GetCurrent; With Current^ Do If (BidPrice = 0) Or (AskPrice = 0) Or (BidPrice >= AskPrice) Then Exit; Last := TosData.GetLast; ChannelBottom := ChannelData.FCurrent.LowPrice; If (Current.BidPrice >= ChannelBottom) And (Last.Price > ChannelBottom) Then Primed := True End; Procedure TChannelBreakdown.NewChannelData; Var Current, Previous : TChannelRecord; Begin LastReport := 0; Current := ChannelData.Current; If Not Current.Interesting Then ChannelValid := False Else Begin ChannelValid := True; Previous := ChannelData.Previous; If Previous.Interesting Then Support := Min(Current.LowPrice, Previous.LowPrice) Else Support := Current.LowPrice; Primed := True; NewL1Data End End; Procedure TChannelBreakdown.ReportNow; Var Channel : TChannelRecord; Begin If (LastReport = 0) Or (GetSubmitTime - LastReport > 1.0 / 24 / 60) Then Begin Channel := ChannelData.Current; Report(Format('Channel breakdown from $%s-%s, %s.', [FormatPrice(Channel.LowPrice), FormatPrice(Channel.HighPrice), DurationString(Channel.StartTime, GetSubmitTime)]), Channel.Quality) End; LastReport := GetSubmitTime; Primed := False End; //////////////////////////////////////////////////////////////////////// // TTrendStrength //////////////////////////////////////////////////////////////////////// Type TTrendStrength = Class(TGenericDataNode) Private BarData : TNormalVolumeBreakBars; VolatilityData : TGenericDataNode; FCachedValue : Double; FCachedValid : Boolean; FUp : Boolean; FLastCount : Integer; Procedure Reset; Procedure MakeCurrent; Procedure ComputeData; Procedure NewBarData; Procedure NewVolatilityData; Protected Constructor Create(Params : TParamList); Override; Public Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; Procedure TTrendStrength.ComputeData; Type TAccumulator = Record LastValue : Double; ExtremeValue : Double; Variance : Double; MaxVarianceIndex : Integer; // Debug only; End; Var Volatility : Double; Count : Integer; Bars : TVolumeBlocks; Procedure CheckValue(Var Accumulator : TAccumulator; CurrentValue : Double); Var NewVariance : Double; Begin { CheckValue } If (FUp And (Accumulator.ExtremeValue > CurrentValue)) Or ((Not FUp) And (Accumulator.ExtremeValue < CurrentValue)) Then Begin If FUp Then NewVariance := ComputeVariance(Accumulator.LastValue, CurrentValue, Volatility, Count) Else NewVariance := ComputeVariance(CurrentValue, Accumulator.LastValue, Volatility, Count); If NewVariance > Accumulator.Variance Then Begin Accumulator.Variance := NewVariance; Accumulator.MaxVarianceIndex := Count End; Accumulator.ExtremeValue := CurrentValue End End; { CheckValue } Var Bottoms, Tops : TAccumulator; Begin { TTrendStrength.ComputeData } Bars := BarData.GetBlocks; Volatility := VolatilityData.GetDouble; If FUp Then Bottoms.ExtremeValue := MaxDouble Else Bottoms.ExtremeValue := -MaxDouble; Tops.ExtremeValue := Bottoms.ExtremeValue; Tops.Variance := 0; Bottoms.Variance := 0; Tops.MaxVarianceIndex := 0; Bottoms.MaxVarianceIndex := 0; With Bars[Pred(Length(Bars))] Do Begin Tops.LastValue := High; Bottoms.LastValue := Low End; For Count := 1 To Pred(Length(Bars)) Do With Bars[Pred(Length(Bars) - Count)] Do Begin CheckValue(Tops, High); CheckValue(Bottoms, Low) End; FCachedValue := RunningQuality(Min(Tops.Variance, Bottoms.Variance)); FCachedValid := True End; { TTrendStrength.ComputeData } Procedure TTrendStrength.MakeCurrent; Var NeedReset : Boolean; BarCount : Integer; Begin NeedReset := True; BarCount := BarData.GetBlockCount; If VolatilityData.IsValid Then If VolatilityData.GetDouble > 0 Then If BarCount = FLastCount Then NeedReset := False Else If BarCount > 1 Then Try ComputeData; FLastCount := BarCount; NeedReset := False Except // We've checked for divide by 0, but maybe divide by a very small // number to get an overflow. End; If NeedReset Then Reset End; Procedure TTrendStrength.Reset; Begin FLastCount := 0; FCachedValid := False; FCachedValue := 0 End; Procedure TTrendStrength.NewBarData; Begin // This means that we need to take another look. Very likely, each time // there is a new bar, some clients will call us before we get the // notification ourselves, and some will call us after. So we don't use // this event to determine what has changed. Use the number of bars to // determine if we are in the same position as before. If BarData.GetBlockCount = 0 Then // If the bar data ever resets, it is required to change the bar // count to 0 and notify all listeners before adding the new data. // So we can't skip this case. In any other case we wait for a // request before computing anything. Reset; // This almost certainly will change the value of our data, although // our listener is probably triggered by something different and will // not use this. NotifyListeners End; Procedure TTrendStrength.NewVolatilityData; Begin // This seldom if ever changes, but when it does we start from scratch. Reset; NotifyListeners End; Constructor TTrendStrength.Create(Params : TParamList); Var Symbol : String; Factory : IGenericDataNodeFactory; Link : TDataNodeLink; Begin Assert(Length(Params)=2, 'Expected params: (Symbol, Up)'); Symbol := Params[0]; FUp := Params[1]; Inherited Create; TNormalVolumeBreakBars.Find(Symbol, NewBarData, BarData, Link); AddAutoLink(Link); Factory := TGenericDataNodeFactory.FindFactory('TickVolatility').Duplicate; Factory.SetValue(SymbolNameKey, Symbol); Factory.Find(NewVolatilityData, VolatilityData, Link); AddAutoLink(Link) End; Function TTrendStrength.IsValid : Boolean; Begin MakeCurrent; Result := FCachedValid End; Function TTrendStrength.GetDouble : Double; Begin MakeCurrent; Result := FCachedValue End; //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Initialization TGenericDataNodeFactory.StoreStandardFactory('Consolidation', TConsolidation); TGenericDataNodeFactory.StoreFactory('RunningUpConfirmed', TGenericDataNodeFactory.CreateWithArgs(TRunningConfirmed, StandardSymbolPlaceHolder, True, False)); TGenericDataNodeFactory.StoreFactory('RunningDownConfirmed', TGenericDataNodeFactory.CreateWithArgs(TRunningConfirmed, StandardSymbolPlaceHolder, False, False)); TGenericDataNodeFactory.StoreFactory('ChannelBreakoutConfirmed', TGenericDataNodeFactory.CreateWithArgs(TRunningConfirmed, StandardSymbolPlaceHolder, True, True)); TGenericDataNodeFactory.StoreFactory('ChannelBreakdownConfirmed', TGenericDataNodeFactory.CreateWithArgs(TRunningConfirmed, StandardSymbolPlaceHolder, False, True)); TGenericDataNodeFactory.StoreStandardFactory('ChannelBreakout', TChannelBreakout); TGenericDataNodeFactory.StoreStandardFactory('ChannelBreakdown', TChannelBreakdown); TGenericDataNodeFactory.StoreFactory('UpTrendStrength', TGenericDataNodeFactory.CreateWithArgs(TTrendStrength, StandardSymbolPlaceHolder, True)); TGenericDataNodeFactory.StoreFactory('DownTrendStrength', TGenericDataNodeFactory.CreateWithArgs(TTrendStrength, StandardSymbolPlaceHolder, False)); End.