Unit RelativeVolume; { This is used in alerts filters. This compares the current volume for today to the average volume at this time of day. } Interface Implementation Uses DataNodes, GenericDataNodes, MarketData, AverageHistoricalVolume, DateUtils, SysUtils; //////////////////////////////////////////////////////////////////////// // TSafeVolume //////////////////////////////////////////////////////////////////////// Type TSafeVolume = TVolume; (* // ESignal has some strange rules about volume before the first // print of the day. The problem is that there is no good way // to determine if a stock has printed yet, especially in the // premarket. We just say that we don't know the volume until // the first time we see a print. Type TSafeVolume = Class(TGenericDataNode) Private LastDate : TDateTime; VolumeIsGood : Boolean; TosData : TGenericTosDataNode; L1Data : TGenericL1DataNode; Procedure NewTos; Procedure NewL1; Protected Constructor Create(Params : TParamList); Override; Public Function IsValid : Boolean; Override; Published Function GetInteger : Integer; Override; End; Procedure TSafeVolume.NewTos; Begin If LastDate <> DateOf(GetSubmitTime) Then VolumeIsGood := False; LastDate := DateOf(GetSubmitTime); If TosData.IsValid Then If TosData.GetLast.EventType = etNewPrint Then VolumeIsGood := True; NotifyListeners End; Procedure TSafeVolume.NewL1; Var NewDate : TDateTime; Begin NewDate := DateOf(GetSubmitTime); If NewDate <> LastDate Then Begin LastDate := NewDate; VolumeIsGood := False End; NotifyListeners End; Constructor TSafeVolume.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Begin Assert(Length(Params) = 1); Inherited Create; Symbol := Params[0]; TGenericTosDataNode.Find(Symbol, NewTos, TosData, Link); AddAutoLink(Link); TGenericL1DataNode.Find(Symbol, NewL1, L1Data, Link); AddAutoLink(Link) End; Function TSafeVolume.IsValid : Boolean; Begin Result := VolumeIsGood And L1Data.IsValid End; Function TSafeVolume.GetInteger : Integer; Begin If IsValid Then Result := L1Data.GetCurrent.Volume Else Result := 0 End; *) //////////////////////////////////////////////////////////////////////// // TRelativeVolume //////////////////////////////////////////////////////////////////////// // This never notifies it's listeners. // If it tried to, it would do so on every TOS or L1 event for the // symbol. But it really updates continuously, because it takes // the current time into consideration. // The only expected use is info for an existing alert, so this // is not a problem. Type TRelativeVolume = Class(TGenericDataNode) Private CurrentVolume : TGenericDataNode; ExpectedVolume : TAverageHistoricalData; VolumePoints : Array [0..AHVPeriods] Of Integer; Initialized : Boolean; Procedure UpdateExpectedVolume; Protected Constructor Create(Params : TParamList); Override; Public Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; Procedure TRelativeVolume.UpdateExpectedVolume; Var I : Integer; Begin Initialized := True; If ExpectedVolume.IsValid Then Begin VolumePoints[0] := ExpectedVolume.GetPreMarketVolume; For I := 1 To AHVPeriods Do VolumePoints[I] := VolumePoints[Pred(I)] + ExpectedVolume.GetVolume(I) End End; Constructor TRelativeVolume.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Factory : IGenericDataNodeFactory; Begin Assert(Length(Params) = 1); Inherited Create; Symbol := Params[0]; Factory := TGenericDataNodeFactory.CreateWithArgs(TSafeVolume, Symbol); Factory.Find(Nil, CurrentVolume, Link); AddAutoLink(Link); TAverageHistoricalData.Find(Symbol, UpdateExpectedVolume, ExpectedVolume, Link); AddAutoLink(Link) End; Function TRelativeVolume.IsValid : Boolean; Begin Result := (TimeOf(GetSubmitTime) > AHVStartTime) And ExpectedVolume.IsValid And CurrentVolume.IsValid End; Function TRelativeVolume.GetDouble : Double; Var Period : Double; ExpectedValue : Double; ActualValue : Integer; LowerPeriod : Integer; Begin If Not Initialized Then UpdateExpectedVolume; Period := (TimeOf(GetSubmitTime) - AHVStartTime) / AHVPeriod; If Period < 0 Then // This should never have passed IsValid, but we don't want an // exception here. Result := 0 Else Begin If Period > AHVPeriods Then ExpectedValue := VolumePoints[AHVPeriods] Else Begin LowerPeriod := Trunc(Period); ExpectedValue := VolumePoints[LowerPeriod] * (1 - Frac(Period)) + VolumePoints[Succ(LowerPeriod)] * Frac(Period) End; ActualValue := CurrentVolume.GetInteger; Try Result := ActualValue / ExpectedValue Except On Exception Do // Assume divided by 0 or overflow. If ActualValue = 0 Then // 0/0 -> 1 because 1 means exactly what was expected! Result := 1 Else // A good enough approximation of +infinity based on // the way it will be used. This will // typically be compared to a reasonable value, and // 999999 is above all reasonable values. Result := 999999 End End End; //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Initialization TGenericDataNodeFactory.StoreStandardFactory('RelVol', TRelativeVolume); End.