#include "../alert_framework/AlertBase.h" #include "../data_framework/GenericTosData.h" #include "../../shared/MarketHours.h" #include "CheckMark.h" /* 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. */ class CheckMark : public Alert { private: CheckMark(DataNodeArgument const &args); friend class GenericDataNodeFactory; // Ideally the inputs would all be const. Unfortunately that's hard to do // with our current setup. bool _inverted; // 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 < } enum HighLowState { 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 }; bool backedAway(HighLowState state) { return (state==hlsBackedAwaySlightly) || (state==hlsBackedAwayPastSaved); } // This is how far along we are in the entire pattern. enum CheckMarkState { 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. */ }; // This is how we know if a high or low goes up or down. double _previousHigh, _previousLow; // We save interesting positions so we know how far to roll back when there // is a correction. double _highToBeat, _lowToBeat; // 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. HighLowState _highState, _lowState; CheckMarkState _patternState; GenericTosDataNode *_tosData; void recordHighsAndLows(); void findChanges(); void onWakeup(int msgId); }; CheckMark::CheckMark(DataNodeArgument const &args) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, Inverted std::string const &symbol = argList[0].getStringValue(); _inverted = argList[1].getBooleanValue(); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } void CheckMark::recordHighsAndLows() { _highToBeat = _previousHigh; _lowToBeat = _previousLow; } void CheckMark::findChanges() { if (!_tosData->getValid()) { _highState = hlsNoData; _lowState = hlsNoData; } else { double newHigh = _tosData->getLast().high; if (newHigh <= 0) _highState = hlsNoData; else if (_highState == hlsNoData) _highState = hlsNewData; else if (newHigh == _previousHigh) _highState = hlsNoChange; else if (newHigh > _previousHigh) _highState = hlsMoreExtreme; else if (newHigh < _highToBeat) _highState = hlsBackedAwayPastSaved; else _highState = hlsBackedAwaySlightly; _previousHigh = newHigh; double newLow = _tosData->getLast().low; if (newLow <= 0) _lowState = hlsNoData; else if (_lowState == hlsNoData) _lowState = hlsNewData; else if (newLow == _previousLow) _lowState = hlsNoChange; else if (newLow < _previousLow) _lowState = hlsMoreExtreme; else if (newLow > _lowToBeat) _lowState = hlsBackedAwayPastSaved; else _lowState = hlsBackedAwaySlightly; _previousLow = newLow; } } void CheckMark::onWakeup(int msgId) { const int startTime = MARKET_HOURS_OPEN + MARKET_HOURS_MINUTE * 3; findChanges(); if ((_lowState == hlsNoData) || (_highState == hlsNoData)) _patternState = cmsNoData; else if ((_patternState == cmsNoData) || (backedAway(_highState) && backedAway(_lowState))) _patternState = cmsWaitingForFirst; else { HighLowState firstAndThirdDirection, secondDirection; if (_inverted) { firstAndThirdDirection = _lowState; secondDirection = _highState; } else { firstAndThirdDirection = _highState; secondDirection = _lowState; } switch(_patternState) // Previous state { case cmsWaitingForFirst: if (firstAndThirdDirection == hlsMoreExtreme) { recordHighsAndLows(); _patternState = cmsWaitingForSecond; } else { _patternState = cmsWaitingForFirst; } break; case cmsWaitingForSecond: if (firstAndThirdDirection == hlsBackedAwayPastSaved) { _patternState = cmsWaitingForFirst; } else if (secondDirection == hlsMoreExtreme) { recordHighsAndLows(); _patternState = cmsWaitingForFinal; } break; case cmsWaitingForSecondAgain: if (firstAndThirdDirection == hlsBackedAwayPastSaved) _patternState = cmsWaitingForFinal; else if (secondDirection == hlsBackedAwayPastSaved) _patternState = cmsWaitingForFirst; else if (secondDirection == hlsMoreExtreme) { recordHighsAndLows(); _patternState = cmsWaitingForFinal; } break; case cmsWaitingForFinal: if (secondDirection == hlsBackedAwayPastSaved) { recordHighsAndLows(); _patternState = cmsWaitingForSecond; } else if (firstAndThirdDirection == hlsBackedAwayPastSaved) { recordHighsAndLows(); _patternState = cmsWaitingForFirst; } else if (firstAndThirdDirection == hlsMoreExtreme) { recordHighsAndLows(); if (secondOfTheDay(getSubmitTime()) > startTime) { if (_inverted) report("Inverted check mark"); else report("Check mark"); } _patternState = cmsWaitingForSecondAgain; } break; case cmsNoData: // This is impossible, but the comipler complains if I leave it out. break; } } } void initializeCheckMark() { GenericDataNodeFactory::sf< CheckMark > ("CheckMark", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< CheckMark > ("InvertedCheckMark", symbolPlaceholderObject, true); } /* Test code: telnet chuck-liddell 1234 command=debug_l1&symbol=TEST&high=10&low=9.99 command=debug_l1&symbol=TEST&high=11&low=9.99 command=debug_l1&symbol=TEST&high=11&low=9.9 command=debug_l1&symbol=TEST&high=12&low=9.9 command=debug_l1&symbol=TEST&high=12&low=9.8 command=debug_l1&symbol=TEST&high=13&low=9.8 command=debug_l1&symbol=TEST&high=13&low=9.7 The fourth debug statement should cause the first check mark alert. The fifth one should cause the first inverted check mark. Then another check mark followed by another inverted check mark. That's way out of date! Highs and lows moved from l1 to tos and you need set=1. */