Unit Correlation; Interface Implementation Uses DataNodes, GenericDataNodes, GenericL1DataNode, GenericTosDataNode, StandardPlaceHolders, AlertBase, Prices, SimpleMarketData, SysUtils, DateUtils, StrUtils, Math; //////////////////////////////////////////////////////////////////////// // TFuturesClose //////////////////////////////////////////////////////////////////////// { This is (ideally) just like the normal Previous Close between 4:55am and the close. By default Previous Close updates at the close for futures and at 4:55 for everything else. So, between those times, we hold onto the old value to make the the futures consistant with the normal stocks. } Type TFuturesClose = Class(TGenericDataNode) Published Function GetDouble : Double; Override; Public Function IsValid : Boolean; Override; Protected Data : TGenericL1DataNode; Constructor Create(Params : TParamList); Override; Private CachedValid : Boolean; CachedValue : Double; Procedure NewDataRecieved; End; Constructor TFuturesClose.Create(Params : TParamList); Var Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 1); TGenericL1DataNode.Find(Params[0], NewDataRecieved, Data, Link); AddAutoLink(Link) End; Function TFuturesClose.IsValid : Boolean; Begin Result := CachedValid End; Function TFuturesClose.GetDouble : Double; Begin Result := CachedValue End; Procedure TFuturesClose.NewDataRecieved; { This part could be done a lot better. For futures the previous close price updates immediately at the close. For normal stocks, it updates at about 4:55am. We need to make the futures update at the same time as the normal stocks. Ideally we'd ask the datafeed when the close was updated. But that would only solve half the problem. We'd still need to keep the prevous value ourselves. Between midnight and noon, an hour before the normal close, we read the close vlaue. Between noon and midnight, we ignore any changes. We never throw out a value when the datafeed goes down. These are a lot of approximatations, but they should do well enough. Between midnight and 4:55 this does not match the closing price of normal stocks. For the most part this is okay because we expect to use this in conjunction with a print from a normal stock, which will not happen during that time. } Const LastUpdateTime = 0.5; Var Current : PL1Data; Begin If Not CachedValid Then If (TimeOf(GetSubmitTime) <= LastUpdateTime) And Data.IsValid Then Begin Current := Data.GetCurrent; If (Current^.PrevClose > 0) And (Current^.PrevClose <> CachedValue) Then Begin CachedValue := Current^.PrevClose; CachedValid := True; NotifyListeners End End End; //////////////////////////////////////////////////////////////////////// // TCorrelationCommon // // The basic comparison algorithm is the same for all of these alerts. // The inputs are slightly different. TSectorBreakout can change its // inputs at various times, which brings up most of the complication. //////////////////////////////////////////////////////////////////////// Type TCorrelationCommon = Class(TAlert) Private FUp : Boolean; Primed : Boolean; LastReported : Integer; MainStockBaselineData : TGenericL1DataNode; MainStockCurrentData, ComparisonCurrentData : TGenericTosDataNode; Procedure CheckMainStockPrice; Protected FSymbol : String; ComparisonMData : TGenericDataNode; CurrentComparisonSymbol : String; CurrentComparisonM : Double; Procedure FindMainStockData; Function InputsValidP : Boolean; Virtual; Abstract; Function GetMainBaseline: Double; Virtual; Abstract; // The starting price. Function GetComparisonBaseline: Double; Virtual; Abstract; // The starting price. Function BurritoDump : String; Virtual; Abstract; Constructor Create(Params : TParamList); Override; Procedure ResetAll; End; Procedure TCorrelationCommon.ResetAll; Begin Primed := False; LastReported := 0; CheckMainStockPrice End; Constructor TCorrelationCommon.Create(Params : TParamList); Begin Assert(Length(Params) >= 2, 'Expected: (Symbol, Up, ...)'); FSymbol := Params[0]; FUp := Params[1]; Inherited Create End; Procedure TCorrelationCommon.FindMainStockData; Var Link : TDataNodeLink; Begin { TCorrelationCommon.FindMainStockData } If Not Assigned(MainStockCurrentData) Then Begin TGenericTosDataNode.Find(FSymbol, CheckMainStockPrice, MainStockCurrentData, Link); Link.SetReceiveInput(True); AddAutoLink(Link) End; If Not Assigned(MainStockBaselineData) Then Begin TGenericL1DataNode.Find(FSymbol, Nil, MainStockBaselineData, Link); AddAutoLink(Link) End End; { TCorrelationCommon.FindMainStockData } Procedure TCorrelationCommon.CheckMainStockPrice; Const StartTime = 5.25 / 24.0; // Really just trying to avoid the switchover between yesterday and today. Var ComparisonStart : Double; CurrentExpectations : Double; CurrentPercentAboveExpectations : Double; PossibleReport : Integer; Description : String; Begin If TimeOf(Self.GetSubmitTime) < StartTime Then Begin LastReported := 0; Primed := False End ELse If InputsValidP Then Try ComparisonStart := GetComparisonBaseline; CurrentExpectations := ((ComparisonCurrentData.GetLast^.Price - ComparisonStart) / ComparisonStart * CurrentComparisonM + 1) * GetMainBaseline; CurrentPercentAboveExpectations := (MainStockCurrentData.GetLast^.Price - CurrentExpectations) / Abs(CurrentExpectations) * 100.0; PossibleReport := Trunc(CurrentPercentAboveExpectations); { Always goes toward 0. This works because we never report a value less than 0. } If Not FUp Then PossibleReport := - PossibleReport; If PossibleReport > LastReported + 50 Then { We jumped up by more than 50% at once. This is probably not real. } LastReported := PossibleReport Else If PossibleReport > LastReported Then Begin LastReported := PossibleReport; If Primed Then Begin Description := Format('Trading %d%% %s expectations based on %s', [LastReported, IfThen(FUp, 'Above', 'Below'), CurrentComparisonSymbol]); //If LastReported >= 100 Then // Description := Description + ' ' + BurritoDump; Report(Description, LastReported) End End Else If PossibleReport + 25 < LastReported Then { We dropped by 25%. Presumably the previous one was a bad print, so we don't want to just cut off the stock from all future reports. In the rare cases when we legitimately drop by 25%, it's okay to repeat some alerts. This is still interesting. If the current print, not the previous print, is the eroneous print, we may see some additional alerts. But not a lot, and this is unavoidable. You to try to set primed to false, but that would only help in the case when the bad print was in the main stock, not the comparison stock. } LastReported := Max(0, PossibleReport); Primed := True Except Primed := False; End Else Primed := False; End; //////////////////////////////////////////////////////////////////////// // TSectorBreakout //////////////////////////////////////////////////////////////////////// Type TSectorBreakout = Class(TCorrelationCommon) Private FFromTheClose : Boolean; ComparisonBaselineData : TGenericL1DataNode; ComparisonSymbolData : TGenericDataNode; ComparisonBaselineDataLink, ComparisonCurrentDataLink : TDataNodeLink; Function StartValue(Data : TGenericL1DataNode) : Double; Procedure FindAllInputs; Procedure FindComparisonData(Const Symbol : String); Procedure ReleaseComparisonData; Protected Function InputsValidP : Boolean; Override; Function GetMainBaseline: Double; Override; Function GetComparisonBaseline: Double; Override; Constructor Create(Params : TParamList); Override; Function BurritoDump : String; Override; Public Destructor Destroy; Override; End; Destructor TSectorBreakout.Destroy; Begin ReleaseComparisonData; Inherited End; Procedure TSectorBreakout.FindAllInputs; Procedure FindMData; Var Link : TDataNodeLink; Factory : IGenericDataNodeFactory; Begin { FindMData } If Not Assigned(ComparisonMData) Then Begin Factory := TGenericDataNodeFactory.FindFactory('CorrelationM').Duplicate; Factory.SetValue(SymbolNameKey, FSymbol); Factory.Find(FindAllInputs, ComparisonMData, Link); Link.SetReceiveInput(True); AddAutoLink(Link) End End; { FindMData } Var NewComparisonSymbol : String; NewComparisonM : Double; StartFromScratch : Boolean; Begin { TSectorBreakout.FindAllInputs } If SymbolIsIndex(FSymbol) And Not FFromTheClose Then { The open data for an index is typically wrong. The field labeled open typically has a copy of the previous close. } Exit; If ComparisonSymbolData.IsValid Then NewComparisonSymbol := ComparisonSymbolData.GetString Else NewComparisonSymbol := ''; NewComparisonM := 0; If NewComparisonSymbol <> '' Then Begin FindMData; If ComparisonMData.IsValid Then NewComparisonM := ComparisonMData.GetDouble End; StartFromScratch := (CurrentComparisonSymbol <> NewComparisonSymbol) Or (CurrentComparisonM <> NewComparisonM); If (CurrentComparisonSymbol <> NewComparisonSymbol) Then Begin ReleaseComparisonData; If NewComparisonSymbol <> '' Then Begin FindMainStockData; { We do this here so we don't have to find the main data nodes if we are never going to use them. } CurrentComparisonSymbol := NewComparisonSymbol; FindComparisonData(CurrentComparisonSymbol) End { Else disable main data nodes. Not important because we don't expect this to change and its only an optimization. } End; CurrentComparisonM := NewComparisonM; If StartFromScratch Then ResetAll End; { TSectorBreakout.FindAllInputs } Function TSectorBreakout.GetMainBaseline: Double; Begin Result := StartValue(MainStockBaselineData) End; Function TSectorBreakout.GetComparisonBaseline: Double; Begin Result := StartValue(ComparisonBaselineData) End; Procedure TSectorBreakout.FindComparisonData(Const Symbol : String); Begin TGenericL1DataNode.Find(CurrentComparisonSymbol, Nil, ComparisonBaselineData, ComparisonBaselineDataLink); TGenericTosDataNode.Find(CurrentComparisonSymbol, Nil, ComparisonCurrentData, ComparisonCurrentDataLink) End; Constructor TSectorBreakout.Create(Params : TParamList); Var Link : TDataNodeLink; Factory : IGenericDataNodeFactory; Begin Assert(Length(Params) = 3, 'Expected: (Symbol, Up, FromTheClose)'); FFromTheClose := Params[2]; Inherited; { Start with just the colleration symbol. Many stocks will not have this so we can't do any work. No since in finding any other data nodes until we have some work to do. } Factory := TGenericDataNodeFactory.FindFactory('CorrelationSymbol').Duplicate; Factory.SetValue(SymbolNameKey, FSymbol); Factory.Find(FindAllInputs, ComparisonSymbolData, Link); AddAutoLink(Link); DoInCorrectThread(FindAllInputs) End; Procedure TSectorBreakout.ReleaseComparisonData; Begin If Assigned(ComparisonBaselineDataLink) Then ComparisonBaselineDataLink.Release; ComparisonBaselineData := Nil; ComparisonBaselineDataLink := Nil; If Assigned(ComparisonCurrentDataLink) Then ComparisonCurrentDataLink.Release; ComparisonCurrentData := Nil; ComparisonCurrentDataLink := Nil; CurrentComparisonSymbol := '' End; Function TSectorBreakout.StartValue(Data : TGenericL1DataNode) : Double; Begin If Data.IsValid Then If FFromTheClose Then Result := Data.GetCurrent.PrevClose Else Result := Data.Getcurrent.Open Else Result := 0 End; Function TSectorBreakout.BurritoDump : String; Var MainCurrent, CompCurrent : PL1Data; MainLast, CompLast : PTosData; Begin MainCurrent := MainStockBaselineData.GetCurrent; MainLast := MainStockCurrentData.GetLast; CompCurrent := ComparisonBaselineData.GetCurrent; CompLast := ComparisonCurrentData.GetLast; Result := 'Main(B: ' + FormatPrice(MainCurrent^.BidPrice) + ', A: ' + FormatPrice(MainCurrent^.AskPrice) + ', L: ' + FormatPrice(MainLast^.Price) + ', O: ' + FormatPrice(MainCurrent^.Open) + ', C: ' + FormatPrice(MainCurrent^.PrevClose) + ') Compare(B: ' + FormatPrice(CompCurrent^.BidPrice) + ', A: ' + FormatPrice(CompCurrent^.AskPrice) + ', L: ' + FormatPrice(CompLast^.Price) + ', O: ' + FormatPrice(CompCurrent^.Open) + ', C: ' + FormatPrice(CompCurrent^.PrevClose) + ', T: ' + TimeToStr(CompLast^.Time) + ')' End; Function TSectorBreakout.InputsValidP : Boolean; Begin Result := Assigned(ComparisonMData) And Assigned(ComparisonBaselineData) And Assigned(ComparisonCurrentData) And Assigned(MainStockBaselineData) And Assigned(MainStockCurrentData) And (CurrentComparisonM <> 0) And ComparisonBaselineData.IsValid And ComparisonCurrentData.IsValid And MainStockBaselineData.IsValid And MainStockCurrentData.IsValid; If Result Then Result := (StartValue(ComparisonBaselineData) > 0) And (StartValue(MainStockBaselineData) > 0) And (ComparisonCurrentData.GetLast^.Price > 0.01) // This is silly, but we've seen prints of 0.01 which are bad. And (MainStockCurrentData.GetLast^.Price > 0.01) End; //////////////////////////////////////////////////////////////////////// // TFuturesDivergence // // This is similar to the above, but simpler. It always compares to the // S & P futures or to nothing. And it always looks at the closing // price. //////////////////////////////////////////////////////////////////////// Type TFuturesDivergence = Class(TCorrelationCommon) Private ComparisonBaselineData : TGenericDataNode; //TFuturesClose; Procedure FindAllInputs; Protected Function InputsValidP : Boolean; Override; Function GetMainBaseline: Double; Override; Function GetComparisonBaseline: Double; Override; Constructor Create(Params : TParamList); Override; Function BurritoDump : String; Override; End; Procedure TFuturesDivergence.FindAllInputs; Const ComparisonSymbol = 'QQQQ'; Var Link : TDataNodeLink; Factory : IGenericDataNodeFactory; NewComparisonM : Double; Begin If ComparisonMData.IsValid Then NewComparisonM := ComparisonMData.GetDouble Else NewComparisonM := 0; If (NewComparisonM <> CurrentComparisonM) Then Begin CurrentComparisonM := NewComparisonM; If CurrentComparisonM <> 0 Then Begin FindMainStockData; If Not Assigned(ComparisonBaselineData) Then Begin Factory := TGenericDataNodeFactory.CreateWithArgs(TFuturesClose, ComparisonSymbol); Factory.Find(FindAllInputs, ComparisonBaselineData, Link); AddAutoLink(Link) End; If Not Assigned(ComparisonCurrentData) Then Begin TGenericTosDataNode.Find(ComparisonSymbol, Nil, ComparisonCurrentData, Link); AddAutoLink(Link) End; ResetAll End End End; Constructor TFuturesDivergence.Create(Params : TParamList); Var Link : TDataNodeLink; Factory : IGenericDataNodeFactory; Begin CurrentComparisonSymbol := 'QQQQ'; Inherited; Factory := TGenericDataNodeFactory.FindFactory('FCorrelationM').Duplicate; Factory.SetValue(SymbolNameKey, FSymbol); Factory.Find(FindAllInputs, ComparisonMData, Link); AddAutoLink(Link); DoInCorrectThread(FindAllInputs) End; Function TFuturesDivergence.InputsValidP : Boolean; Begin Result := Assigned(ComparisonMData) And Assigned(ComparisonBaselineData) And Assigned(ComparisonCurrentData) And Assigned(MainStockBaselineData) And Assigned(MainStockCurrentData) And (CurrentComparisonM <> 0) And ComparisonBaselineData.IsValid And ComparisonCurrentData.IsValid And MainStockBaselineData.IsValid And MainStockCurrentData.IsValid; If Result Then Result := (MainStockBaselineData.GetCurrent^.PrevClose > 0) And (ComparisonCurrentData.GetLast^.Price > 0.01) // This is silly, but we've seen prints of 0.01 which are bad. And (MainStockCurrentData.GetLast^.Price > 0.01) End; Function TFuturesDivergence.GetMainBaseline: Double; Begin Result := MainStockBaselineData.GetCurrent^.PrevClose End; Function TFuturesDivergence.GetComparisonBaseline: Double; Begin Result := ComparisonBaselineData.GetDouble End; Function TFuturesDivergence.BurritoDump : String; Var MainCurrent : PL1Data; MainLast, CompLast : PTosData; Begin MainCurrent := MainStockBaselineData.GetCurrent; MainLast := MainStockCurrentData.GetLast; CompLast := ComparisonCurrentData.GetLast; Result := 'Main(B: ' + FormatPrice(MainCurrent^.BidPrice) + ', A: ' + FormatPrice(MainCurrent^.AskPrice) + ', L: ' + FormatPrice(MainLast^.Price) + ', O: ' + FormatPrice(MainCurrent^.Open) + ', C: ' + FormatPrice(MainCurrent^.PrevClose) + ') Compare(L: ' + FormatPrice(CompLast^.Price) + ', C: ' + FormatPrice(ComparisonBaselineData.GetDouble) + ', T: ' + TimeToStr(CompLast^.Time) + ')' End; //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Initialization TGenericDataNodeFactory.StoreFactory('SectorBreakdownOpen', TGenericDataNodeFactory.CreateWithArgs(TSectorBreakout, StandardSymbolPlaceHolder, False, False)); TGenericDataNodeFactory.StoreFactory('SectorBreakdownClose', TGenericDataNodeFactory.CreateWithArgs(TSectorBreakout, StandardSymbolPlaceHolder, False, True)); TGenericDataNodeFactory.StoreFactory('SectorBreakoutOpen', TGenericDataNodeFactory.CreateWithArgs(TSectorBreakout, StandardSymbolPlaceHolder, True, False)); TGenericDataNodeFactory.StoreFactory('SectorBreakoutClose', TGenericDataNodeFactory.CreateWithArgs(TSectorBreakout, StandardSymbolPlaceHolder, True, True)); TGenericDataNodeFactory.StoreFactory('PositiveFuturesDivergence',TGenericDataNodeFactory.CreateWithArgs(TFuturesDivergence, StandardSymbolPlaceHolder, True)); TGenericDataNodeFactory.StoreFactory('NegativeFuturesDivergence',TGenericDataNodeFactory.CreateWithArgs(TFuturesDivergence, StandardSymbolPlaceHolder, False)); End.