#include "../data_framework/GenericTosData.h" #include "../data_framework/NewDayDetector.h" #include "../alert_framework/AlertBase.h" #include "../data_framework/SimpleMarketData.h" #include "Pullbacks.h" //////////////////////////////////////////////////////////////////// // PullbackCommon //////////////////////////////////////////////////////////////////// enum PullbackStartType { pstOpen, pstClose, pstSmart }; class PullbackCommon : public Alert { protected: /* This depends on the stock. Some are quoted in pennies and others in * hundreths of a penny. * The distance can't be negative, or we are reporting something really * wrong, like the open is less than low. * The distance can't be 0 or we'll have a divide by 0 problem. * Maybe a similar problem if the distance is very, very small. * If this is too small we will report a lot of noise in the morning. */ static double minDistance(double base); double _minPullback; std::string _msg; PullbackStartType _pullbackStartType; GenericTosDataNode *_tosData; double _previousClose; enum State { // Not primed. When we make a new extreme, we will prime. psApproachingExtreme, // Primed. As soon as the pullback is big enough, we signal an alert. psPullingBack }; State _state; double _lastExtreme; bool _lastExtremeValid; time_t _filterUntil; virtual void checkForNewExtreme() =0; virtual void checkForPullback() =0; void resetTimeFilter(); bool timeFilterOkay(); double getStart(TosData const &last, bool goingUp); PullbackCommon(DataNodeArgument const &args); private: void onNewDay(); void onNewTosData(); enum { wNewDay, wNewTosData }; void onWakeup(int msgId); }; inline double PullbackCommon::minDistance(double base) { // This is based on a simple rule from the TDA API. I assume this is correct // for the market in general. More than a dollar and the stock is quoted in // pennies, otherwise it's quoted in hundreths of pennies. if (base > 1.00) return 0.01; else return 0.0005; // I suppose there would be issues if the base was <= 0, but I assume this // gets filtered out elsewhere. } inline double PullbackCommon::getStart(TosData const &last, bool goingUp) { switch (_pullbackStartType) { case pstOpen: return last.open; case pstClose: return _previousClose; case pstSmart: // Smart will make the initial leg as long as possible. Originally we // only used this mode. if (goingUp) return std::max(last.open, _previousClose); else return std::min(last.open, _previousClose); default: assert(false && "unknown _pullbackStartType"); return 0.0; } } PullbackCommon::PullbackCommon(DataNodeArgument const &args) : _state(psApproachingExtreme), _lastExtreme(0.0), _lastExtremeValid(false), _filterUntil(0) { DataNodeArgumentVector const &argList = args.getListValue(); // Symbol, min pullback as decimal fraction, msg, PullbackStartType assert(argList.size() == 4); const std::string symbol = argList[0].getStringValue(); _minPullback = argList[1].getDoubleValue(); _msg = argList[2].getStringValue(); _pullbackStartType = (PullbackStartType)argList[3].getIntValue(); _previousClose = getPreviousClose(symbol); addAutoLink(NewDay::find(this, wNewDay, symbol)); addAutoLink(GenericTosDataNode::find(this, wNewTosData, _tosData, symbol)); } void PullbackCommon::resetTimeFilter() { // After a new day starts, wait 90 seconds before reporting any alerts. static const int pauseTime = 90; _filterUntil = getSubmitTime() + pauseTime; } inline bool PullbackCommon::timeFilterOkay() { return getSubmitTime() > _filterUntil; } void PullbackCommon::onWakeup(int msgId) { switch (msgId) { case wNewDay: onNewDay(); break; case wNewTosData: onNewTosData(); break; }; } void PullbackCommon::onNewDay() { resetTimeFilter(); _lastExtremeValid = false; checkForNewExtreme(); // We start in the not primed state. // If we start in the middle of the day, we may get a lot of new day alerts. // In this case we do not want to print a bunch of alerts. // On a real new day notification, high and low are typically the same. // If they are not, we still are in a someone unknown state as we don't // know which happened first. _state = psApproachingExtreme; } void PullbackCommon::onNewTosData() { if (_tosData->getValid()) { checkForNewExtreme(); if (_tosData->getLast().newPrint && (_tosData->getLast().price > 0)) checkForPullback(); } } //////////////////////////////////////////////////////////////////// // PullbackFromLows //////////////////////////////////////////////////////////////////// class PullbackFromLows : public PullbackCommon { protected: virtual void checkForNewExtreme(); virtual void checkForPullback(); public: PullbackFromLows(DataNodeArgument const &args) : PullbackCommon(args) { } friend class GenericDataNodeFactory; }; void PullbackFromLows::checkForNewExtreme() { bool newValueKnown = false; double newValue = 0.0; if (_tosData->getValid()) { newValue = _tosData->getLast().low; newValueKnown = newValue > 0.0; } if (newValueKnown && _tosData->getValid()) { const double last = _tosData->getLast().price; if (last > 0) // Merge the after hours TOS into the offical low. See more notes in // PullbackFromHighs::checkForNewExtreme(). newValue = std::min(last, newValue); } if (newValueKnown) { if (_lastExtremeValid) { if (newValue < _lastExtreme) { _lastExtreme = newValue; _state = psPullingBack; } } else { _lastExtreme = newValue; _lastExtremeValid = true; } } } void PullbackFromLows::checkForPullback() { if (_state == psPullingBack) if (_tosData->getValid()) { TosData const &last = _tosData->getLast(); const double start = getStart(last, true); const double distanceToExtreme = start - _lastExtreme; if (distanceToExtreme > minDistance(start)) if ((last.price - _lastExtreme) / distanceToExtreme >= _minPullback) { if (last.low == last.high) /* Just in case the print data node was awakened before * the new day data node. This just protects this one * instance. We don't have to worry about all the other * else clauses. The new day will be called soon, if not * already. */ resetTimeFilter(); else if (timeFilterOkay()) report(_msg, distanceToExtreme); _state = psApproachingExtreme; } } } //////////////////////////////////////////////////////////////////// // PullbackFromHighs //////////////////////////////////////////////////////////////////// class PullbackFromHighs : public PullbackCommon { protected: virtual void checkForNewExtreme(); virtual void checkForPullback(); public: PullbackFromHighs(DataNodeArgument const &args) : PullbackCommon(args) { } friend class GenericDataNodeFactory; }; void PullbackFromHighs::checkForNewExtreme() { double newValue = 0.0; bool newValueKnown = false; if (_tosData->getValid()) { newValue = _tosData->getLast().high; newValueKnown = newValue > 0.0; } if (newValueKnown && _tosData->getValid()) // After hours we create our own high by following the prints and watching // the official high. If there is no official high, we don't try to // proceed. We just don't have enough information. Unfortunately // this prevents us from working in the premarket. But it's required // to avoid the case of starting too soon, where we assume the first // print is a new high, then we get another new high as soon as we find // out the real high from the market. This was happening consistantly // if we start the alerrts in the middle of the day. newValue = std::max(_tosData->getLast().price, newValue); if (newValueKnown) { if (_lastExtremeValid) { if (newValue > _lastExtreme) { _lastExtreme = newValue; _state = psPullingBack; } } else { _lastExtreme = newValue; _lastExtremeValid = true; } } } void PullbackFromHighs::checkForPullback() { if (_state == psPullingBack) if (_tosData->getValid()) { TosData const &last = _tosData->getLast(); const double start = getStart(last, false); const double distanceToExtreme = _lastExtreme - start; if (distanceToExtreme > minDistance(start)) if ((_lastExtreme - last.price) / distanceToExtreme >= _minPullback) { if (last.low == last.high) /* Just in case the print data node was awakened before * the new day data node. This just protects this one * instance. We don't have to worry about all the other * else clauses. The new day will be called soon, if not * already. */ resetTimeFilter(); else if (timeFilterOkay()) report(_msg, distanceToExtreme); _state = psApproachingExtreme; } } } //////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////// void initializePullbacks() { GenericDataNodeFactory::sf< PullbackFromLows > ("PullbackFromLows25", symbolPlaceholderObject, 0.25, "25% pullback from lows.", pstSmart); GenericDataNodeFactory::sf< PullbackFromLows > ("PullbackFromLows75", symbolPlaceholderObject, 0.75, "75% pullback from lows.", pstSmart); GenericDataNodeFactory::sf< PullbackFromHighs > ("PullbackFromHighs25", symbolPlaceholderObject, 0.25, "25% pullback from highs.", pstSmart); GenericDataNodeFactory::sf< PullbackFromHighs > ("PullbackFromHighs75", symbolPlaceholderObject, 0.75, "75% pullback from highs.", pstSmart); GenericDataNodeFactory::sf< PullbackFromLows > ("PullbackFromLows25Open", symbolPlaceholderObject, 0.25, "25% pullback from lows. (Open)", pstOpen); GenericDataNodeFactory::sf< PullbackFromLows > ("PullbackFromLows75Open", symbolPlaceholderObject, 0.75, "75% pullback from lows. (Open)", pstOpen); GenericDataNodeFactory::sf< PullbackFromHighs > ("PullbackFromHighs25Open", symbolPlaceholderObject, 0.25, "25% pullback from highs. (Open)", pstOpen); GenericDataNodeFactory::sf< PullbackFromHighs > ("PullbackFromHighs75Open", symbolPlaceholderObject, 0.75, "75% pullback from highs. (Open)", pstOpen); GenericDataNodeFactory::sf< PullbackFromLows > ("PullbackFromLows25Close", symbolPlaceholderObject, 0.25, "25% pullback from lows. (Close)", pstClose); GenericDataNodeFactory::sf< PullbackFromLows > ("PullbackFromLows75Close", symbolPlaceholderObject, 0.75, "75% pullback from lows. (Close)", pstClose); GenericDataNodeFactory::sf< PullbackFromHighs > ("PullbackFromHighs25Close", symbolPlaceholderObject, 0.25, "25% pullback from highs. (Close)", pstClose); GenericDataNodeFactory::sf< PullbackFromHighs > ("PullbackFromHighs75Close", symbolPlaceholderObject, 0.75, "75% pullback from highs. (Close)", pstClose); }