Unit CheckMark; Interface { A Check mark pattern starts by making new highs, then makes new lows, then makes new highs again. The alert happens on the first high after the low. This is a continuation pattern, and the stock is expected to continue up a lot. An inverted check mark is the same thing up-side-down. We do not display any check marks in the first 3 minutes of trading. The setup for the alert can happen in the first 3 minutes, but not the last part. If an entire checkmark happens in that time, we are not set up. The next new high will not cause the alert. It has to reset completely. We allow multiple check marks in a day. This is in case we had a really small one that didn't count. This is similar to the way we handle the 25/75% pullbacks, only more extreme, so we don't expect a huge number of these. No notion of quality at this time. We'll reserve that until we get some user suggestions. The first tricky part of the implementation is that we're not sure we won't get new highs and lows at the same time. Most of that would probably happen right at the open, and hopefully we avoid that with the 3 minute pause. If it happens at another time, it would be a stock with a very small range for the day, or it would be an error/correction from the market. This is far from the most important case, and we could probably get away with (a) always putting it into the initial state at the point. That would treat this like an error, same as no data. The alternative is (b) to assume that it made the initial high and at least one low. We can't say for certain if the new low came before or after the new high, so take the conservative position, and wait for the next new low. We choose (b) because this is a better guess of what's going on. Also, the we have to do the work to detect all the cases anyway, before we decide what to do with the cases, so the difference in code between (a) and (b) is trivial. The next tricky part is when the exchange recalls a new high or low. This is important because we see this somewhat often, but the user probably never notices this. So he won't understand why he didn't see the pattern in our alerts but he saw it on the chart. To deal with this we assume that the correction comes very quickly, relative to the timescale of the entire checkmark. We're really only considering the case where the high goes wild, then returns to the previous value before any other changes happen. When we are looking for a new high or low we already know the price to beat. If we save interesting values of this, we can tell if we backed up more than this or not. These two cases can conspire against us. If both values go the wrong way at the same time we assume there was a serious error. We treat this like the beginning of the day and completely reset. Otherwise, if the value that we're not focused on moves the wrong way, we ignore it. We use the time of day as the primary way to know when a stock opens. We also look at the stocks with no data, which would be the stocks which don't trade in the first 3 minutes. Finally, if a stock reverts both the high and low at the same time, that's a new day. In TAL, we see a transition from no data to data in the high and low at the opening print. So if we didn't have the 3 minute requirement, we could take away time completely. This might not cover the case of a new day in eSignal, so we'd need the time or the TNewDay to reset us. That is to say, the logic in the previous paragraphs makes use of some of the pacularities of TAL. } Implementation Uses DataNodes, GenericDataNodes, AlertBase, GenericL1DataNode, StandardPlaceHolders, DateUtils; Type { This allows us to do limited processing that is specific to the high vs. low, i.e. a higher high corresponds to a lower low. Most of the code does not have a > or a < } THighLowState = ( hlsNoData, { No value is present. All below have data. } hlsNewData, { No value was present last time. } hlsNoChange, { Same value as last time. } hlsMoreExtreme, { Higher high or lower low. } hlsBackedAwaySlightly, hlsBackedAwayPastSaved); { This is how far along we are in the entire pattern. } TCheckMarkState = ( cmsNoData, { This is the first thing we look for. No data always resets our state. } cmsWaitingForFirst, { This is the reset state. We have data but no real history. We are looking for the first move in the right direction. } cmsWaitingForSecond, { We have found the first move. We are waiting for the second move to let us go forward, or for an oops from the market to revert us back to the first state. } cmsWaitingForSecondAgain, { We just reported a check mark. Since the first and third points are the same, the third point of this check mark could be the first point of a larger check mark. So we are set up to look for the second point. The only difference between this and cmsSecond is that if the last change was a mistake, we return to a different state. } cmsWaitingForFinal); { Waiting for the third point. } TCheckMark = Class(TAlert) Private Inverted : Boolean; PreviousHigh, PreviousLow : Double; { This is how we know if a high or low goes up or down. } HighToBeat, LowToBeat : Double; { We save interesting positions so we know how far to roll back when there is a correction. } HighState, LowState : THighLowState; { This is the last change to the high and low. There is some redundancy between this and the PatternState, but this allows us to break up the processing into smaller pieces. } PatternState : TCheckMarkState; L1Data : TGenericL1DataNode; Procedure NewL1Data; Protected Constructor Create(Params : TParamList); Override; End; Constructor TCheckMark.Create(Params : TParamList); Var Symbol : String; Link : TDataNodeLink; Begin Assert(Length(Params) = 2, 'Expected (Symbol, Inverted)'); Symbol := Params[0]; Inverted := Params[1]; Inherited Create; TGenericL1DataNode.Find(Symbol, NewL1Data, L1Data, Link); AddAutoLink(Link) End; Procedure TCheckMark.NewL1Data; Procedure RecordHighsAndLows; Begin HighToBeat := PreviousHigh; LowToBeat := PreviousLow End; Procedure FindChanges; Var Current : PL1Data; NewHigh, NewLow : Double; Begin { FindChanges } If Not L1Data.IsValid Then Begin HighState := hlsNoData; LowState := hlsNoData End Else Begin Current := L1Data.GetCurrent; NewHigh := Current^.High; If NewHigh <= 0 Then HighState := hlsNoData Else If HighState = hlsNoData Then HighState := hlsNewData Else If NewHigh = PreviousHigh Then HighState := hlsNoChange Else If NewHigh > PreviousHigh Then HighState := hlsMoreExtreme Else If NewHigh < HighToBeat Then HighState := hlsBackedAwayPastSaved Else HighState := hlsBackedAwaySlightly; PreviousHigh := NewHigh; NewLow := Current^.Low; If NewLow <= 0 Then LowState := hlsNoData Else If LowState = hlsNoData Then LowState := hlsNewData Else If NewLow = PreviousLow Then LowState := hlsNoChange Else If NewLow < PreviousLow Then LowState := hlsMoreExtreme Else If NewLow > LowToBeat Then LowState := hlsBackedAwayPastSaved Else LowState := hlsBackedAwaySlightly; PreviousLow := NewLow End; End; { FindChanges } Const OneHour = 1.0 / 24.0; OneMinute = OneHour / 60.0; StartTime = 6 * OneHour + 33 * OneMinute; Var FirstAndThirdDirection, SecondDirection : THighLowState; Begin { TCheckMark.NewL1Data } FindChanges; If (LowState = hlsNoData) Or (HighState = hlsNoData) Then PatternState := cmsNoData Else If (PatternState = cmsNoData) Or ((HighState In [hlsBackedAwaySlightly, hlsBackedAwayPastSaved]) And (LowState In [hlsBackedAwaySlightly, hlsBackedAwayPastSaved])) Then PatternState := cmsWaitingForFirst Else Begin If Inverted Then Begin FirstAndThirdDirection := LowState; SecondDirection := HighState End Else Begin FirstAndThirdDirection := HighState; SecondDirection := LowState End; Case PatternState Of { Previous state } cmsWaitingForFirst : If FirstAndThirdDirection = hlsMoreExtreme Then Begin RecordHighsAndLows; PatternState := cmsWaitingForSecond End Else PatternState := cmsWaitingForFirst; cmsWaitingForSecond : If FirstAndThirdDirection = hlsBackedAwayPastSaved Then PatternState := cmsWaitingForFirst Else If SecondDirection = hlsMoreExtreme Then Begin RecordHighsAndLows; PatternState := cmsWaitingForFinal End; cmsWaitingForSecondAgain : If FirstAndThirdDirection = hlsBackedAwayPastSaved Then PatternState := cmsWaitingForFinal Else If SecondDirection = hlsBackedAwayPastSaved Then PatternState := cmsWaitingForFirst Else If SecondDirection = hlsMoreExtreme Then Begin RecordHighsAndLows; PatternState := cmsWaitingForFinal End; cmsWaitingForFinal : If SecondDirection = hlsBackedAwayPastSaved Then Begin RecordHighsAndLows; PatternState := cmsWaitingForSecond End Else If FirstAndThirdDirection = hlsBackedAwayPastSaved Then Begin RecordHighsAndLows; PatternState := cmsWaitingForFirst End Else If FirstAndThirdDirection = hlsMoreExtreme Then Begin RecordHighsAndLows; If TimeOf(GetSubmitTime) > StartTime Then If Inverted Then Report('Inverted check mark') Else Report('Check mark'); PatternState := cmsWaitingForSecondAgain End End End End; { TCheckMark.NewL1Data } //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Initialization TGenericDataNodeFactory.StoreFactory('CheckMark', TGenericDataNodeFactory.CreateWithArgs(TCheckMark, StandardSymbolPlaceHolder, False)); TGenericDataNodeFactory.StoreFactory('InvertedCheckMark', TGenericDataNodeFactory.CreateWithArgs(TCheckMark, StandardSymbolPlaceHolder, True)); End.