Unit RecentHighsAndLows; { This provides access to the daily highs and lows. This offers two advantages over going directly to the L1 data. 1) The primary advantage is that it caches the value of yesterday's low, so we don't lose it at 4:55am. 2) It also caches the changes so it doesn't sound off except when the value actually changes. #1 Any time the value is reported from L1 as 0 or not valid, we assume that the value is not valid. In that case we continue to report the last good value that we have. We only report invalid if we've never recieved a valid value. Ideally we'd report that we don't know the value when the L1 data is not valid. But that's inconsistant with our goal of not giving up a good value. If the network goes down for a second in the morning, we don't want to give everything up. So we do our best not to pass on the invalid notice. One of the primary reasons that people watch for the invalid bit is to look for things that are now valid but were not previously valid. If the data feed goes down for a while then goes up, everyone just displaying data should immediately display the current value. But we don't want to report a lot of new high and similar alerts at this time. Those alerts actually happened while we were down, and are lost. So we keep track of the previously down bit. #2 As long as we're at it, we cache the changes. Several data nodes just watch the high or the low. The L1 data updates a lot more often than either of these two values. We do this mostly because most of the work was already done, so why not take advantage of it. Note, this only prevents us from calling the OnChange callback any more than required. Using this data node can actually cause more calls to read the L1 data. Every request to read from this data node requires a call to read the L1 data, and every time the L1 data changes, this node reads the L1 data. } Interface Uses DataNodes, GenericL1DataNode, Classes; Type TRecentHighLow = Class(TDataNodeWithStringKey) Protected Class Function CreateNew(Data : String) : TDataNodeWithStringKey; Override; Constructor Create(Symbol : String; High : Boolean); Private FHigh : Boolean; FPreviouslyValid : Boolean; LastValue : Double; L1Data : TGenericL1DataNode; Procedure NewData; Function CurrentInput : Double; Public Class Procedure Find(Symbol : String; High : Boolean; OnChange : TThreadMethod; Out Node : TRecentHighLow; Out Link : TDataNodeLink); Function IsValid : Boolean; // We believe we know the right value. // This is an optimistic estimate. // It's more effecient just to store // CurrentValue and check if it is 0. Function CurrentValue : Double; // The most recent high or low. Before // the open the refers to yesterday. // If there is no valid value, return // 0. Function PreviouslyValid : Boolean; // This refers to the underlying // data feed. This really only // makes sense in the OnChange // callback. We call OnChange // every time the CurrentValue // changes, but not every time that // the PreviouslyValid flag changes. End; Implementation Uses StrUtils; Const EncodeHighLow : Array [False..True] Of Char = ('L', 'H'); Class Function TRecentHighLow.CreateNew(Data : String) : TDataNodeWithStringKey; Begin Assert(Length(Data) >= 1); Result := Create(MidStr(Data, 2, MaxInt), Data[1]=EncodeHighLow[True]) End; Constructor TRecentHighLow.Create(Symbol : String; High : Boolean); Var Link : TDataNodeLink; Begin Inherited Create; FHigh := High; TGenericL1DataNode.Find(Symbol, NewData, L1Data, Link); AddAutoLink(Link); DoInCorrectThread(NewData) End; Function TRecentHighLow.CurrentInput : Double; Begin If L1Data.IsValid Then If FHigh Then Result := L1Data.GetCurrent^.High Else Result := L1Data.GetCurrent^.Low Else Result := 0 End; Procedure TRecentHighLow.NewData; Var NewValue : Double; Begin FPreviouslyValid := LastValue <> 0; NewValue := CurrentInput; If (NewValue <> 0) And (NewValue <> LastValue) Then Begin LastValue := NewValue; NotifyListeners End End; Class Procedure TRecentHighLow.Find(Symbol : String; High : Boolean; OnChange : TThreadMethod; Out Node : TRecentHighLow; Out Link : TDataNodeLink); Var TempNode : TDataNodeWithStringKey; Begin FindCommon(TRecentHighLow, EncodeHighLow[High] + Symbol, OnChange, TempNode, Link); Node := TempNode As TRecentHighLow End; Function TRecentHighLow.IsValid : Boolean; Begin Result := CurrentValue <> 0 End; Function TRecentHighLow.CurrentValue : Double; Begin Result := CurrentInput; // Always try to use the most current value. // If the caller is listening to multiple // data nodes, he may call this right before // we do any update. For example, the // initialization code for the new high/low // bid/ask could see the first bid/ask before // this data node gets the first high/low, and // the new high/low bid/ask code can never // recover from that. If Result <= 0 Then Result := LastValue // We don't have a current value, so check for // a cached value. End; Function TRecentHighLow.PreviouslyValid : Boolean; Begin Result := FPreviouslyValid End; End.