Unit HighLowBidAsk; Interface Implementation Uses DataNodes, GenericDataNodes, L1Alerts, NewDayDetector, GenericL1DataNode, Prices, AlertBase, DebugOutput, RecentHighsAndLows, StandardPlaceHolders, Math, DateUtils; //////////////////////////////////////////////////////////////////////// // TL1AndNewDayUser // // This is just a common base class for the classes below. This takes // care of inheriting and/or creating all the common data nodes used // by all of these. //////////////////////////////////////////////////////////////////////// Type TL1AndNewDayUser = Class(TL1Alert) Private Function SafeBid : Double; Function SafeAsk : Double; Protected FUp : Boolean; FUseBid : Boolean; HighLowData : TRecentHighLow; DebugSymbol : String; // Only useful for debug messages. Constructor Create(Params : TParamList); Override; Procedure OnNewDay; Virtual; Abstract; Class Function SafeMin(A, B : Double) : Double; Procedure ReportChange(PriceChange : Double); Function InsideMarketValid : Boolean; Function BidAskValue : Double; Function IsMoreExtreme(Newer, Older : Double) : Boolean; // True if Newer is more extreme than older. Function MoreExtremeValue(A, B : Double) : Double; End; Function TL1AndNewDayUser.IsMoreExtreme(Newer, Older : Double) : Boolean; Begin If Newer = 0 Then Result := False Else If FUp Then Result := Newer > Older Else Result := Newer < Older End; Function TL1AndNewDayUser.MoreExtremeValue(A, B : Double) : Double; Begin If FUp Then Result := Max(A, B) Else Result := SafeMin(A, B) End; Function TL1AndNewDayUser.BidAskValue : Double; Begin If FUseBid Then Result := SafeBid Else Result := SafeAsk End; // Do not report any alerts between 30 seconds before the open and one // minute after the open. Procedure TL1AndNewDayUser.ReportChange(PriceChange : Double); Const BlankStartTime = 1.0 / 24.0 * 6.5 - 1.0 / 24.0 / 60.0 / 2; BlankEndTime = 1.0 / 24.0 * 6.5 + 1.0 / 24.0 / 60.0; BidAskString : Array[Boolean] Of String = ('Ask', 'Bid'); HighLowString : Array[Boolean] Of String = ('Low', 'High'); Var ReportTime : TDateTime; Description : String; Begin ReportTime := TimeOf(GetSubmitTime); If (ReportTime < BlankStartTime) Or (ReportTime > BlankEndTime) Then Begin Description := 'New ' + HighLowString[FUp] + ' ' + BidAskString[FUseBid] + ': ' + FormatPrice(PriceChange, True); If Not InsideMarketValid Then Report(Description) Else If FUseBid Then Report(Description, Data.GetCurrent^.BidSize) Else Report(Description, Data.GetCurrent^.AskSize) End End; Constructor TL1AndNewDayUser.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Begin Assert(Length(Params)=3, 'Expected params: (Symbol, Up, Bid)'); Symbol := Params[0]; FUp := Params[1]; FUseBid := Params[2]; DebugSymbol := Symbol; Inherited; TNewDay.Find(Symbol, OnNewDay, Link); AddAutoLink(Link); TRecentHighLow.Find(Symbol, FUp, Nil, HighLowData, Link); AddAutoLink(Link); // This call is very important. We need to start from a baseline. // When we get the first change, we need to know how it compares to the // previous value. DoInCorrectThread(OnNewDay) End; // A lot of these 0 checks are from the old eSignal code. // ESignal would normally freeze high and low from the close and // keep them until they got new values at the open. Except for random // times when these values would be 0, which caused this code to // become a lot more complicated. // TAL will make the high and low undefined, which I convert to 0, // between the day rollover and the open. It doesn't appear to be // 0 at any other times. // It appears that TAL will sometimes make the bid 0.01 and the ask 0.00 // in the moring before it gets a good value. In that case we convert // both values to 0, below. Class Function TL1AndNewDayUser.SafeMin(A, B : Double) : Double; Begin If A = 0 Then Result := B Else If B = 0 Then Result := A Else Result := Min(A, B) End; // This should get rid of some of the silly cases. Sometimes the bid // goes to 0.01 and the ask goes to 0.00. In this case we don't report // a new low bid or ask because it's trash. Reporting this would prevent // us from reporting some legitimate stuff later. Function TL1AndNewDayUser.InsideMarketValid : Boolean; Var Current : PL1Data; Begin Result := False; If Data.IsValid Then Begin Current := Data.GetCurrent; Result := (Current.BidPrice > 0) And (Current.AskPrice > 0) End End; // Returns 0 if the bid or the ask is not valid. Function TL1AndNewDayUser.SafeBid : Double; Begin If InsideMarketValid Then Result := Data.GetCurrent^.BidPrice Else Result := 0.0 End; // Returns 0 if the bid or the ask is not valid. Function TL1AndNewDayUser.SafeAsk : Double; Begin If InsideMarketValid Then Result := Data.GetCurrent^.AskPrice Else Result := 0.0 End; //////////////////////////////////////////////////////////////////////// // TEveryNewHighLowBidAsk //////////////////////////////////////////////////////////////////////// Type TEveryNewHighLowBidAsk = Class(TL1AndNewDayUser) Private Initialized : Boolean; Extreme : Double; Protected Procedure NewDataRecieved; Override; Procedure OnNewDay; Override; End; Procedure TEveryNewHighLowBidAsk.OnNewDay; Begin Extreme := MoreExtremeValue(BidAskValue, HighLowData.CurrentValue); Initialized := Extreme > 0 End; Procedure TEveryNewHighLowBidAsk.NewDataRecieved; Var CurrentBidAskPrice : Double; Begin If Not Initialized Then OnNewDay Else Begin CurrentBidAskPrice := BidAskValue; If IsMoreExtreme(CurrentBidAskPrice, Extreme) Then Begin If Extreme > 0 Then ReportChange(CurrentBidAskPrice - Extreme); Extreme := CurrentBidAskPrice End End End; //////////////////////////////////////////////////////////////////////// // TFilteredNewHighLowBidAsk //////////////////////////////////////////////////////////////////////// Type TFilteredNewHighLowBidAsk = Class(TL1AndNewDayUser) Private Initialized : Boolean; InterestingCutoff : Double; Extreme : Double; PreviouslyDisplayedExtreme : Double; PreviousTime : TDateTime; Protected Constructor Create(Params : TParamList); Override; Procedure NewTickVolatility; Override; Procedure NewDataRecieved; Override; Procedure OnNewDay; Override; End; Constructor TFilteredNewHighLowBidAsk.Create(Params : TParamList); Begin InterestingCutoff := 1/8; // A good default. Inherited; End; Procedure TFilteredNewHighLowBidAsk.NewTickVolatility; Begin InterestingCutoff := 1/8; // Default; If Assigned(TickVolatilityData) Then If TickVolatilityData.IsValid Then // Expected volatility for 3 minutes. InterestingCutoff := TickVolatilityData.GetDouble / Sqrt(5) End; Procedure TFilteredNewHighLowBidAsk.OnNewDay; Begin Extreme := MoreExtremeValue(BidAskValue, HighLowData.CurrentValue); PreviouslyDisplayedExtreme := Extreme; PreviousTime := 0; Initialized := Extreme > 0 End; Const MinAlertTime = 1.0 / 24 / 60; // One minute; Procedure TFilteredNewHighLowBidAsk.NewDataRecieved; Var CurrentBidAskPrice : Double; Begin If Not Initialized Then OnNewDay Else Begin CurrentBidAskPrice := BidAskValue; If IsMoreExtreme(CurrentBidAskPrice, Extreme) Then Begin If (Abs(CurrentBidAskPrice - Extreme) > InterestingCutoff) Or (GetSubmitTime > PreviousTime + MinAlertTime) Then If Extreme > 0 Then Begin ReportChange(CurrentBidAskPrice - PreviouslyDisplayedExtreme); PreviouslyDisplayedExtreme := CurrentBidAskPrice; PreviousTime := GetSubmitTime End; Extreme := CurrentBidAskPrice End End End; //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Initialization // Symbol, up, bid TGenericDataNodeFactory.StoreFactory('NewHighAsk', TGenericDataNodeFactory.CreateWithArgs(TEveryNewHighLowBidAsk, StandardSymbolPlaceHolder, True, False)); TGenericDataNodeFactory.StoreFactory('NewLowBid', TGenericDataNodeFactory.CreateWithArgs(TEveryNewHighLowBidAsk, StandardSymbolPlaceHolder, False, True)); TGenericDataNodeFactory.StoreFactory('NewHighAskFiltered', TGenericDataNodeFactory.CreateWithArgs(TFilteredNewHighLowBidAsk, StandardSymbolPlaceHolder, True, False)); TGenericDataNodeFactory.StoreFactory('NewLowBidFiltered', TGenericDataNodeFactory.CreateWithArgs(TFilteredNewHighLowBidAsk, StandardSymbolPlaceHolder, False, True)); TGenericDataNodeFactory.StoreFactory('NewHighBidFiltered', TGenericDataNodeFactory.CreateWithArgs(TFilteredNewHighLowBidAsk, StandardSymbolPlaceHolder, True, True)); TGenericDataNodeFactory.StoreFactory('NewLowAskFiltered', TGenericDataNodeFactory.CreateWithArgs(TFilteredNewHighLowBidAsk, StandardSymbolPlaceHolder, False, False)); End.