#include #include "../../shared/SimpleLogFile.h" #include "../misc_framework/GenericDataNodes.h" #include "../data_framework/GenericTosData.h" #include "../data_framework/TradeDirection.h" #include "../data_framework/StandardCandles.h" #include "../data_framework/SimpleMarketData.h" #include "TiqMisc.h" #include "ReportAlertsThread.h" #include "BigBear.h" class BigBearAlerts : public GenericDataNode { private: static const std::string category[2]; static const std::string oldCategory[2]; std::string _symbol; TradeDirection _direction; StandardCandles *_candleData; GenericTosDataNode *_tosData; GenericDataNode *_up60Data; double _previousClose; std::string const &getCategory() const; std::string const &getOldCategory() const; void oldAlerts(); void newAlerts(); void onWakeup(int msgId); BigBearAlerts(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(std::string const &symbol, bool up) { BigBearAlerts *node; return findHelper(NULL, 0, node, argList(symbol, up)); } }; const std::string BigBearAlerts::category[2] = { "BigBear-Down", "BigBear-Up" }; std::string const &BigBearAlerts::getCategory() const { return category[(int)_direction.isLong()]; } void BigBearAlerts::onWakeup(int msgId) { newAlerts(); oldAlerts(); } void BigBearAlerts::newAlerts() { if (!_tosData->getValid()) return; StandardCandles::CandleArray const &candles = _candleData->getHistory(); if (candles.size() < 6) return; TosData const &last = _tosData->getLast(); if (!(last.price && last.open)) return; // get rid of the rules that state it has to be up from the close, and up // from the open. I want to see any drive up or down of 1% no matter where // the stock is on the day. // email 1/26/2011 //if (!_direction.upByAtLeast(last.price - _previousClose, 0.40)) // return; //if (!_direction.upByAtLeast(last.price - last.open, 0.40)) // return; double up60; bool up60Valid; _up60Data->getDouble(up60Valid, up60); if (!up60Valid) { // What if it's not valid? That's almost never the case, but that // suggests a stock with very little trading. If something hasn't // traded at all before now, it's hard to believe it's up $1 return; } else { if (!_direction.upByAtLeast(up60, 0.48)) // Must be up by at least 48 cents... return; if (!(_direction.upByAtLeast(up60 / (last.price - up60), 0.01) || _direction.upByAtLeast(up60, 0.95))) // ...and at least 1% in the last hour. // email 1/26/2011: Any move over 95 cents triggers an alert as well, // no matter the percentage move of the stock. return; } const double dailyExtreme = _direction.isLong()?last.high:last.low; if (!dailyExtreme) return; double values[4]; int i = 0; bool nearExtreme = false; int smallRangeCount = 0; const double epsilon = 0.00005; // If it's +/- epsilon, we say it's equal. for (StandardCandles::CandleArray::const_reverse_iterator it = candles.rbegin(); i < 4; it++, i++) { const int range = it->high - it->low; if (range < 0.12 - epsilon) smallRangeCount++; if (_direction.isLong()) values[i] = it->high; else values[i] = it->low; if (!nearExtreme) nearExtreme = !_direction.upByAtLeast(dailyExtreme - values[i], 0.05); } if (!nearExtreme) // email 1/26/2011: one of those [the last four] bars has to be within 5 // cents of the high. return; if (smallRangeCount < 3) // email 1/26/2011: Three out of the last four bars have to have a range // less then [sic] 12 cents return; int up = 1; int down = 1; const double compare = values[0]; for (i = 1; i < 4; i++) { const double current = values[i]; if ((current >= compare - epsilon) && (current <= compare + 0.01 + epsilon)) up++; if ((current <= compare + epsilon) && (current >= compare - 0.01 - epsilon)) down++; } const int count = std::max(up, down); if (count < 3) // 3 of out of 4 "five-minute" bars that have to have a high within one // penny of each other. Before 1/26/2011 the requirement was 3 out of 5. return; std::string msg = "symbol="; msg += urlEncode(_symbol); msg += "&last="; msg += formatPrice(last.price); msg += "&time="; msg += formatTimeMinutes(getSubmitTime()); msg += "&prev_close="; msg += formatPrice(_previousClose); msg += "&volume="; msg += ntoa(last.volume); msg += "&count="; msg += ntoa(count); msg += "&up60="; if (up60Valid) msg += dtoaFixed(up60,4); else msg += "INVALID"; ReportAlertsThread::getInstance()->reportAlert(getCategory(), msg); /* TclList debug; debug<<__FILE__<<__LINE__<<__FUNCTION__ <<"_symbol"<<_symbol <<"last"<open<high<low<close<volume; recentCandles<getValid()) return; StandardCandles::CandleArray const &candles = _candleData->getHistory(); if (candles.size() < 6) return; TosData const &last = _tosData->getLast(); if (!(last.price && last.open)) return; if (!_direction.upByAtLeast(last.price - _previousClose, 0.40)) return; if (!_direction.upByAtLeast(last.price - last.open, 0.40)) return; double up60; bool up60Valid; _up60Data->getDouble(up60Valid, up60); if (up60Valid) { // What if it's not valid? That's almost never the case, so it's // not that important. if (!_direction.upByAtLeast(up60, 0.48)) // Must be up by at least 48 cents... return; if (!_direction.upByAtLeast(up60 / (last.price - up60), 0.01)) // ...and at least 1% in the last hour. return; } const double dailyExtreme = _direction.isLong()?last.high:last.low; if (!dailyExtreme) return; double values[6]; int i = 0; bool touchedExtreme = false; for (StandardCandles::CandleArray::const_reverse_iterator it = candles.rbegin(); i < 6; it++, i++) { if (_direction.isLong()) values[i] = it->high; else values[i] = it->low; if (!touchedExtreme) touchedExtreme = dailyExtreme == values[i]; } if (!touchedExtreme) return; int up = 1; int down = 1; const double epsilon = 0.00005; // If it's +/- epsilon, we say it's equal. const double compare = values[0]; for (i = 1; i < 6; i++) { const double current = values[i]; if ((current >= compare - epsilon) && (current <= compare + 0.01 + epsilon)) up++; if ((current <= compare + epsilon) && (current >= compare - 0.01 - epsilon)) down++; } const int count = std::max(up, down); if (count < 3) return; std::string msg = "symbol="; msg += urlEncode(_symbol); msg += "&last="; msg += formatPrice(last.price); msg += "&time="; msg += formatTimeMinutes(getSubmitTime()); msg += "&prev_close="; msg += formatPrice(_previousClose); msg += "&volume="; msg += ntoa(last.volume); msg += "&count="; msg += ntoa(count); ReportAlertsThread::getInstance()->reportAlert(getOldCategory(), msg); } BigBearAlerts::BigBearAlerts(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); if (!_previousClose) return; addAutoLink(GenericTosDataNode::find(_tosData, _symbol)); addAutoLink(StandardCandles::find(this, 0, _candleData, _symbol, 5)); const DataNodeArgument factory = GenericDataNodeFactory::findFactory("TimedMovement60"); addAutoLink(GenericDataNodeFactory::replaceAndFind(factory, NULL, 0, _up60Data, symbolPlaceholder, _symbol)); } ///////////////////////////////////////////////////////////////////// // Global ///////////////////////////////////////////////////////////////////// static std::vector< DataNodeLink * > cleanup; static void alertsForSymbol(std::string const &symbol) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__<