#include #include #include "../alert_framework/AlertBase.h" #include "../data_framework/VolumeBlockMinAndMax.h" #include "../data_framework/SimpleMarketData.h" //////////////////////////////////////////////////////////////////// // GeometricPattern //////////////////////////////////////////////////////////////////// typedef unsigned int Epoch; struct PatternDescription { // This changes every time the pattern changes. This value is valid even if // the pattern as a whole is not. Epoch epoch; // We have seen a pattern of the expected type, and its description is // listed below. If this is false, then nothing below is meaningful. bool valid; // Starting from the right and going backward you can see this pattern. No // stray points exist to the right of this pattern. bool active; // The pattern should be described in green. For triangles this means that // the longest line goes up. bool up; // In order from left to right. std::vector< double > prices; // Rough estimates. These go from the start of the first bar to the/ end of // the last bar. time_t startTime; time_t endTime; // The number of bars / 4. Roughtly, the number of hours. double quality; }; // This class wakes the listener every time the underlying volume bars // data node wakes it. The pattern may or may not have changed. It might // (unfortunately) have an extra wakeup or so durring initialization. class GeometricPattern : public DataNode { private: VolumeBlockMinAndMax *_pointsData; int _previousPointCount; Epoch _lastEpoch; void possibleNewData(); void onWakeup(int msgId); protected: PatternDescription _description; GeometricPattern(std::string const &symbol); virtual void newPoint(int lastPoint) =0; void newEpoch(); void afterConstruction(); VolumeBlocks const &getAllBlocks() const { return _pointsData->getAllBlocks(); } VolumeBlockMinAndMax::TurningPoints const &getTurningPoints() const { return _pointsData->getTurningPoints(); } public: PatternDescription const &getDescription() const { return _description; } std::string getPriceList() const; template < class T > static DataNodeLink *find(DataNodeListener *listener, int msgId, GeometricPattern *&node, std::string const &symbol); }; template < class T > DataNodeLink *GeometricPattern::find(DataNodeListener *listener, int msgId, GeometricPattern *&node, std::string const &symbol) { T *tempNode; DataNodeLink *result = findHelper(listener, msgId, tempNode, symbol); node = tempNode; return result; } void GeometricPattern::newEpoch() { _lastEpoch++; _description.epoch = _lastEpoch; }; void GeometricPattern::possibleNewData() { const int newPointCount = _pointsData->getTurningPoints().size(); if (newPointCount < _previousPointCount) { _description.valid = false; _previousPointCount = 0; } else while (_previousPointCount < newPointCount) { newPoint(_previousPointCount); _previousPointCount++; } notifyListeners(); }; GeometricPattern::GeometricPattern(std::string const &symbol) { addAutoLink(VolumeBlockMinAndMax::find(this, 0, _pointsData, symbol)); } void GeometricPattern::afterConstruction() { possibleNewData(); } void GeometricPattern::onWakeup(int msgId) { possibleNewData(); } std::string GeometricPattern::getPriceList() const { std::string result; if (_description.valid) { if (_description.prices.size() > 10) { double highest = std::numeric_limits< double >::min(); double lowest = std::numeric_limits< double >::max(); for (std::vector< double >::const_iterator it = _description.prices.begin(); it != _description.prices.end(); it++) { highest = std::max(*it, highest); lowest = std::min(*it, lowest); } result = Alert::formatPrice(lowest); result += " - "; result += Alert::formatPrice(highest); } else { for (std::vector< double >::const_iterator it = _description.prices.begin(); it != _description.prices.end(); it++) { if (!result.empty()) result += ", "; result += Alert::formatPrice(*it); } } } return result; } //////////////////////////////////////////////////////////////////// // NarrowingTrianglePattern //////////////////////////////////////////////////////////////////// class NarrowingTrianglePattern : public GeometricPattern { protected: virtual void newPoint(int lastPoint); private: NarrowingTrianglePattern(DataNodeArgument const &args); friend class DataNode; }; NarrowingTrianglePattern::NarrowingTrianglePattern (DataNodeArgument const &args) : GeometricPattern(args.getStringValue()) { afterConstruction(); } void NarrowingTrianglePattern::newPoint(int lastPoint) { _description.active = false; if (lastPoint < 4) return; VolumeBlockMinAndMax::TurningPoints const &points = getTurningPoints(); double highPrice, lowPrice; if (points[lastPoint].orientation == VolumeBlockMinAndMax::vboHigh) { highPrice = points[lastPoint].price; lowPrice = points[lastPoint - 1].price; } else { lowPrice = points[lastPoint].price; highPrice = points[lastPoint - 1].price; } int firstPoint = lastPoint; for (int i = lastPoint - 2; i >= 0; i--) { bool continues = false; VolumeBlockMinAndMax::TurningPoint const &point = points[i]; if (point.orientation == VolumeBlockMinAndMax::vboHigh) { if (point.price > highPrice) { highPrice = point.price; continues = true; } } else { if (point.price < lowPrice) { lowPrice = point.price; continues = true; } } if (continues) firstPoint = i; else break; } if ((lastPoint - firstPoint) < 4) return; newEpoch(); _description.active = true; _description.valid = true; _description.up = points[lastPoint].orientation == VolumeBlockMinAndMax::vboLow; _description.prices.clear(); _description.prices.reserve(lastPoint - firstPoint + 1); for (int i = firstPoint; i <= lastPoint; i++) _description.prices.push_back(points[i].price); VolumeBlocks const &volumeBlocks = getAllBlocks(); _description.startTime = volumeBlocks[points[firstPoint].index].startTime; _description.endTime = volumeBlocks[points[lastPoint].index].endTime; _description.quality = (points[lastPoint].index - points[firstPoint].index + 1) / 4.0; } //////////////////////////////////////////////////////////////////// // BroadeningTrianglePattern //////////////////////////////////////////////////////////////////// class BroadeningTrianglePattern : public GeometricPattern { protected: virtual void newPoint(int lastPoint); private: BroadeningTrianglePattern(DataNodeArgument const &args); friend class DataNode; }; BroadeningTrianglePattern::BroadeningTrianglePattern (DataNodeArgument const &args) : GeometricPattern(args.getStringValue()) { afterConstruction(); } void BroadeningTrianglePattern::newPoint(int lastPoint) { _description.active = false; if (lastPoint < 4) return; VolumeBlockMinAndMax::TurningPoints const &points = getTurningPoints(); double highPrice, lowPrice; if (points[lastPoint].orientation == VolumeBlockMinAndMax::vboHigh) { highPrice = points[lastPoint].price; lowPrice = points[lastPoint - 1].price; } else { lowPrice = points[lastPoint].price; highPrice = points[lastPoint - 1].price; } int firstPoint = lastPoint; for (int i = lastPoint - 2; i >= 0; i--) { bool continues = false; VolumeBlockMinAndMax::TurningPoint const &point = points[i]; if (point.orientation == VolumeBlockMinAndMax::vboHigh) { if (point.price < highPrice) { highPrice = point.price; continues = true; } } else { if (point.price > lowPrice) { lowPrice = point.price; continues = true; } } if (continues) firstPoint = i; else break; } if ((lastPoint - firstPoint) < 4) return; newEpoch(); _description.active = true; _description.valid = true; _description.up = points[lastPoint].orientation == VolumeBlockMinAndMax::vboLow; _description.prices.clear(); _description.prices.reserve(lastPoint - firstPoint + 1); for (int i = firstPoint; i <= lastPoint; i++) _description.prices.push_back(points[i].price); VolumeBlocks const &volumeBlocks = getAllBlocks(); _description.startTime = volumeBlocks[points[firstPoint].index].startTime; _description.endTime = volumeBlocks[points[lastPoint].index].endTime; _description.quality = (points[lastPoint].index - points[firstPoint].index + 1) / 4.0; } //////////////////////////////////////////////////////////////////// // PatternWithEquals //////////////////////////////////////////////////////////////////// class PatternWithEquals : public GeometricPattern { private: double _volatility; protected: double getMarginOfError(int bars) const; PatternWithEquals(std::string const &symbol); }; double PatternWithEquals::getMarginOfError(int bars) const { if (_volatility) return _volatility * sqrt(bars) / sqrt(44); else return 0; } PatternWithEquals::PatternWithEquals(std::string const &symbol) : GeometricPattern(symbol) { _volatility = getTickVolatility(symbol); if (_volatility < MIN_VOLATILITY) _volatility = 0.0; } //////////////////////////////////////////////////////////////////// // RectanglePattern //////////////////////////////////////////////////////////////////// class RectanglePattern : public PatternWithEquals { protected: virtual void newPoint(int lastPoint); private: RectanglePattern(DataNodeArgument const &args); friend class DataNode; }; void RectanglePattern::newPoint(int lastPoint) { _description.active = false; if (lastPoint < 4) return; VolumeBlockMinAndMax::TurningPoints const &points = getTurningPoints(); double highestHigh, lowestLow; if (points[lastPoint].orientation == VolumeBlockMinAndMax::vboHigh) { highestHigh = points[lastPoint].price; lowestLow = points[lastPoint - 1].price; } else { lowestLow = points[lastPoint].price; highestHigh = points[lastPoint - 1].price; } double lowestHigh = highestHigh; double highestLow = lowestLow; int firstPoint = lastPoint; for (int i = lastPoint - 2; i >= 0; i--) { bool continues = false; VolumeBlockMinAndMax::TurningPoint const &point = points[i]; const double currentMarginOfError = getMarginOfError(points[lastPoint].index - point.index); if (point.orientation == VolumeBlockMinAndMax::vboHigh) { if ((point.price > highestLow) && (point.price - lowestHigh <= currentMarginOfError) && (highestHigh - point.price <= currentMarginOfError)) { highestHigh = std::max(highestHigh, point.price); lowestHigh = std::min(lowestHigh, point.price); continues = true; } } else { if ((point.price < lowestHigh) && (point.price - lowestLow <= currentMarginOfError) && (highestLow - point.price <= currentMarginOfError)) { highestLow = std::max(highestLow, point.price); lowestLow = std::max(lowestLow, point.price); continues = true; } } if (continues) if ((lowestHigh - highestLow) < std::max(highestLow - lowestLow, highestHigh - lowestHigh)) continues = false; if (continues) firstPoint = i; else break; } if ((lastPoint - firstPoint) >= 4) { newEpoch(); _description.active = true; _description.valid = true; _description.up = points[lastPoint].orientation == VolumeBlockMinAndMax::vboLow; _description.prices.clear(); _description.prices.reserve(lastPoint - firstPoint + 1); for (int i = firstPoint; i <= lastPoint; i++) _description.prices.push_back(points[i].price); VolumeBlocks const &volumeBlocks = getAllBlocks(); _description.startTime = volumeBlocks[points[firstPoint].index].startTime; _description.endTime = volumeBlocks[points[lastPoint].index].endTime; _description.quality = (points[lastPoint].index - points[firstPoint].index + 1) / 4.0; } } RectanglePattern::RectanglePattern(DataNodeArgument const &args) : PatternWithEquals(args.getStringValue()) { afterConstruction(); } //////////////////////////////////////////////////////////////////// // HeadAndShouldersPattern //////////////////////////////////////////////////////////////////// class HeadAndShouldersPattern : public PatternWithEquals { protected: virtual void newPoint(int lastPoint); private: HeadAndShouldersPattern(DataNodeArgument const &args); friend class DataNode; }; HeadAndShouldersPattern::HeadAndShouldersPattern (DataNodeArgument const &args) : PatternWithEquals(args.getStringValue()) { afterConstruction(); } void HeadAndShouldersPattern::newPoint(int lastPoint) { _description.active = false; bool valid = false; if (lastPoint < 4) return; VolumeBlockMinAndMax::TurningPoints const &points = getTurningPoints(); const double marginOfError = getMarginOfError(points[lastPoint].index - points[lastPoint-4].index); if ((fabs(points[lastPoint].price - points[lastPoint-4].price) < marginOfError) && (fabs(points[lastPoint-1].price - points[lastPoint-3].price) < marginOfError)) { if (points[lastPoint].orientation == VolumeBlockMinAndMax::vboHigh) valid = points[lastPoint-2].price > marginOfError + std::max(points[lastPoint].price, points[lastPoint-4].price); else valid = points[lastPoint-2].price < std::min(points[lastPoint].price, points[lastPoint-4].price) - marginOfError; } if (!valid) return; newEpoch(); _description.active = true; _description.valid = true; _description.up = points[lastPoint].orientation == VolumeBlockMinAndMax::vboHigh; _description.prices.clear(); _description.prices.reserve(5); for (int i = lastPoint - 5; i < lastPoint; i++) _description.prices.push_back(points[i].price); VolumeBlocks const &volumeBlocks = getAllBlocks(); _description.startTime = volumeBlocks[points[lastPoint-4].index].startTime; _description.endTime = volumeBlocks[points[lastPoint].index].endTime; _description.quality = (points[lastPoint].index - points[lastPoint-4].index + 1) / 4.0; } //////////////////////////////////////////////////////////////////// // SimpleGeometricPatternAlert //////////////////////////////////////////////////////////////////// class SimpleGeometricPatternAlert : public Alert { public: enum Type { sgpTriangle, sgpBroadening, sgpRectangle, sgpHeadAndShoulders }; private: // The first index is the type, from the enumeration above. // The second index is the direction, true for up. static const std::string _allNames[4][2]; GeometricPattern *_pattern; std::string _patternName; bool _up; Epoch _lastEpoch; void onWakeup(int msgId); SimpleGeometricPatternAlert(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; const std::string SimpleGeometricPatternAlert::_allNames[4][2] = { { "Triangle top", "Triangle bottom" }, { "Broadening top", "Broadening bottom" }, { "Rectangle top", "Rectangle bottom" }, { "Inverted head and shoulders", "Head and shoulders" } }; SimpleGeometricPatternAlert::SimpleGeometricPatternAlert (DataNodeArgument const &args) : _lastEpoch(0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 3); // Symbol, type, up std::string const &symbol = argList[0].getStringValue(); const Type type = (Type)argList[1].getIntValue(); _up = argList[2].getBooleanValue(); _patternName = _allNames[type][_up]; switch (type) { case sgpTriangle: addAutoLink(GeometricPattern::find< NarrowingTrianglePattern > (this, 0, _pattern, symbol)); break; case sgpBroadening: addAutoLink(GeometricPattern::find< BroadeningTrianglePattern > (this, 0, _pattern, symbol)); break; case sgpRectangle: addAutoLink(GeometricPattern::find< RectanglePattern > (this, 0, _pattern, symbol)); break; case sgpHeadAndShoulders: addAutoLink(GeometricPattern::find< HeadAndShouldersPattern > (this, 0, _pattern, symbol)); break; default: assert(false); } onWakeup(0); } void SimpleGeometricPatternAlert::onWakeup(int msgId) { PatternDescription const &description = _pattern->getDescription(); if ((_lastEpoch != description.epoch) && description.valid && (description.up == _up)) { const std::string priceList = _pattern->getPriceList(); std::string msg = _patternName; msg += ". Prices: "; msg += priceList; msg += ". Started "; msg += durationString(description.startTime, getSubmitTime()); msg += " ago. Last turn "; msg += durationString(description.endTime, getSubmitTime()); msg += " ago."; std::string altMsg = "pl="; altMsg += urlEncode(priceList); altMsg +="&d1="; altMsg +=ntoa(getSubmitTime() - description.startTime); altMsg +="&d2="; altMsg +=ntoa(getSubmitTime() - description.endTime); report(msg, altMsg, description.quality); } _lastEpoch = description.epoch; } //////////////////////////////////////////////////////////////////// // DoubleTopResistance //////////////////////////////////////////////////////////////////// class DoubleTopResistance : public PatternWithEquals { protected: void newPoint(int lastPoint); private: DoubleTopResistance(DataNodeArgument const &args); friend class DataNode; }; DoubleTopResistance::DoubleTopResistance(DataNodeArgument const &args) : PatternWithEquals(args.getStringValue()) { afterConstruction(); } void DoubleTopResistance::newPoint(int lastPoint) { _description.active = false; if (lastPoint < 3) return; VolumeBlockMinAndMax::TurningPoints const &points = getTurningPoints(); static const VolumeBlockMinAndMax::Orientation PREFERRED_ORIENTATION = VolumeBlockMinAndMax::vboHigh; if (points[lastPoint].orientation != PREFERRED_ORIENTATION) return; bool firstPointFound = false; int firstPoint = lastPoint; double marginOfError = 0.0; double highestPossible = 0.0; double lowestPossible = 0.0; int matchingPointCount = 0; for (int i = 0; i < lastPoint; i++) if (points[i].orientation == PREFERRED_ORIENTATION) { if (!firstPointFound) { marginOfError = getMarginOfError(points[lastPoint].index - points[i].index); highestPossible = points[lastPoint].price + marginOfError / 2.0; lowestPossible = points[lastPoint].price - marginOfError / 2.0; } if (points[i].price > highestPossible) firstPointFound = false; else if (points[i].price >= lowestPossible) { if (firstPointFound) matchingPointCount++; else { firstPointFound = true; firstPoint = i; matchingPointCount = 2; } } } else if (firstPointFound && (points[i].price >= lowestPossible)) firstPointFound = false; if (!firstPointFound) return; if (points[lastPoint].index - points[firstPoint].index < 21) return; newEpoch(); _description.active = true; _description.valid = true; _description.up = PREFERRED_ORIENTATION == VolumeBlockMinAndMax::vboHigh; _description.prices.clear(); _description.prices.reserve(matchingPointCount); for (int i = firstPoint; i <= lastPoint; i++) if ((points[i].orientation == PREFERRED_ORIENTATION) && (points[i].price <= highestPossible) && (points[i].price >= lowestPossible)) _description.prices.push_back(points[i].price); VolumeBlocks const &volumeBlocks = getAllBlocks(); _description.startTime = volumeBlocks[points[firstPoint].index].startTime; _description.endTime = volumeBlocks[points[lastPoint].index].endTime; _description.quality = (points[lastPoint].index - points[lastPoint-4].index + 1) / 4.0; } //////////////////////////////////////////////////////////////////// // DoubleBottomSupport //////////////////////////////////////////////////////////////////// class DoubleBottomSupport : public PatternWithEquals { protected: void newPoint(int lastPoint); private: DoubleBottomSupport(DataNodeArgument const &args); friend class DataNode; }; DoubleBottomSupport::DoubleBottomSupport(DataNodeArgument const &args) : PatternWithEquals(args.getStringValue()) { afterConstruction(); } void DoubleBottomSupport::newPoint(int lastPoint) { _description.active = false; if (lastPoint < 3) return; VolumeBlockMinAndMax::TurningPoints const &points = getTurningPoints(); static const VolumeBlockMinAndMax::Orientation PREFERRED_ORIENTATION = VolumeBlockMinAndMax::vboLow; if (points[lastPoint].orientation != PREFERRED_ORIENTATION) return; bool firstPointFound = false; int firstPoint = lastPoint; double marginOfError = 0.0; double highestPossible = 0.0; double lowestPossible = 0.0; int matchingPointCount = 0; for (int i = 0; i < lastPoint; i++) if (points[i].orientation == PREFERRED_ORIENTATION) { if (!firstPointFound) { marginOfError = getMarginOfError(points[lastPoint].index - points[i].index); highestPossible = points[lastPoint].price + marginOfError / 2.0; lowestPossible = points[lastPoint].price - marginOfError / 2.0; } if (points[i].price < lowestPossible) firstPointFound = false; else if (points[i].price <= highestPossible) { if (firstPointFound) matchingPointCount++; else { firstPointFound = true; firstPoint = i; matchingPointCount = 2; } } } else if (firstPointFound && (points[i].price <= highestPossible)) firstPointFound = false; if (!firstPointFound) return; if (points[lastPoint].index - points[firstPoint].index < 21) return; newEpoch(); _description.active = true; _description.valid = true; _description.up = PREFERRED_ORIENTATION == VolumeBlockMinAndMax::vboHigh; _description.prices.clear(); _description.prices.reserve(matchingPointCount); for (int i = firstPoint; i <= lastPoint; i++) if ((points[i].orientation == PREFERRED_ORIENTATION) && (points[i].price <= highestPossible) && (points[i].price >= lowestPossible)) _description.prices.push_back(points[i].price); VolumeBlocks const &volumeBlocks = getAllBlocks(); _description.startTime = volumeBlocks[points[firstPoint].index].startTime; _description.endTime = volumeBlocks[points[lastPoint].index].endTime; _description.quality = (points[lastPoint].index - points[lastPoint-4].index + 1) / 4.0; } //////////////////////////////////////////////////////////////////// // DoubleTopAlert //////////////////////////////////////////////////////////////////// class DoubleTopAlert : public Alert { private: GeometricPattern *_pattern; bool _up; Epoch _lastEpoch; void onWakeup(int msgId); DoubleTopAlert(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; DoubleTopAlert::DoubleTopAlert(DataNodeArgument const &args) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, up std::string const &symbol = argList[0].getStringValue(); _up = argList[1].getBooleanValue(); if (_up) addAutoLink(GeometricPattern::find< DoubleTopResistance > (this, 0, _pattern, symbol)); else addAutoLink(GeometricPattern::find< DoubleBottomSupport > (this, 0, _pattern, symbol)); onWakeup(0); } void DoubleTopAlert::onWakeup(int msgId) { PatternDescription const &description = _pattern->getDescription(); if ((_lastEpoch != description.epoch) && description.valid) { const std::string count = ntoa(description.prices.size()); const std::string priceList = _pattern->getPriceList(); std::string msg; switch (description.prices.size()) { case 2: msg = "Double"; break; case 3: msg = "Triple"; break; case 4: msg = "Quadruple"; break; default: msg = count; } if (_up) msg += " top."; else msg += " bottom."; msg += " Prices: "; msg += priceList; msg += ". Started "; msg += durationString(description.startTime, getSubmitTime()); msg += " ago. Last turn "; msg += durationString(description.endTime, getSubmitTime()); msg += " ago."; std::string altMsg = "count="; altMsg += count; altMsg += "&pl="; altMsg += urlEncode(priceList); altMsg += "&d1="; altMsg += ntoa(getSubmitTime() - description.startTime); altMsg += "&d2="; altMsg += ntoa(getSubmitTime() - description.endTime); report(msg, altMsg, description.quality); } _lastEpoch = description.epoch; } //////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////// void initializeGeometricPatterns() { GenericDataNodeFactory::sf< SimpleGeometricPatternAlert > ("TriangleBottom", symbolPlaceholderObject, SimpleGeometricPatternAlert::sgpTriangle, true); GenericDataNodeFactory::sf< SimpleGeometricPatternAlert > ("TriangleTop", symbolPlaceholderObject, SimpleGeometricPatternAlert::sgpTriangle, false); GenericDataNodeFactory::sf< SimpleGeometricPatternAlert > ("BroadeningBottom", symbolPlaceholderObject, SimpleGeometricPatternAlert::sgpBroadening, true); GenericDataNodeFactory::sf< SimpleGeometricPatternAlert > ("BroadeningTop", symbolPlaceholderObject, SimpleGeometricPatternAlert::sgpBroadening, false); GenericDataNodeFactory::sf< SimpleGeometricPatternAlert > ("RectangleBottom", symbolPlaceholderObject, SimpleGeometricPatternAlert::sgpRectangle, true); GenericDataNodeFactory::sf< SimpleGeometricPatternAlert > ("RectangleTop", symbolPlaceholderObject, SimpleGeometricPatternAlert::sgpRectangle, false); GenericDataNodeFactory::sf< SimpleGeometricPatternAlert > ("HeadAndShoulders", symbolPlaceholderObject, SimpleGeometricPatternAlert::sgpHeadAndShoulders, true); GenericDataNodeFactory::sf< SimpleGeometricPatternAlert > ("InvertedHeadAndShoulders", symbolPlaceholderObject, SimpleGeometricPatternAlert::sgpHeadAndShoulders, false); GenericDataNodeFactory::sf< DoubleTopAlert > ("DoubleTop", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< DoubleTopAlert > ("DoubleBottom", symbolPlaceholderObject, false); }