#include #include #include #include "../../shared/SimpleLogFile.h" #include "../../shared/DatabaseWithRetry.h" #include "../misc_framework/GenericDataNodes.h" #include "../data_framework/SynchronizedTimers.h" #include "../data_framework/GenericTosData.h" #include "../data_framework/SimpleMarketData.h" #include "../data_framework/TradeDirection.h" #include "../misc_framework/CsvFileDataNodes.h" #include "ReportAlertsThread.h" #include "TiqMisc.h" #include "VitoGarfi.h" // Look in WestonAlerts.C for more stuff for VitoGarfi. ///////////////////////////////////////////////////////////////////// // VitoGarfiVolume ///////////////////////////////////////////////////////////////////// class VitoGarfiVolume : public DataNode { private: const std::string _symbol; const double _previousClose; BarCounter *_barCounterData; GenericTosDataNode *_tosData; Integer _volumeTrigger; Integer _candleStartVolume; bool _triggeredThisTime; double getPrevClose() const { return _previousClose; } void checkForTrigger(); void prepairNextCandle(); enum { wTOS, wBarCounter }; void onWakeup(int msgId); VitoGarfiVolume(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(std::string const &symbol) { VitoGarfiVolume *node; return findHelper(NULL, 0, node, symbol); } }; VitoGarfiVolume::VitoGarfiVolume(DataNodeArgument const &args) : _symbol(args.getStringValue()), _previousClose(getPreviousClose(_symbol)) { addAutoLink(GenericTosDataNode::find(this, wTOS, _tosData, _symbol)); addAutoLink(BarCounter::find(this, wBarCounter, _barCounterData, 5)); // These are all pretty much ignored. We rely on the bar counter to tell us // that we are have not been initialized yet. _volumeTrigger = 0; _candleStartVolume = 0; _triggeredThisTime = true; } void VitoGarfiVolume::onWakeup(int msgId) { switch (msgId) { case wTOS: checkForTrigger(); break; case wBarCounter: prepairNextCandle(); break; } } void VitoGarfiVolume::checkForTrigger() { if (_triggeredThisTime) return; const int currentBar = _barCounterData->getCurrentBar(); if ((currentBar == 0) || (currentBar == BarCounter::UNKNOWN_TIME)) return; if (!_tosData->getValid()) return; const Integer volume = _tosData->getLast().volume - _candleStartVolume; if (volume > _volumeTrigger) { std::string info = "symbol="; info += urlEncode(_symbol); info += "&volume="; info += ntoa(volume); if (_tosData->getLast().price) { info += "&last="; info += urlEncode(formatPrice(_tosData->getLast().price)); } if (getPrevClose()) { info += "&prev_close="; info += urlEncode(formatPrice(getPrevClose())); } info += "&time="; info += formatTime(time(NULL)); ReportAlertsThread::getInstance()->reportAlert("VitoGarfi-Volume", info); _triggeredThisTime = true; } } void VitoGarfiVolume::prepairNextCandle() { if (_barCounterData->getTimePhase() != BarCounter::tpNotify) // Only update once per candle transition. return; if (!_tosData->getValid()) { // There really isn't any good answer here! if (_triggeredThisTime) _candleStartVolume += _volumeTrigger; } else { if (_triggeredThisTime) { const Integer recentVolume = _tosData->getLast().volume - _candleStartVolume; if (recentVolume > _volumeTrigger) // This should always be true. But it is possible for volume to // go backwards in case of a correction. _volumeTrigger = recentVolume; } } _triggeredThisTime = false; _candleStartVolume = _tosData->getLast().volume; } ///////////////////////////////////////////////////////////////////// // VitoGarfiBreakHiLo // // This is based on WestonBreakHiLo. We removed the requirement to // be up a certain amount for the day. ///////////////////////////////////////////////////////////////////// class VitoGarfiBreakHiLo : public DataNode { private: std::string _symbol; TradeDirection _direction; GenericTosDataNode *_tosData; double _previousClose; double getPrevClose() const { return _previousClose; } TosData const &getLast() const { return _tosData->getLast(); } double _lastHighLow; void onValidHighLow(double value); void onTos(); void report() const; void onWakeup(int msgId); VitoGarfiBreakHiLo(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(std::string const &symbol, bool up) { VitoGarfiBreakHiLo *node; return findHelper(NULL, 0, node, argList(symbol, up)); } }; void VitoGarfiBreakHiLo::report() const { std::string info = "symbol="; info += urlEncode(_symbol); /* if (_tosData->getValid() && _tosData->getLast().price) { info += "&last="; info += urlEncode(formatPrice(_tosData->getLast().price)); } if (getPrevClose()) { info += "&prev_close="; info += urlEncode(formatPrice(getPrevClose())); } */ std::string category = _direction.isLong()?"VitoGarfi-BreakHi":"VitoGarfi-BreakLow"; ReportAlertsThread::getInstance()->reportAlert(category, info); } void VitoGarfiBreakHiLo::onWakeup(int msgId) { // TOS event. if (_tosData->getValid()) onTos(); } void VitoGarfiBreakHiLo::onTos() { TosData const &last = getLast(); if (last.high && last.low) { // If the normal highs and lows are available, use them exclusively. const double price = _direction.isLong()?last.high:last.low; if (price > 0) onValidHighLow(price); } else { // Try to compute the premarket highs and lows ourselves. if (!last.newPrint) // Don't make an alert just because we connected to the datafeed. return; const double price = last.price; if (price <= 0) return; if (_direction.moreReportable(price, _lastHighLow)) // Note: With the normal highs and lows we can recover from a bad // print. If the high drops and then goes up again, we'll report it // again on the up swing. For premarket we have no way to know about // a bad print. If the price goes down, we just say it's not a high. onValidHighLow(price); } } void VitoGarfiBreakHiLo::onValidHighLow(double price) { if (_direction.moreReportable(price, _lastHighLow)) report(); _lastHighLow = price; } VitoGarfiBreakHiLo::VitoGarfiBreakHiLo(DataNodeArgument const &args) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, Long _symbol = argList[0].getStringValue(); _direction = argList[1].getBooleanValue(); _previousClose = getPreviousClose(_symbol); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, _symbol)); _lastHighLow = _direction.leastReportable(); } ///////////////////////////////////////////////////////////////////// // VitoGarfiStrongVolume // // Trigger when the volume for the day hits 1.5 times normal. So // It's similar to the Strong Volume alert, with a minimum quality // of 1.5. ///////////////////////////////////////////////////////////////////// class VitoGarfiStrongVolume : public DataNode { private: const std::string _symbol; const double _previousClose; GenericTosDataNode *_tosData; Integer _volumeTrigger; bool _triggered; void onWakeup(int msgId); VitoGarfiStrongVolume(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(std::string const &symbol) { VitoGarfiStrongVolume *node; return findHelper(NULL, 0, node, symbol); } }; void VitoGarfiStrongVolume::onWakeup(int msgId) { if (_triggered) return; if (!_tosData->getValid()) return; Integer volume = _tosData->getLast().volume; if (volume < _volumeTrigger) return; std::string info = "symbol="; info += urlEncode(_symbol); info += "&volume="; info += ntoa(volume); if (_tosData->getLast().price) { info += "&last="; info += urlEncode(formatPrice(_tosData->getLast().price)); } if (_previousClose) { info += "&prev_close="; info += urlEncode(formatPrice(_previousClose)); } info += "&time="; info += formatTime(time(NULL)); ReportAlertsThread::getInstance()->reportAlert("VitoGarfi-StrongVolume", info); _triggered = true; } VitoGarfiStrongVolume::VitoGarfiStrongVolume(DataNodeArgument const &args) : _symbol(args.getStringValue()), _previousClose(getPreviousClose(_symbol)), _volumeTrigger(getAverageDailyVolume(_symbol)*3/2), _triggered(false) { addAutoLink(GenericTosDataNode::find(this, 0, _tosData, _symbol)); } ///////////////////////////////////////////////////////////////////// // ContinuousBollinger // // Compute a bollinger band, where the last data point is changing // a lot. We expect the first n - 1 points to stay the same (i.e. // we are not adding new candles and throwing out old ones). But // the last candle is changing all the time, probably with every // print. ///////////////////////////////////////////////////////////////////// class ContinuousBollinger { private: // In wikipedia's terms, these are s0, s1, and s2, respectively. int _n; double _sx; double _sxx; public: void clear() { _n = 0; _sx = 0; _sxx = 0; } ContinuousBollinger() { clear(); } void add(double x) { _n++; _sx += x; _sxx += x*x; } void get(double x, bool &success, double &sma, double &stdDev) { const int n = _n + 1; const double sx = _sx + x; const double sxx = _sxx + x*x; if (n < 2) { success = false; return; } success = true; sma = sx / n; // http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods stdDev = sqrt(n * sxx - sx * sx) / n; } TclList debugDump() { return TclList()<<_n<<_sx<<_sxx; } }; ///////////////////////////////////////////////////////////////////// // ContinuousBollingerDataNode // // This computes a daily bollinger band. This is different from // most of our formulas because. // 1) We read historical data directly from the database, rather // than a CSV file. // 2) We continuously update with the current price, rather than // freezing the price at yesterday's close. ///////////////////////////////////////////////////////////////////// class ContinuousBollingerDataNode : public DataNode { private: static __thread DatabaseWithRetry *_database; static DatabaseWithRetry *getDatabase(); GenericTosDataNode *_tosData; ContinuousBollinger _accumulator; double _lastInput; double _cachedTop; double _cachedBottom; bool cacheValid(); ContinuousBollingerDataNode(DataNodeArgument const &args); friend class DataNode; public: void get(bool top, bool &valid, double &value); TclList debugDump() { return _accumulator.debugDump(); } static DataNodeLink *find(DataNodeListener *listener, int msgId, ContinuousBollingerDataNode *&node, std::string const &symbol, int days) { return findHelper(listener, msgId, node, argList(symbol, days)); } static DataNodeLink *find(ContinuousBollingerDataNode *&node, std::string const &symbol, int days) { return find(NULL, 0, node, symbol, days); } }; ContinuousBollingerDataNode::ContinuousBollingerDataNode (DataNodeArgument const &args) { // Reset cache // No input should match this, so any time we check the cache we only need to // compare the current input to this value. _lastInput = std::numeric_limits< double >::quiet_NaN(); // Load arguments. DataNodeArgumentVector const &argList = args.getListValue(); // symbol, days assert(argList.size() == 2); const std::string &symbol = argList[0].getStringValue(); const int days = argList[1].getIntValue(); assert(days > 2); // Load from database DatabaseWithRetry &database = *getDatabase(); std::string sql = "SELECT close FROM bars_d WHERE symbol='" + mysqlEscapeString(symbol) + "' ORDER BY date DESC LIMIT " + ntoa(days - 1); for (MysqlResultRef result = database.tryQueryUntilSuccess(sql); result->rowIsValid(); result->nextRow()) { const double price = result->getDoubleField(0, std::numeric_limits< double >::quiet_NaN()); if (isfinite(price)) _accumulator.add(price); } // Get live data // Curently we are receiving all notifications from the datafeed and passing // them on to our listeners. We would expect our listener to ignore this, // and trigger off of the tos data directly, but someone could listen to this. addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } void ContinuousBollingerDataNode::get(bool top, bool &valid, double &value) { valid = cacheValid(); if (!valid) return; if (top) value = _cachedTop; else value = _cachedBottom; } bool ContinuousBollingerDataNode::cacheValid() { if (!_tosData->getValid()) return false; const double price = _tosData->getLast().price; if (!price) return false; if (price == _lastInput) // Keep cache in tact! return true; bool valid; double sma, stdDev; _accumulator.get(price, valid, sma, stdDev); if (!valid) return false; _lastInput = price; _cachedTop = sma + 2 * stdDev; _cachedBottom = sma - 2 * stdDev; return true; } DatabaseWithRetry *ContinuousBollingerDataNode::getDatabase() { if (!_database) { _database = new DatabaseWithRetry("@candles", "candles"); } return _database; } __thread DatabaseWithRetry *ContinuousBollingerDataNode::_database; ///////////////////////////////////////////////////////////////////// // VitoGarfiBollinger // // This is like (premarket highs + normal highs + postmarket highs) // with a filter that we are above the daily bollinger band. And // a flip of the same. The number of days is an input. ///////////////////////////////////////////////////////////////////// class VitoGarfiBollinger : public DataNode { private: std::string _symbol; TradeDirection _direction; std::string _category; double _prevClose; GenericTosDataNode *_tosData; ContinuousBollingerDataNode *_bollinger10Data; ContinuousBollingerDataNode *_bollinger20Data; double _highLowPrice; bool _marketHasOpened; void getBollingerBandPrice(bool &valid, double &value); VitoGarfiBollinger(DataNodeArgument const &args); friend class DataNode; void onWakeup(int msgId); public: static DataNodeLink *find(std::string const &symbol, bool up, std::string const &category) { VitoGarfiBollinger *node; return findHelper(NULL, 0, node, argList(symbol, up, category)); } }; void VitoGarfiBollinger::onWakeup(int msgId) { if (!_tosData->getValid()) return; const double price = _tosData->getLast().price; const bool open = _tosData->getLast().open; // Reset the highs and lows at the open. if (_marketHasOpened != open) { _highLowPrice = _direction.leastReportable(); _marketHasOpened = open; } if (!_direction.moreReportable(price, _highLowPrice)) // We can only fire on a new high or a new low. return; _highLowPrice = price; bool bollingerBandValid; double bollingerBandPrice; getBollingerBandPrice(bollingerBandValid, bollingerBandPrice); if (!bollingerBandValid) return; if (!_direction.moreReportable(price, bollingerBandPrice)) // We can only fire if we are outside of the bollinger band. return; // We have an alert! std::string info = "symbol="; info += urlEncode(_symbol); info += "&last="; info += urlEncode(formatPrice(price)); if (_prevClose) { info += "&prev_close="; info += urlEncode(formatPrice(_prevClose)); } // sma_divergence is what quickstrike uses. Presumably the client knows // how to read that. info += "&sma_divergence="; info += formatPrice(price - bollingerBandPrice); info += "&time="; info += formatTime(time(NULL)); ReportAlertsThread::getInstance()->reportAlert(_category, info); } void VitoGarfiBollinger::getBollingerBandPrice(bool &valid, double &value) { bool valid10; double value10; _bollinger10Data->get(_direction.isLong(), valid10, value10); bool valid20; double value20; _bollinger20Data->get(_direction.isLong(), valid20, value20); valid = true; if (valid10) if (valid20) value = _direction.mostReportable(value10, value20); else value = value10; else if (valid20) value = value20; else valid = false; } VitoGarfiBollinger::VitoGarfiBollinger(DataNodeArgument const &args) : _marketHasOpened(false) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 3); // Symbol, Long, Category _symbol = argList[0].getStringValue(); _direction = argList[1].getBooleanValue(); _category = argList[2].getStringValue(); _prevClose = getPreviousClose(_symbol); _highLowPrice = _direction.leastReportable(); addAutoLink(ContinuousBollingerDataNode::find(_bollinger10Data, _symbol, 10)); addAutoLink(ContinuousBollingerDataNode::find(_bollinger20Data, _symbol, 20)); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, _symbol)); TclList msg; msg<debugDump() <<"_bollinger20Data"<<_bollinger20Data->debugDump(); sendToLogFile(msg); } ///////////////////////////////////////////////////////////////////// // Global ///////////////////////////////////////////////////////////////////// static std::vector< DataNodeLink * > cleanup; static void alertsForSymbol(std::string const &symbol) { TclList msg; msg<