Unit NyseArb; { This looks for Arbitrage on the listed exchanges. These are too fast for our normal alerts project, but may have some value to more specific applications. This looks at the best bid and ask from various venues, and filters out the slows ones. It looks at NYSE and all the ECNs. } Interface Implementation Uses DataNodes, GenericDataNodes, AlertBase, GenericLevel2DataNode, DateUtils, Math, SysUtils; //////////////////////////////////////////////////////////////////////// // TNyseArbBase //////////////////////////////////////////////////////////////////////// Type TNyseArbBase = Class(TGenericDataNode) Protected Constructor Create(Params : TParamList); Override; Private L2Data : TGenericLevel2DataNode; FIsValid : Boolean; FStringValue : String; FQuality : Double; Procedure NewData; Public Function IsValid : Boolean; Override; Property Quality : Double Read FQuality; Published Function GetString : String; Override; End; Function TNyseArbBase.IsValid : Boolean; Begin Result := FIsValid End; Function TNyseArbBase.GetString : String; Begin Result := FStringValue End; Constructor TNyseArbBase.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Begin Assert(Length(Params) = 1); Symbol := Params[0]; Inherited Create; TGenericLevel2DataNode.Find(Symbol, NewData, L2Data, Link); AddAutoLink(Link); DoInCorrectThread(NewData); End; Procedure TNyseArbBase.NewData; Var NyseBid, EcnBid, NyseAsk, EcnAsk : Double; NyseBidSize, EcnBidSize, NyseAskSize, EcnAskSize : Integer; BidSide, AskSide : TL2EntryArray; I : Integer; Time : TDateTime; Begin SetLength(BidSide, 0); // Avoid compiler warning message. SetLength(AskSide, 0); // Avoid compiler warning message. Time := TimeOf(GetSubmitTime); If (Time < (6.5/24)) Or (Time > (13/25)) Then FIsValid := False Else Begin NyseBid := -MaxDouble; EcnBid := -MaxDouble; NyseAsk := MaxDouble; EcnAsk := MaxDouble; NyseBidSize := 0; EcnBidSize := 0; NyseAskSize := 0; EcnAskSize := 0; BidSide := L2Data.GetBidList; AskSide := L2Data.GetAskList; If (Length(BidSide) > 0) And (Length(AskSide) > 0) And (BidSide[0].Price > AskSide[0].Price) Then Begin For I := Low(BidSide) To High(BidSide) Do With BidSide[I] Do If (MMID = 'THRD') Or (MMID = 'PSE') Then Begin If Price > EcnBid Then Begin EcnBid := Price; EcnBidSize := Size End Else If Price = EcnBid Then EcnBidSize := EcnBidSize + Size End Else If (MMID = 'NYSE') And (Size >= 2) Then Begin NyseBid := Price; NyseBidSize := Size End; For I := Low(AskSide) To High(AskSide) Do With AskSide[I] Do If (MMID = 'THRD') Or (MMID = 'PSE') Then Begin If Price < EcnAsk Then Begin EcnAsk := Price; EcnAskSize := Size End Else If Price = EcnAsk Then EcnAskSize := EcnAskSize + Size End Else If (MMID = 'NYSE') And (Size >= 2) Then Begin NyseAsk := Price; NyseAskSize := Size End End; If NyseBid > EcnAsk Then Begin FIsValid := True; FStringValue := Format('Sell NYSE %.0n @ %f > %.0n @ %f', [NyseBidSize * 1.0, NyseBid, EcnAskSize * 1.0, EcnAsk]); FQuality := (NyseBid - EcnAsk) * Min(NyseBidSize, EcnAskSize) End Else If EcnBid > NyseAsk Then Begin FIsValid := True; FStringValue := Format('Buy NYSE %0.n @ %f < %.0n @ %f', [NyseAskSize * 1.0, NyseAsk, EcnBidSize * 1.0, EcnBid]); FQuality := (EcnBid - NyseAsk) * Min(NyseAskSize, EcnBidSize) End Else FIsValid := False End; NotifyListeners End; //////////////////////////////////////////////////////////////////////// // TNyseArb //////////////////////////////////////////////////////////////////////// Type TNyseArb = Class(TAlert) Protected Constructor Create(Params : TParamList); Override; Private ArbData : TNyseArbBase; PreviouslyCrossed : Boolean; Procedure NewData; Published End; Constructor TNyseArb.Create(Params : TParamList); Var Symbol : String; Node : TGenericDataNode; Link : TDataNodeLink; Factory : IGenericDataNodeFactory; Begin Assert(Length(Params) = 1); Symbol := Params[0]; Inherited Create; Factory := TGenericDataNodeFactory.CreateWithArgs(TNyseArbBase, Symbol); Factory.Find(NewData, Node, Link); AddAutoLink(Link); ArbData := Node As TNyseArbBase End; Procedure TNyseArb.NewData; Begin If ArbData.IsValid Then Begin If Not PreviouslyCrossed Then Begin PreviouslyCrossed := True; Report(ArbData.GetString, ArbData.Quality) End End Else PreviouslyCrossed := False End; //////////////////////////////////////////////////////////////////////// // TNyseArbEnd //////////////////////////////////////////////////////////////////////// Type TNyseArbEnd = Class(TAlert) Protected Constructor Create(Params : TParamList); Override; Private ArbData : TNyseArbBase; PreviouslyCrossed : Boolean; InitialMessage : String; MaxQuality, MinQuality : Double; StartTime : TDateTime; Procedure NewData; Published End; Constructor TNyseArbEnd.Create(Params : TParamList); Var Symbol : String; Node : TGenericDataNode; Link : TDataNodeLink; Factory : IGenericDataNodeFactory; Begin Assert(Length(Params) = 1); Symbol := Params[0]; Inherited Create; Factory := TGenericDataNodeFactory.CreateWithArgs(TNyseArbBase, Symbol); Factory.Find(NewData, Node, Link); AddAutoLink(Link); ArbData := Node As TNyseArbBase End; Procedure TNyseArbEnd.NewData; Begin If ArbData.IsValid Then Begin If PreviouslyCrossed Then Begin MinQuality := Min(MinQuality, ArbData.Quality); MaxQuality := Max(MaxQuality, ArbData.Quality) End Else Begin PreviouslyCrossed := True; MinQuality := ArbData.Quality; MaxQuality := ArbData.Quality; InitialMessage := ArbData.GetString; StartTime := GetSubmitTime End End Else Begin If PreviouslyCrossed Then Begin PreviouslyCrossed := False; Report(Format('Arb Ended. $%f - $%f, %f seconds. "%s"', [MinQuality, MaxQuality, (GetSubmitTime - StartTime)*24*60*60, InitialMessage])) End End End; //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Initialization TGenericDataNodeFactory.StoreStandardFactory('NyseArb', TNyseArb); TGenericDataNodeFactory.StoreStandardFactory('NyseArbEnd', TNyseArbEnd); End.