Unit MarketData; { This really needs to be broken into two seperate units. MiscGenericDataNodes should provides nodes like + and - which can be used with any data in a TGenericDataNode. The MarketData unit should only hold market data. It will be a possible place to seperate the code if we have two seperate data sources, although, ideally, this is done at a lower level. } Interface Uses GenericDataNodes, DataNodes, GenericL1DataNode, GenericTosDataNode, GenericFundamentalDataNode, Timers, Classes, Graphics; Type TDoubleUpTick = Class(TGenericDataNode) Published Function GetBoolean : Boolean; Override; Public Function IsValid : Boolean; Override; Protected Constructor Create(Params : TParamList); Override; Private PreviousValueValid : Boolean; PreviousValue : Double; Answer : Boolean; AnswerValid : Boolean; Input : TGenericDataNode; Procedure NewDataRecieved; End; TOpenPrice = Class(TGenericDataNode) Published { This returns 0.0 if IsValid is false. } Function GetDouble : Double; Override; Public { We return false in two seperate cases. False can mean that there is no meaningful open price; the stock has not opened yet today. False can also mean that we just don't have any data. If you're just going to display the value, these two cases are about the same. } Function IsValid : Boolean; Override; Protected Constructor Create(Params : TParamList); Override; Private FirstPrint : Double; FirstPrintExpires : TDateTime; L1Data : TGenericL1DataNode; TosData : TGenericTosDataNode; Function GetFirstPrint : Double; Procedure NewTosData; End; TL1User = Class(TGenericDataNode) Public Function IsValid : Boolean; Override; Protected Data : TGenericL1DataNode; Constructor Create(Params : TParamList); Override; Procedure NewDataRecieved; Virtual; Private End; // Always quoted in shares. TBestBidSize = Class(TL1User) Published Function GetInteger : Integer; Override; Protected Private End; TBestBidPrice = Class(TL1User) Published Function GetDouble : Double; Override; Protected Private End; // Always quoted in shares. TBestAskSize = Class(TL1User) Published Function GetInteger : Integer; Override; Protected Private End; TBestAskPrice = Class(TL1User) Published Function GetDouble : Double; Override; Protected Private End; THighPrice = Class(TL1User) Published Function GetDouble : Double; Override; Protected Private End; TLowPrice = Class(TL1User) Published Function GetDouble : Double; Override; Protected Private End; TClosePrice = Class(TL1User) Published Function GetDouble : Double; Override; Protected Private End; TBidExchange = Class(TL1User) Published Function GetString : String; Override; Protected Private End; TAskExchange = Class(TL1User) Published Function GetString : String; Override; Protected Private End; TTosUser = Class(TGenericDataNode) Public Function IsValid : Boolean; Override; Protected Data : TGenericTosDataNode; Constructor Create(Params : TParamList); Override; Procedure NewDataRecieved; Virtual; Private End; TTodaysClosePrice = Class(TTosUser) Published Function GetDouble : Double; Override; Protected Private End; TLastPrintPrice = Class(TTosUser) Published Function GetDouble : Double; Override; End; TLastPrintTime = Class(TTosUser) Published Function GetDouble : Double; Override; End; TLastPrintSize = Class(TTosUser) Published Function GetInteger : Integer; Override; End; TLastPrintExchange = Class(TTosUser) Published Function GetString : String; Override; End; TLastPrintFormT = Class(TTosUser) Published Function GetBoolean : Boolean; Override; End; TVolume = Class(TTosUser) Published Function GetInteger : Integer; Override; Protected Private End; TFundamentalUser = Class(TGenericDataNode) Public Function IsValid : Boolean; Override; Protected Data : TGenericFundamentalDataNode; Constructor Create(Params : TParamList); Override; Procedure NewDataRecieved; Virtual; Private FSymbol : String; Public Property Symbol : String Read FSymbol; End; TCompanyName = Class(TFundamentalUser) Published Function GetString : String; Override; End; TPrintableName = Class(TFundamentalUser) Published Function GetString : String; Override; Public Function IsValid : Boolean; Override; End; TListedExchange = Class(TFundamentalUser) Published Function GetString : String; Override; Protected Private Public Function IsValid : Boolean; Override; End; T52WeekHigh = Class(TFundamentalUser) Published Function GetDouble : Double; Override; End; T52WeekLow = Class(TFundamentalUser) Published Function GetDouble : Double; Override; End; T52HighDate = Class(TFundamentalUser) Published Function GetDouble : Double; Override; End; T52LowDate = Class(TFundamentalUser) Published Function GetDouble : Double; Override; End; // Between the (first print after the) open and the (first print after the) // close, this is the official open from the market. Otherwise this is the // last print, which is our best estimate of the next open price. // Warning: This can oscillate shortly after the close. This is especially // bad for certain AMEX issues which the ECNs report as closed at 1pm, and // the exchange reports as open until much later. TExpectedOpen = Class(TGenericDataNode) Private TosData : TGenericTosDataNode; OpenData : TGenericDataNode; AlwaysUseOpen : Boolean; Protected Constructor Create(Params : TParamList); Override; Public Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; { The provides the put volume divided by the call volume. If both are 0, we will report invalid. If the call volue is valid, this is a simple divided by operation. If the put volume is valid but the call volume is invalid then we will report a finite but very high result. Because the inputs are integers and the output is a double, we can select an output value for 1/0 that is greater than any value that would be generated by normal division. This is not derrived from TL1User because our code for IsValid is more complicated that that class allows. Actually, that is the case for a lot of the data nodes which are currently derrived from TL1User or TTosUser. Many of these should be rewritten. This data node is written from scratch with the benefit of experience of those mistakes. } TPutCallRatio = Class(TGenericDataNode) Private L1Data : TGenericL1DataNode; Protected Constructor Create(Params : TParamList); Override; Public Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; { This is just put volume + call volume. Potentially we could provide the put volume and the call volume to the database seperately. Potentially we could do a lot more with the data, but this is probably sufficient. And we've already been saving the put/call ratio for a while, so I'm going to stick with that. I don't want to move the put/call ratio code from this program to the distribution servers. } TPutCallVolume = Class(TL1User) Published Function GetInteger : Integer; Override; Protected Private End; { This is a way to say how likely it is that a print is invalid. If the print in between the bid and ask it is very reasonable. The further away it is, the more likely that it is not real. There are a lot of cases where this would have helped -- late prints and stuff -- but we've fixed those in other places. But this still should be useful. Especially in the oddsmaker, where we want to see prices that we can actually get. There are some cases where the bid and ask are messed up. Sometimes a bid or ask gets stuck on an ECN, and someone manages to get a print at the odd value. However, this is rare. Usually the bid and ask do a good job. If the spread is negative, we still look to see if the value is BETWEEN the bid and ask. These still look like reasonable prints. Anyting between the bid and ask (inclusive) gives a result of 0. Otherwise we compare the price to whichever one is closer to the price. We return the % difference in the normal way. The difference between the price and the bid or ask divided by the bid or ask * 100. } TDistanceFromNbbo = Class(TGenericDataNode) Private L1Data : TGenericL1DataNode; TosData : TGenericTosDataNode; Protected Constructor Create(Params : TParamList); Override; Public Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; { This is simply the high of the day less the low of the day. We choose to use the L1 data directly so that the high and low will be undefined before the open. } TTodaysRange = Class(TGenericDataNode) Private L1Data : TGenericL1DataNode; Protected Constructor Create(Params : TParamList); Override; Public Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; TPositionInRange = Class(TGenericDataNode) Private Data : Array [0..2] Of TGenericDataNode; Public Constructor Create(Params : TParamList); Override; Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; TTriColor = Class(TGenericDataNode) Private Value : TGenericDataNode; NegativeColor : TGenericDataNode; ZeroColor : TGenericDataNode; PositiveColor : TGenericDataNode; Function ColorData : TGenericDataNode; Public Constructor Create(Params : TParamList); Override; Function IsValid : Boolean; Override; Class Function FgFactory(Value : IGenericDataNodeFactory) : TGenericDataNodeFactory; Class Function BgFactory(Value : IGenericDataNodeFactory) : TGenericDataNodeFactory; Class Function Factory(Value, NegativeColor, ZeroColor, PositiveColor : IGenericDataNodeFactory) : TGenericDataNodeFactory; Published Function GetColor : TColor; Override; End; TPreviousValue = Class(TGenericDataNode) Private Factory : IGenericDataNodeFactory; Data : TGenericDataNode; PreviousValue : Double; PreviousValueValid : Boolean; CurrentValue : Double; CurrentValueValid : Boolean; Procedure Initialize; Procedure NewData; Public Constructor Create(Params : TParamList); Override; Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; TIgnoreNullChanges = Class(TGenericDataNode) Private Factory : IGenericDataNodeFactory; Data : TGenericDataNode; CurrentValue : Double; CurrentValueValid : Boolean; Procedure Initialize; Procedure NewData; Public Constructor Create(Params : TParamList); Override; Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; TTwoDoubleInputs = Class(TGenericDataNode) Private DataA, DataB : TGenericDataNode; Protected Current : Boolean; CurrentOutputValid : Boolean; Function InputsValid : Boolean; Procedure NewInput; Procedure EnsureCurrent; Procedure UpdateData(A, B : Double); Virtual; Abstract; Public Constructor Create(Params : TParamList); Override; Function IsValid : Boolean; Override; End; TTwoDoubleInputsBoolean = Class(TTwoDoubleInputs) Protected CurrentOutput : Boolean; Published Function GetBoolean : Boolean; Override; End; TTwoDoubleInputsDouble = Class(TTwoDoubleInputs) Protected CurrentOutput : Double; Published Function GetDouble : Double; Override; End; TAdd = Class(TTwoDoubleInputsDouble) Protected Procedure UpdateData(A, B : Double); Override; End; TSubtract = Class(TTwoDoubleInputsDouble) Protected Procedure UpdateData(A, B : Double); Override; End; TMultiply = Class(TTwoDoubleInputsDouble) Protected Procedure UpdateData(A, B : Double); Override; End; TDivide = Class(TTwoDoubleInputsDouble) Protected Procedure UpdateData(A, B : Double); Override; End; TLessThan = Class(TTwoDoubleInputsBoolean) Protected Procedure UpdateData(A, B : Double); Override; End; TEqualTo = Class(TTwoDoubleInputsBoolean) Protected Procedure UpdateData(A, B : Double); Override; End; TAtMost = Class(TTwoDoubleInputsBoolean) Protected Procedure UpdateData(A, B : Double); Override; End; TIntegerToDouble = Class(TGenericDataNode) Private Data : TGenericDataNode; Public Constructor Create(Params : TParamList); Override; Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; TVisiblePulses = Class(TGenericDataNode) Private PreviousStartTime : TDateTime; TimerEvent : ITimerEvent; PulsesRemaining : Integer; OutputHigh : Boolean; Procedure TimerInTimerThread; Procedure TimerInDataNodeThread; Procedure NewData; Public Constructor Create(Params : TParamList); Override; Function IsValid : Boolean; Override; Destructor Destroy; Override; Published Function GetBoolean : Boolean; Override; End; (*TPricePulses = Class(TGenericDataNode) Private Link : TDataNodeLink; PreviousStartTime : TDateTime; TimerEvent : ITimerEvent; PulsesRemaining : Integer; OutputHigh : Boolean; Procedure TimerInTimerThread; Procedure TimerInDataNodeThread; Procedure NewData; Protected Procedure CleanUpInThread; Override; Public Constructor Create(Params : TParamList); Override; Function IsValid : Boolean; Override; Published Function GetColor : TColor; Override; End; *) TStandardColors = (scBackground, scForeground, scRedFg, scGreenFg, scRedBg, scGreenBg); TStandardColor = Class(TGenericDataNode) Private Valid : Boolean; Value : TColor; Public Constructor Create(Params : TParamList); Override; Procedure SetValue(NewValue : TColor); Procedure ClearValue; Function IsValid : Boolean; Override; Published Function GetColor : TColor; Override; End; TStandardPalette = Class(TComponent) Private Links : Array [TStandardColors] Of TDataNodeLink; Nodes : Array [TStandardColors] Of TStandardColor; Procedure DoNothing; Procedure ReleaseOld; Procedure FindNew(Key : String); Public Constructor Create(AOwner : TComponent); Override; Destructor Destroy; Override; Procedure SetConfigurationKey(Key : TObject); Overload; Procedure SetConfigurationKey(Key : String); Overload; Procedure SetValue(Position : TStandardColors; Value : TColor); Function GetValue(Position : TStandardColors) : TColor; Procedure ClearValue(Position : TStandardColors); Procedure Sane; End; Function GetConfigurationOwnerKey(Owner : TObject) : String; Implementation Uses StringObjectHashTables, StandardPlaceHolders, SimpleMarketData, Variants, SysUtils, DateUtils, Math; //////////////////////////////////////////////////////////////////////// // TDoubleUpTick //////////////////////////////////////////////////////////////////////// Function TDoubleUpTick.IsValid : Boolean; Begin Result := AnswerValid End; Function TDoubleUpTick.GetBoolean : Boolean; Begin Result := Answer End; Constructor TDoubleUpTick.Create(Params : TParamList); Var InputFactory : IGenericDataNodeFactory; Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); InputFactory := IUnknown(Params[0]) As IGenericDataNodeFactory; InputFactory.Find(NewDataRecieved, Input, Link); AddAutoLink(Link); DoInCorrectThread(NewDataRecieved) End; Procedure TDoubleUpTick.NewDataRecieved; Var NewValue : Double; Begin If Input.IsValid Then Begin NewValue := Input.GetDouble; If PreviousValueValid Then If PreviousValue = NewValue Then { No Change. Do Nothing. } Else Begin AnswerValid := True; Answer := NewValue > PreviousValue; PreviousValue := NewValue; NotifyListeners End Else Begin PreviousValueValid := True; PreviousValue := NewValue End End Else Begin AnswerValid := False; NotifyListeners End End; //////////////////////////////////////////////////////////////////////// // TOpenPrice //////////////////////////////////////////////////////////////////////// { This has at least one big problem. If you start it up late, you will see bad values. This doesn't happen we have a valid value for the real open price from L1. So maybe things are no worse than if we didn't have this logic. When we don't have the real value we can fill in with a value that is right 90% of the time, or with nothing. Some code, like the expected open, assumes that no value means we haven't opened yet, not that there was a data problem, so in that case this is definately better. In the more general case, it would be better to report unknown rather than a really bad value. A lot of this logic is TAL specific. ESignal would show us the previous day's open, rather than 0. So this complicated logic would do nothing, but just copy whatever eSignal said that open value was. We really see this problem with TAL. We don't always get the open price when the datafeed is busy. } Function TOpenPrice.GetFirstPrint : Double; Begin If GetSubmitTime > FirstPrintExpires Then Result := 0 Else Result := FirstPrint End; Function TOpenPrice.GetDouble : Double; Begin { Because we are saving that value, it is always possible to miss the first print. If someone is responding to the first print, and they call us, there is a 50-50 chance that we will see the print. But this same problem would have happened with the real open price from the L1 data, even under ideal circumstances, so this is as good as it gets. } If L1Data.IsValid Then Result := L1Data.GetCurrent^.Open Else Result := 0.0; If (Result = 0.0) Then Result := GetFirstPrint; End; Function TOpenPrice.IsValid : Boolean; Begin Result := GetDouble <> 0.0 End; Constructor TOpenPrice.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Begin Assert(Length(Params) = 1, 'Expected params: (Symbol)'); Symbol := Params[0]; Inherited Create; If Not SymbolIsFuture(Symbol) Then Begin TGenericTosDataNode.Find(Symbol, NewTosData, TosData, Link); AddAutoLink(Link) End; TGenericL1DataNode.Find(Symbol, NotifyListeners, L1Data, Link); AddAutoLink(Link) End; Procedure TOpenPrice.NewTosData; Var Last : PTosData; Begin If (GetFirstPrint = 0) And TosData.IsValid Then Begin Last := TosData.GetLast; If (Last.EventType = etNewPrint) And Not Last.FormT Then Begin FirstPrint := Last.Price; FirstPrintExpires := Ceil(Last.Time) End End; NotifyListeners End; //////////////////////////////////////////////////////////////////////// // TL1User //////////////////////////////////////////////////////////////////////// Constructor TL1User.Create(Params : TParamList); Var Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); TGenericL1DataNode.Find(Params[0], NewDataRecieved, Data, Link); AddAutoLink(Link) End; Procedure TL1User.NewDataRecieved; Begin NotifyListeners End; Function TL1User.IsValid : Boolean; Begin Result := Data.IsValid End; //////////////////////////////////////////////////////////////////////// // Simple L1 Data Nodes //////////////////////////////////////////////////////////////////////// Function TBestBidSize.GetInteger : Integer; Begin Result := Data.GetCurrent.BidSize End; Function TBestBidPrice.GetDouble : Double; Begin Result := Data.GetCurrent.BidPrice End; Function TBestAskSize.GetInteger : Integer; Begin Result := Data.GetCurrent.AskSize End; Function TBestAskPrice.GetDouble : Double; Begin Result := Data.GetCurrent.AskPrice End; Function TClosePrice.GetDouble : Double; Begin Result := Data.GetCurrent.PrevClose End; Function TBidExchange.GetString : String; Begin Result := Data.GetCurrent.BidExchange End; Function TAskExchange.GetString : String; Begin Result := Data.GetCurrent.AskExchange End; Function THighPrice.GetDouble : Double; Begin Result := Data.GetCurrent.High End; Function TLowPrice.GetDouble : Double; Begin Result := Data.GetCurrent.Low End; Function TPutCallVolume.GetInteger : Integer; Var Current : PL1Data; Begin Current := Data.GetCurrent; Result := Current^.PutVolume + Current^.CallVolume End; //////////////////////////////////////////////////////////////////////// // TTosUser //////////////////////////////////////////////////////////////////////// Constructor TTosUser.Create(Params : TParamList); Var Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); TGenericTosDataNode.Find(Params[0], NewDataRecieved, Data, Link); AddAutoLink(Link) End; Procedure TTosUser.NewDataRecieved; Begin NotifyListeners End; Function TTosUser.IsValid : Boolean; Begin Result := Data.IsValid End; //////////////////////////////////////////////////////////////////////// // Simple TOS Data Nodes //////////////////////////////////////////////////////////////////////// Function TTodaysClosePrice.GetDouble : Double; Begin Result := Data.GetLast.TodaysClose End; Function TLastPrintPrice.GetDouble : Double; Begin Result := Data.GetLast.Price End; Function TLastPrintTime.GetDouble : Double; Begin Result := Data.GetLast.Time End; Function TLastPrintSize.GetInteger : Integer; Begin Result := Data.GetLast.Size End; Function TLastPrintExchange.GetString : String; Begin Result := Data.GetLast.Exchange End; Function TLastPrintFormT.GetBoolean : Boolean; Begin Result := Data.GetLast.FormT End; Function TVolume.GetInteger : Integer; Begin Result := Data.GetLast.Volume End; //////////////////////////////////////////////////////////////////////// // TFundamentalUser //////////////////////////////////////////////////////////////////////// Constructor TFundamentalUser.Create(Params : TParamList); Var Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); FSymbol := Params[0]; TGenericFundamentalDataNode.Find(FSymbol, NewDataRecieved, Data, Link); AddAutoLink(Link) End; Procedure TFundamentalUser.NewDataRecieved; Begin NotifyListeners End; Function TFundamentalUser.IsValid : Boolean; Begin Result := Data.IsValid End; //////////////////////////////////////////////////////////////////////// // Simple Fundamental Data Nodes //////////////////////////////////////////////////////////////////////// Function TCompanyName.GetString : String; Begin Result := Data.GetCurrent.Company End; Function TPrintableName.IsValid : Boolean; Begin Result := True; End; Function TPrintableName.GetString : String; Var CompanyName : String; Begin Result := Symbol; If Data.IsValid Then Begin CompanyName := Data.GetCurrent.Company; If CompanyName <> '' Then Result := CompanyName End End; Function TListedExchange.IsValid : Boolean; Begin If Symbol[1]='$' Then Result := True Else Result := Data.IsValid End; Function TListedExchange.GetString : String; Begin If Symbol[1]='$' Then Result := '$NDX' Else Result := Data.GetCurrent.ListedExchange End; Function T52WeekHigh.GetDouble : Double; Begin Result := Data.GetCurrent.High52Week End; Function T52WeekLow.GetDouble : Double; Begin Result := Data.GetCurrent.Low52Week End; Function T52HighDate.GetDouble : Double; Begin Result := Data.GetCurrent.High52WeekDate End; Function T52LowDate.GetDouble : Double; Begin Result := Data.GetCurrent.Low52WeekDate End; //////////////////////////////////////////////////////////////////////// // TExpectedOpen //////////////////////////////////////////////////////////////////////// Constructor TExpectedOpen.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Factory : IGenericDataNodeFactory; Begin Assert(Length(Params)=1, 'Expected params: (Symbol)'); Symbol := Params[0]; Inherited Create; AlwaysUseOpen := SymbolIsFuture(Symbol); Factory := TGenericDataNodeFactory.CreateWithArgs(TOpenPrice, Symbol); Factory.Find(NotifyListeners, OpenData, Link); AddAutoLink(Link); If Not AlwaysUseOpen Then Begin TGenericTosDataNode.Find(Symbol, NotifyListeners, TosData, Link); AddAutoLink(Link); End End; Function TExpectedOpen.IsValid : Boolean; Begin { This design is forced by the rules regarding TOpenPrice.IsValid. } Result := GetDouble <> 0 End; Function TExpectedOpen.GetDouble : Double; Var RealOpenPrice : Double; Begin RealOpenPrice := OpenData.GetDouble; If AlwaysUseOpen Then Result := RealOpenPrice Else If (TosData.GetLast^.FormT) Or (RealOpenPrice = 0) Then Result := TosData.GetLast^.Price Else Result := RealOpenPrice End; //////////////////////////////////////////////////////////////////////// // TPutCallRatio //////////////////////////////////////////////////////////////////////// Constructor TPutCallRatio.Create(Params : TParamList); Var Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); TGenericL1DataNode.Find(Params[0], NotifyListeners, L1Data, Link); AddAutoLink(Link) End; Function TPutCallRatio.IsValid : Boolean; Var Current : PL1Data; PutVolume, CallVolume : Integer; Begin Result := L1Data.IsValid; If Result Then Begin Current := L1Data.GetCurrent; PutVolume := Current^.PutVolume; CallVolume := Current^.CallVolume; Result := (PutVolume > -1) And (CallVolume > -1) And ((PutVolume > 0) Or (CallVolume > 0)) End End; Function TPutCallRatio.GetDouble : Double; Var Current : PL1Data; PutVolume, CallVolume : Integer; Begin If Not IsValid Then Result := 0.0 Else Begin Current := L1Data.GetCurrent; PutVolume := Current^.PutVolume; CallVolume := Current^.CallVolume; If CallVolume > 0 Then Result := PutVolume / CallVolume Else Result := MaxInt + 1.0 + PutVolume End End; //////////////////////////////////////////////////////////////////////// // TDistanceFromNbbo //////////////////////////////////////////////////////////////////////// Constructor TDistanceFromNbbo.Create(Params : TParamList); Var Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); TGenericL1DataNode.Find(Params[0], NotifyListeners, L1Data, Link); AddAutoLink(Link); TGenericTosDataNode.Find(Params[0], NotifyListeners, TosData, Link); AddAutoLink(Link) End; Function TDistanceFromNbbo.IsValid : Boolean; Var Current : PL1Data; Begin Result := L1Data.IsValid And TosData.IsValid; If Result Then Begin Current := L1Data.GetCurrent; Result := (Current^.BidPrice > 0) And (Current^.AskPrice > 0) End End; Function TDistanceFromNbbo.GetDouble : Double; Var Current : PL1Data; Price, Bid, Ask, RangeTop, RangeBottom : Double; Begin If Not IsValid Then Result := 0.0 Else Begin Current := L1Data.GetCurrent; Bid := Current^.BidPrice; Ask := Current^.AskPrice; RangeTop := Max(Bid, Ask); RangeBottom := Min(Bid, Ask); Price := TosData.GetLast^.Price; If Price > RangeTop Then Result := (Price - RangeTop) / RangeTop * 100.0 Else If Price < RangeBottom Then Result := (RangeBottom - Price) / RangeBottom * 100.0 Else Result := 0.0 End End; //////////////////////////////////////////////////////////////////////// // TTodaysRange //////////////////////////////////////////////////////////////////////// Constructor TTodaysRange.Create(Params : TParamList); Var Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); TGenericL1DataNode.Find(Params[0], NotifyListeners, L1Data, Link); AddAutoLink(Link) End; Function TTodaysRange.IsValid : Boolean; Var Current : PL1Data; Begin Result := L1Data.IsValid; If Result Then Begin Current := L1Data.GetCurrent; Result := (Current^.High > 0) And (Current^.Low > 0) End End; Function TTodaysRange.GetDouble : Double; Var Current : PL1Data; Begin If Not IsValid Then Result := 0.0 Else Begin Current := L1Data.GetCurrent; Result := Current^.High - Current^.Low End End; //////////////////////////////////////////////////////////////////////// // TPositionInRange //////////////////////////////////////////////////////////////////////// Constructor TPositionInRange.Create(Params : TParamList); Var I : Integer; Factory : IGenericDataNodeFactory; Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 3); For I := 0 To 2 Do Begin Factory := IUnknown(Params[I]) As IGenericDataNodeFactory; Factory.Find(NotifyListeners, Data[I], Link); AddAutoLink(Link) End End; Function TPositionInRange.IsValid : Boolean; Var I : Integer; Begin For I := 0 To 2 Do If Not Data[I].IsValid Then Begin Result := False; Exit End; Result := Data[0].GetDouble < Data[2].GetDouble End; Function TPositionInRange.GetDouble : Double; Var Low, Current, High : Double; Begin Try Low := Data[0].GetDouble; Current := Data[1].GetDouble; High := Data[2].GetDouble; Result := (Current - Low) / (High - Low); Except //On EInvalidOp Do // Result := 0.5; //On EZeroDivide Do On Exception Do Result := 0.5 End End; //////////////////////////////////////////////////////////////////////// // TTriColor //////////////////////////////////////////////////////////////////////// Class Function TTriColor.FgFactory(Value : IGenericDataNodeFactory) : TGenericDataNodeFactory; Begin Result := Factory(Value, TGenericDataNodeFactory.FindFactory('StandardRedFgColor'), TGenericDataNodeFactory.FindFactory('StandardForegroundColor'), TGenericDataNodeFactory.FindFactory('StandardGreenFgColor')) End; Class Function TTriColor.BgFactory(Value : IGenericDataNodeFactory) : TGenericDataNodeFactory; Begin Result := Factory(Value, TGenericDataNodeFactory.FindFactory('StandardRedBgColor'), TGenericDataNodeFactory.FindFactory('StandardBackgroundColor'), TGenericDataNodeFactory.FindFactory('StandardGreenBgColor')) End; Class Function TTriColor.Factory(Value, NegativeColor, ZeroColor, PositiveColor : IGenericDataNodeFactory) : TGenericDataNodeFactory; Begin Result := TGenericDataNodeFactory.CreateWithArgs(TTriColor, Value, NegativeColor, ZeroColor, PositiveColor) End; Constructor TTriColor.Create(Params : TParamList); Var Factory : IGenericDataNodeFactory; Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 4); Factory := IUnknown(Params[0]) As IGenericDataNodeFactory; Factory.Find(NotifyListeners, Value, Link); AddAutoLink(Link); Factory := IUnknown(Params[1]) As IGenericDataNodeFactory; Factory.Find(NotifyListeners, NegativeColor, Link); AddAutoLink(Link); Factory := IUnknown(Params[2]) As IGenericDataNodeFactory; Factory.Find(NotifyListeners, ZeroColor, Link); AddAutoLink(Link); Factory := IUnknown(Params[3]) As IGenericDataNodeFactory; Factory.Find(NotifyListeners, PositiveColor, Link); AddAutoLink(Link) End; Function TTriColor.ColorData : TGenericDataNode; Var V : Double; Begin V := 0; If Value.IsValid Then V := Value.GetDouble; If V < 0 Then Result := NegativeColor Else If V > 0 Then Result := PositiveColor Else Result := ZeroColor End; Function TTriColor.IsValid : Boolean; Begin Result := ColorData.IsValid End; Function TTriColor.GetColor : TColor; Begin Result := ColorData.GetColor End; //////////////////////////////////////////////////////////////////////// // TPreviousValue //////////////////////////////////////////////////////////////////////// Constructor TPreviousValue.Create(Params : TParamList); Begin Inherited Create; Assert(Length(Params) = 1); Factory := IUnknown(Params[0]) As IGenericDataNodeFactory; DoInCorrectThread(Initialize) End; Procedure TPreviousValue.Initialize; Var Link : TDataNodeLink; Begin Factory.Find(NewData, Data, Link); Factory := Nil; CurrentValueValid := Data.IsValid; If CurrentValueValid Then CurrentValue := Data.GetDouble; Link.SetReceiveInput(True); AddAutoLink(Link) End; Procedure TPreviousValue.NewData; Begin PreviousValue := CurrentValue; PreviousValueValid := CurrentValueValid; CurrentValueValid := Data.IsValid; If CurrentValueValid Then CurrentValue := Data.GetDouble; NotifyListeners End; Function TPreviousValue.IsValid : Boolean; Begin Result := PreviousValueValid End; Function TPreviousValue.GetDouble : Double; Begin Result := PreviousValue End; //////////////////////////////////////////////////////////////////////// // TIgnoreNullChanges //////////////////////////////////////////////////////////////////////// Procedure TIgnoreNullChanges.Initialize; Var Link : TDataNodeLink; Begin Factory.Find(NewData, Data, Link); Factory := Nil; CurrentValueValid := Data.IsValid; If CurrentValueValid Then CurrentValue := Data.GetDouble; Link.SetReceiveInput(True); AddAutoLink(Link) End; Procedure TIgnoreNullChanges.NewData; Var NewValue : Double; NewValueValid : Boolean; ValueChanged : Boolean; Begin NewValueValid := Data.IsValid; If NewValueValid Then NewValue := Data.GetDouble Else NewValue := 0; ValueChanged := (NewValueValid <> CurrentValueValid) Or (NewValueValid And CurrentValueValid And (NewValue <> CurrentValue)); If ValueChanged Then Begin CurrentValueValid := NewValueValid; CurrentValue := NewValue; NotifyListeners End End; Constructor TIgnoreNullChanges.Create(Params : TParamList); Begin Inherited Create; Assert(Length(Params) = 1); Factory := IUnknown(Params[0]) As IGenericDataNodeFactory; DoInCorrectThread(Initialize) End; Function TIgnoreNullChanges.IsValid : Boolean; Begin Result := CurrentValueValid End; Function TIgnoreNullChanges.GetDouble : Double; Begin Result := CurrentValue End; //////////////////////////////////////////////////////////////////////// // TTwoDoubleInputs //////////////////////////////////////////////////////////////////////// Procedure TTwoDoubleInputs.NewInput; Begin Current := False; NotifyListeners End; Procedure TTwoDoubleInputs.EnsureCurrent; Begin If Not Current Then If InputsValid Then Begin CurrentOutputValid := True; UpdateData(DataA.GetDouble, DataB.GetDouble) End Else CurrentOutputValid := False; Current := True End; Function TTwoDoubleInputs.InputsValid : Boolean; Begin Result := DataA.IsValid And DataB.IsValid End; Constructor TTwoDoubleInputs.Create(Params : TParamList); Var Factory : IGenericDataNodeFactory; Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 2); Factory := IUnknown(Params[0]) As IGenericDataNodeFactory; Factory.Find(NewInput, DataA, Link); AddAutoLink(Link); Factory := IUnknown(Params[1]) As IGenericDataNodeFactory; Factory.Find(NewInput, DataB, Link); AddAutoLink(Link) End; Function TTwoDoubleInputs.IsValid : Boolean; Begin EnsureCurrent; Result := CurrentOutputValid End; //////////////////////////////////////////////////////////////////////// // TTwoDoubleInputsBoolean //////////////////////////////////////////////////////////////////////// Function TTwoDoubleInputsBoolean.GetBoolean : Boolean; Begin EnsureCurrent; Result := CurrentOutput End; //////////////////////////////////////////////////////////////////////// // TTwoDoubleInputsDouble //////////////////////////////////////////////////////////////////////// Function TTwoDoubleInputsDouble.GetDouble : Double; Begin EnsureCurrent; Result := CurrentOutput End; //////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////// Procedure TAdd.UpdateData(A, B : Double); Begin CurrentOutput := A + B End; Procedure TSubtract.UpdateData(A, B : Double); Begin CurrentOutput := A - B End; Procedure TMultiply.UpdateData(A, B : Double); Begin CurrentOutput := A * B End; Procedure TDivide.UpdateData(A, B : Double); Begin If B = 0 Then CurrentOutputValid := False Else CurrentOutput := A / B End; Procedure TLessThan.UpdateData(A, B : Double); Begin CurrentOutput := A < B End; Procedure TEqualTo.UpdateData(A, B : Double); Begin CurrentOutput := A = B End; Procedure TAtMost.UpdateData(A, B : Double); Begin CurrentOutput := A <= B End; //////////////////////////////////////////////////////////////////////// // TIntegerToDouble //////////////////////////////////////////////////////////////////////// Constructor TIntegerToDouble.Create(Params : TParamList); Var Factory : IGenericDataNodeFactory; Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); Factory := IUnknown(Params[0]) As IGenericDataNodeFactory; Factory.Find(NotifyListeners, Data, Link); AddAutoLink(Link) End; Function TIntegerToDouble.IsValid : Boolean; Begin Result := Data.IsValid End; Function TIntegerToDouble.GetDouble : Double; Begin Result := Data.GetInteger End; //////////////////////////////////////////////////////////////////////// // TVisiblePulses //////////////////////////////////////////////////////////////////////// Destructor TVisiblePulses.Destroy; Begin If Assigned(TimerEvent) Then TimerEvent.Clear; Inherited End; Constructor TVisiblePulses.Create(Params : TParamList); Var Factory : IGenericDataNodeFactory; Node : TGenericDataNode; Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); Factory := IUnknown(Params[0]) As IGenericDataNodeFactory; Factory.Find(NewData, Node, Link); // Throw away the node. We don't care about the data. AddAutoLink(Link) End; Function TVisiblePulses.IsValid : Boolean; Begin // We only look at the notifications of changes, not the data. // There is no reason to be invalid. Result := True End; Function TVisiblePulses.GetBoolean : Boolean; Begin Result := OutputHigh End; Procedure TVisiblePulses.TimerInTimerThread; Begin AddToThreadQueue(TimerInDataNodeThread) End; Procedure TVisiblePulses.TimerInDataNodeThread; Const OnTime = 300; OffTime = 200; Begin If OutputHigh Then Begin OutputHigh := False; PreviousStartTime := PreviousStartTime + OffTime / MsToTime; TimerEvent := RequestCallbackAt(TimerInTimerThread, PreviousStartTime); NotifyListeners End Else If PulsesRemaining > 0 Then Begin Dec(PulsesRemaining); OutputHigh := True; PreviousStartTime := PreviousStartTime + OnTime / MsToTime; TimerEvent := RequestCallbackAt(TimerInTimerThread, PreviousStartTime); NotifyListeners End Else PreviousStartTime := 0 End; Procedure TVisiblePulses.NewData; Const MaxPulseQueue = 10; Begin If PulsesRemaining < MaxPulseQueue Then Inc(PulsesRemaining); If PreviousStartTime = 0 Then Begin PreviousStartTime := Now; TimerInDataNodeThread End End; //////////////////////////////////////////////////////////////////////// // TStandardColor //////////////////////////////////////////////////////////////////////// Constructor TStandardColor.Create(Params : TParamList); Begin Inherited Create; // Nothing. The parameters are only used to make unique keys for the factory. End; Procedure TStandardColor.SetValue(NewValue : TColor); Begin Value := ColorToRGB(NewValue); Valid := True; DoInCorrectThread(NotifyListeners) End; Procedure TStandardColor.ClearValue; Begin Valid := False; DoInCorrectThread(NotifyListeners) End; Function TStandardColor.IsValid : Boolean; Begin Result := Valid; End; Function TStandardColor.GetColor : TColor; Begin Result := Value; End; //////////////////////////////////////////////////////////////////////// // TStandardPalette //////////////////////////////////////////////////////////////////////// Function GetConfigurationOwnerKey(Owner : TObject) : String; Begin Result := Format('$%.8x', [Integer(Owner)]) End; Procedure TStandardPalette.Sane; Begin Nodes[scBackground].SetValue($00c0c0c0); Nodes[scForeground].SetValue(clBlack); Nodes[scRedFg].SetValue($000000ff); Nodes[scGreenFg].SetValue($00248024); Nodes[scRedBg].SetValue($000000ff); Nodes[scGreenBg].SetValue($0000ff00) End; Procedure TStandardPalette.ReleaseOld; Var I : TStandardColors; Begin For I := Low(TStandardColors) To High(TStandardColors) Do Begin If Assigned(Links[I]) Then Links[I].Release; Links[I] := Nil; Nodes[I] := Nil End End; Procedure TStandardPalette.FindNew(Key : String); Var Factory : TGenericDataNodeFactory; I : TStandardColors; Node : TGenericDataNode; Begin For I := Low(TStandardColors) To High(TStandardColors) Do Begin Factory := TGenericDataNodeFactory.CreateWithArgs(TStandardColor, Key, I); Factory.Find(DoNothing, Node, Links[I]); Nodes[I] := Node As TStandardColor; Factory.Free End End; Procedure TStandardPalette.DoNothing; Begin Assert(False) End; Constructor TStandardPalette.Create(AOwner : TComponent); Begin Inherited; FindNew(GetConfigurationOwnerKey(AOwner)) End; Destructor TStandardPalette.Destroy; Begin ReleaseOld; Inherited End; Procedure TStandardPalette.SetConfigurationKey(Key : TObject); Begin SetConfigurationKey(GetConfigurationOwnerKey(Key)) End; Procedure TStandardPalette.SetConfigurationKey(Key : String); Begin ReleaseOld; FindNew(Key) End; Procedure TStandardPalette.SetValue(Position : TStandardColors; Value : TColor); Begin Nodes[Position].SetValue(Value) End; Function TStandardPalette.GetValue(Position : TStandardColors) : TColor; Begin Result := Nodes[Position].GetColor End; Procedure TStandardPalette.ClearValue(Position : TStandardColors); Begin Nodes[Position].ClearValue End; Procedure StoreStandardPalette; Var StandardPlaceholder : IPlaceHolder; //I : TStandardColors; Begin StandardPlaceholder := TPlaceHolder.Create(ConfigurationOwnerKey, ''); TGenericDataNodeFactory.StoreFactory('StandardBackgroundColor', TGenericDataNodeFactory.CreateWithArgs(TStandardColor, StandardPlaceholder, scBackground)); TGenericDataNodeFactory.StoreFactory('StandardForegroundColor', TGenericDataNodeFactory.CreateWithArgs(TStandardColor, StandardPlaceholder, scForeground)); TGenericDataNodeFactory.StoreFactory('StandardRedFgColor', TGenericDataNodeFactory.CreateWithArgs(TStandardColor, StandardPlaceholder, scRedFg)); TGenericDataNodeFactory.StoreFactory('StandardGreenFgColor', TGenericDataNodeFactory.CreateWithArgs(TStandardColor, StandardPlaceholder, scGreenFg)); TGenericDataNodeFactory.StoreFactory('StandardRedBgColor', TGenericDataNodeFactory.CreateWithArgs(TStandardColor, StandardPlaceholder, scRedBg)); TGenericDataNodeFactory.StoreFactory('StandardGreenBgColor', TGenericDataNodeFactory.CreateWithArgs(TStandardColor, StandardPlaceholder, scGreenBg)) End; //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Initialization TGenericDataNodeFactory.StoreStandardFactory('BidSize', TBestBidSize); TGenericDataNodeFactory.StoreStandardFactory('BidPrice', TBestBidPrice); TGenericDataNodeFactory.StoreStandardFactory('AskSize', TBestAskSize); TGenericDataNodeFactory.StoreStandardFactory('AskPrice', TBestAskPrice); TGenericDataNodeFactory.StoreStandardFactory('Volume', TVolume); TGenericDataNodeFactory.StoreStandardFactory('OpenPrice', TOpenPrice); TGenericDataNodeFactory.StoreStandardFactory('ClosePrice', TClosePrice); TGenericDataNodeFactory.StoreStandardFactory('TodaysClosePrice', TTodaysClosePrice); TGenericDataNodeFactory.StoreStandardFactory('BidExchange', TBidExchange); TGenericDataNodeFactory.StoreStandardFactory('AskExchange', TAskExchange); TGenericDataNodeFactory.StoreStandardFactory('ListedExchange', TListedExchange); TGenericDataNodeFactory.StoreStandardFactory('LastPrintExchange', TLastPrintExchange); TGenericDataNodeFactory.StoreStandardFactory('LastPrintFormT', TLastPrintFormT); TGenericDataNodeFactory.StoreStandardFactory('HighPrice', THighPrice); TGenericDataNodeFactory.StoreStandardFactory('LowPrice', TLowPrice); TGenericDataNodeFactory.StoreFactory('BidTick', TGenericDataNodeFactory.CreateWithArgs(TDoubleUpTick, TGenericDataNodeFactory.FindFactory('BidPrice'))); TGenericDataNodeFactory.StoreStandardFactory('LastPrintPrice', TLastPrintPrice); TGenericDataNodeFactory.StoreStandardFactory('LastPrintTime', TLastPrintTime); TGenericDataNodeFactory.StoreStandardFactory('LastPrintSize', TLastPrintSize); TGenericDataNodeFactory.StoreStandardFactory('Symbol', TConstantStringDataNode); TGenericDataNodeFactory.StoreStandardFactory('CompanyName', TCompanyName); TGenericDataNodeFactory.StoreStandardFactory('PrintableName', TPrintableName); TGenericDataNodeFactory.StoreStandardFactory('52WeekHigh', T52WeekHigh); TGenericDataNodeFactory.StoreStandardFactory('52WeekLow', T52WeekLow); //TGenericDataNodeFactory.StoreFactory('DayRange', TGenericDataNodeFactory.CreateWithArgs(TPositionInRange, TGenericDataNodeFactory.FindFactory('LowPrice'), TGenericDataNodeFactory.FindFactory('LastPrintPrice'), TGenericDataNodeFactory.FindFactory('HighPrice'))); TGenericDataNodeFactory.StoreFactory('YearRange', TGenericDataNodeFactory.CreateWithArgs(TPositionInRange, TGenericDataNodeFactory.FindFactory('52WeekLow'), TGenericDataNodeFactory.FindFactory('LastPrintPrice'), TGenericDataNodeFactory.FindFactory('52WeekHigh'))); TGenericDataNodeFactory.StoreFactory('ChangeFromClose', TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('LastPrintPrice'), TGenericDataNodeFactory.FindFactory('ClosePrice'))); TGenericDataNodeFactory.StoreFactory('ChangeFromOpen', TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('LastPrintPrice'), TGenericDataNodeFactory.FindFactory('OpenPrice'))); TGenericDataNodeFactory.StoreStandardFactory('52HighDate', T52HighDate); TGenericDataNodeFactory.StoreStandardFactory('52LowDate', T52LowDate); TGenericDataNodeFactory.StoreStandardFactory('ExpectedOpen', TExpectedOpen); TGenericDataNodeFactory.StoreFactory('Gap', TGenericDataNodeFactory.CreateWithArgs(TSubtract, TGenericDataNodeFactory.FindFactory('ExpectedOpen'), TGenericDataNodeFactory.FindFactory('MostRecentClose'))); TGenericDataNodeFactory.StoreStandardFactory('PutCallRatio', TPutCallRatio); TGenericDataNodeFactory.StoreStandardFactory('PutCallVolume', TPutCallVolume); TGenericDataNodeFactory.StoreStandardFactory('DistanceFromNbbo', TDistanceFromNbbo); TGenericDataNodeFactory.StoreStandardFactory('TodaysRange', TTodaysRange); StoreStandardPalette; End.