#include #include #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 "../data_framework/IntradaySma.h" #include "../data_framework/LinearRegression.h" #include "TiqMisc.h" #include "ReportAlertsThread.h" #include "WestonDatabaseMonitor.h" #include "JoeFavaloro.h" // For detailed requirements see "TIQ - JoeFavaloro" in google docs. // // TODO: Deal with missing candles better. Currently we look for more more than 5 candles in the pattern, but we just ignore empty candles. The requirements weren't specific, so I suppose this is a defensible position. static const std::string SYMBOL_LIST = "data/JoeFavaloro_symbols.txt"; static const int MAX_LIST_SIZE = 50; ///////////////////////////////////////////////////////////////////// // JoeFavaloroLists // // This structure was copied directly from the SmbRadar lists. The // formulas are different, and some of the features of SmbRadar will // not be required, but the structure is similar. ///////////////////////////////////////////////////////////////////// class JoeFavaloroLists : public DataNode { private: struct CurrentData { public: std::string symbol; double price; double previousClose; double high; double low; int highCount; int lowCount; Integer volume; typedef std::vector< CurrentData > List; }; class DataType { // This represents a window or a pair of windows. Sometimes two windows // are identical except for their sort order. public: enum Type { Gainers, HighCount, LowCount }; private: Type _type; // This can't be const because we want to live in a vector. double getSortValue(CurrentData const &data) const; static void add(std::string &output, std::string const &fieldName, std::string const &id, std::string fieldValue); static void add(std::string &output, std::string const &fieldName, std::string const &id, double fieldValue); static void add(std::string &output, std::string const &fieldName, std::string const &id, Integer fieldValue); static void add(std::string &output, std::string const &fieldName, std::string const &id, int fieldValue); public: DataType(Type type = Gainers) : _type(type) { } bool operator ()(CurrentData const &a, CurrentData const &b) const; bool valid(CurrentData const &data) const; std::string smallEndName() const; std::string bigEndName() const; bool smallEndBullish() const; bool bigEndBullish() const; void dump(CurrentData const &, std::string &output, int id) const; void dumpAll(CurrentData const &, std::string &output, int id) const; typedef std::vector< DataType > List; static void getAll(List &); }; class DataProvider : public DataNode { private: const std::string _symbol; const double _previousClose; GenericTosDataNode *_tosData; double _lastHigh, _lastLow; int _highCount, _lowCount; enum { wTos }; void onWakeup(int msgId); void onTos(); DataProvider(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(DataProvider *&node, std::string const &fileName); CurrentData getCurrentData() const; typedef std::vector< DataProvider * > List; }; void sendMessage(std::string const &window, std::string const &message); typedef std::map< std::string, int > Rank; template < class IT > void addFromIterator(std::string &output, DataType dataType, IT begin, IT end, int max, Rank &addTo); void buildAndSend(DataType dataType, CurrentData::List const &sorted, bool lowestFirst); DataType::List _dataTypes; DataProvider::List _normalSymbols; void checkForMessages(); void loadList(DataProvider::List &providers, std::string const &listName); void onBroadcast(BroadcastMessage &message, int msgId); TimerThread::Helper _timer; // 0 for the first position. 1 for the second position, etc. // Each time we re do the lists, we record the ranking of each stock. A // stock could possibly be in both lists. Notice that we have 4 different // rankings. We group the two bullish ones and the two bearish ones. The // The stock's position in _bullishRanks is the highest of its positions in // the highs list and the gainers list. Simialr for _bearishRanks. Rank _bullishRanks; Rank _bearishRanks; JoeFavaloroLists(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(JoeFavaloroLists *&node) { return findHelper(NULL, 0, node, DataNodeArgument()); } static DataNodeLink *find() { JoeFavaloroLists *node; return find(node); } // 1.0 is the highest rank. 0.0 is the lowest. 0.0 means unranked. Even // the lowest rank is slightly higher than 0. double getBullishScore(std::string const symbol) const; double getBearishScore(std::string const symbol) const; }; ///////////////////////////////////////////////////////////////////// // JoeFavaloroConsolidation ///////////////////////////////////////////////////////////////////// class JoeFavaloroConsolidation : public DataNode { private: static const std::string category[2][2]; static double minTVol; std::string _symbol; TradeDirection _direction; std::string _minutes; GenericTosDataNode *_tosData; StandardCandles *_candleData; GenericDataNode *_stdDev; GenericDataNode *_sma; JoeFavaloroLists *_listData; double _previousClose; std::string const getCategory(bool confirmed) const; double _lastDailyExtreme; bool _primed; int _alertsThisChain; // Not meaningful unless primed. int _barsSincePrimed; // Not meaningful unless primed. double _primedHigh; // Not meaningful unless _barsSincePrimed > 0. double _primedLow; // Not meaningful unless _barsSincePrimed > 0. enum {wCandle, wTos}; void onWakeup(int msgId); void onCandle(); void onTos(); void onBroadcast(BroadcastMessage &message, int msgId); std::string getSymbol() const { return _symbol; } TradeDirection getDirection() const { return _direction; } void checkForReport(StandardCandles::SingleCandle const &check); double positionInBB() const; double relVol() const; static double roundPrice(double p); JoeFavaloroConsolidation(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(std::string const &symbol, bool up, int minutes) { JoeFavaloroConsolidation *node; return findHelper(NULL, 0, node, argList(symbol, up, minutes)); } static DataNodeLink *find() { JoeFavaloroConsolidation *node; return findHelper(NULL, 0, node, DataNodeArgument()); } static double getMinTVol() { return minTVol; } }; void JoeFavaloroConsolidation::onWakeup(int msgId) { switch (msgId) { case wCandle: onCandle(); break; case wTos: onTos(); break; } } void JoeFavaloroConsolidation::onCandle() { // Check for an alert this time. StandardCandles::CandleArray const &candles = _candleData->getHistory(); if (!candles.empty()) checkForReport(candles[candles.size() - 1]); // Prepare for next time. if (_primed) { if (_barsSincePrimed == 0) { if (candles.empty()) _primed = false; else { StandardCandles::SingleCandle const ¤t = candles[candles.size() - 1]; _primedHigh = roundPrice(current.high); _primedLow = roundPrice(current.low); } } _barsSincePrimed++; if (_barsSincePrimed >= 10) // Bar 0 is where we saw the new high. In bars 1-5 we can report an alert. _primed = false; } } double JoeFavaloroConsolidation::roundPrice(double p) { // Round to the nearist penny. This was not explicitly specified. But it // will make things more like realtick, which seemed to be good. return round(p*100.0)/100.0; } void JoeFavaloroConsolidation::onTos() { if (!_tosData->getValid()) return; TosData const &last = _tosData->getLast(); const double newExtreme = roundPrice(_direction.isLong()?last.high:last.low); if (!newExtreme) return; if (_direction.moreReportable(newExtreme, _lastDailyExtreme)) { _primed = true; _alertsThisChain = 0; _barsSincePrimed = 0; } _lastDailyExtreme = newExtreme; } void JoeFavaloroConsolidation::checkForReport(StandardCandles::SingleCandle const &check) { if (!_primed) return; if (_barsSincePrimed == 0) return; if ((roundPrice(check.high) > _primedHigh) || (roundPrice(check.low) < _primedLow)) { _primed = false; return; } if (_tosData->getValid()) if (_tosData->getLast().volume < minTVol) // I'm not sure how to deal with the special cases. I always check the // TOS to make sure it's valid before reading it. But we probably // wouldn't get here if it wasn't. I'm optimistically skipping this test // (allowing more alerts to go through) if we don't know the current // volume. In several places we check if the volume is 0. Again, that's // just a standard test, and I'm not sure of the conditions that I'm // looking for. That could mean that we have a special symbol which // does't track volume. I'll assume that doesn't happen since this // alert doesn't track indexes, forex, etc. // // If we forget about those special cases, we do not report an alert if // the current volume today is less than the user defined cutoff. return; double rank; if (_direction.isLong()) rank = _listData->getBullishScore(getSymbol()); else rank = _listData->getBearishScore(getSymbol()); if (rank <= 0.0) // New requirement, phone call with Brad, 11/29/2010 return; bool confirmed = false; if (_direction.isLong()) { confirmed = roundPrice(check.high) == _primedHigh; } else { confirmed = roundPrice(check.low) == _primedLow; } std::string msg = "symbol="; msg += urlEncode(getSymbol()); if (check.close != 0) { //msg += "&last="; //msg += formatPrice(check.close); if (_previousClose != 0) { msg += "&change="; msg += formatPrice((check.close - _previousClose) / _previousClose * 100.0); } } msg += "&time="; msg += formatTimeMinutes(getSubmitTime()); msg += "&count="; msg += ntoa(_barsSincePrimed + 1); if (_tosData->getValid() && (_tosData->getLast().volume)) { msg += "&volume="; msg += ntoa(_tosData->getLast().volume); } const double bb = positionInBB(); if (finite(bb)) { msg += "&bb="; msg += formatPrice(bb); } const double rv = relVol(); if (finite(rv)) { msg += "&rv="; msg += formatPrice(rv); } if (rank) { // We send the rank as a double. 0 for not ineresting and 1 for very // interesting. That makes it easy if we want to change the number of // things we are looking at. msg += "&rank="; msg += formatPrice(rank); } ReportAlertsThread::getInstance()->reportAlert(getCategory(confirmed), msg); /* static time_t lastReport = 0; if (lastReport != getSubmitTime()) { lastReport = getSubmitTime(); struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); TclList debug; debug<<__FILE__<<__LINE__<<__FUNCTION__ <<"getSubmitTime()"<getValid()) return NAN; const double price = _tosData->getLast().price; double sma1, stdDev1; bool valid; _stdDev->getDouble(valid, stdDev1); if (!valid) return NAN; _sma->getDouble(valid, sma1); if (!valid) return NAN; const double bottom = sma1 - 2 * stdDev1; const double height = 4 * stdDev1; // This is our standard measure. 0 means touching the lower band, 100 means // touching the upper band. 50 is in the middle. return (price - bottom) / height * 100.0; } double JoeFavaloroConsolidation::relVol() const { static const int HISTORICAL_CANDLE_COUNT = 5; StandardCandles::CandleArray const &candles = _candleData->getHistory(); const int currentCandle = candles.size() - 1; const int firstHistorical = currentCandle - HISTORICAL_CANDLE_COUNT; if (firstHistorical < 0) // Not enough candles! return NAN; Integer historicalVolume = 0; for (int i = firstHistorical; i < currentCandle; i++) historicalVolume += candles[i].volume; return candles[currentCandle].volume / (historicalVolume / (double)HISTORICAL_CANDLE_COUNT); } JoeFavaloroConsolidation::JoeFavaloroConsolidation(DataNodeArgument const &args) { if (!args) { // This is the special node which listens to the database. registerForBroadcast(WestonDatabaseMonitor::getJoeFavaloroNotesChannel(), 0); return; } DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 3); // Symbol, Long, Minutes _symbol = argList[0].getStringValue(); _direction = argList[1].getBooleanValue(); const int minutes = argList[2].getIntValue(); _minutes = ntoa(minutes); _previousClose = getPreviousClose(_symbol); _lastDailyExtreme = _direction.mostReportable(); _primed = false; addAutoLink(GenericTosDataNode::find(this, wTos, _tosData, _symbol)); addAutoLink(StandardCandles::find(this, wCandle, _candleData, _symbol, minutes, false)); addAutoLink(JoeFavaloroLists::find(_listData)); DataNodeArgument factory; factory = LinearRegressionBase::stdDevFactory(minutes, 20, false, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_stdDev)); factory = createIntradaySmaFactory(minutes, 20, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_sma)); } const std::string JoeFavaloroConsolidation::category[2][2] = { { "JoeFavaloro-AnticipateDown", "JoeFavaloro-ConfirmedDown" }, { "JoeFavaloro-AnticipateUp", "JoeFavaloro-ConfirmedUp" } }; std::string const JoeFavaloroConsolidation::getCategory(bool confirmed) const { return category[(int)_direction.isLong()][(int)confirmed] + _minutes; } double JoeFavaloroConsolidation::minTVol = 0.0; void JoeFavaloroConsolidation::onBroadcast(BroadcastMessage &message, int msgId) { DatabaseNotes const &m = dynamic_cast< DatabaseNotes & >(message); static const std::string MIN_TVOL = "Min TVol"; const double newTVol = getProperty(m.values, MIN_TVOL, minTVol); if (newTVol != minTVol) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ < 0.125) return NAN; return data.highCount; case LowCount: if (data.lowCount <= 0) return NAN; if (data.price <= 5.0) return NAN; if ((data.price - data.low) > 0.125) return NAN; return data.lowCount; default: // I got a warning before I added this. I don't know why. return NAN; } } bool JoeFavaloroLists::DataType::operator ()(CurrentData const &a, CurrentData const &b) const { // This is used for sorting. This says if a < b. // Our comparisons always use the symbol as the last key field. That // ensures that the sort order is unique. We don't want two symbols // to randomly switch places over time. const double aValue = getSortValue(a); const double bValue = getSortValue(b); if (aValue < bValue) return true; else if (aValue > bValue) return false; else return a.symbol < b.symbol; } bool JoeFavaloroLists::DataType::valid(CurrentData const &data) const { // We typically use this as a filter before sorting. Otherwise we'd have a // lot of bad values at one end or the other. return std::isfinite(getSortValue(data)); } std::string JoeFavaloroLists::DataType::smallEndName() const { switch (_type) { case Gainers: return "Losers"; default: return ""; } } std::string JoeFavaloroLists::DataType::bigEndName() const { switch (_type) { case Gainers: return "Gainers"; case HighCount: return "HighCount"; case LowCount: return "LowCount"; default: return ""; } } bool JoeFavaloroLists::DataType::smallEndBullish() const { switch (_type) { case Gainers: // Losers -- bearish. return false; default: assert(false); } } bool JoeFavaloroLists::DataType::bigEndBullish() const { switch (_type) { case Gainers: return true; case HighCount: return true; case LowCount: return false; default: assert(false); } } void JoeFavaloroLists::DataType::getAll(List &dest) { dest.clear(); // There is no ++ defined for enumerated types! //for (Type type = PriceSpike; type <= NearLow; type = (Type)(type + 1)) // dest.push_back(DataType(type)); dest.push_back(DataType(Gainers)); dest.push_back(DataType(HighCount)); dest.push_back(DataType(LowCount)); } void JoeFavaloroLists::DataType::dump(CurrentData const &data, std::string &output, int id) const { const std::string indexStr = ntoa(id); add(output, "symbol", indexStr, data.symbol); add(output, "last", indexStr, data.price); add(output, "change", indexStr, (data.price - data.previousClose) / data.previousClose * 100.0); if (data.volume) add(output, "volume", indexStr, data.volume); switch (_type) { case Gainers: break; case HighCount: add(output, "count", indexStr, data.highCount); break; case LowCount: add(output, "count", indexStr, data.lowCount); break; } } void JoeFavaloroLists::DataType::dumpAll(CurrentData const &data, std::string &output, int id) const { } void JoeFavaloroLists::DataType::add(std::string &output, std::string const &fieldName, std::string const &id, std::string fieldValue) { if (fieldValue.empty()) return; if (!output.empty()) output += '&'; output += urlEncode(fieldName); output += id; output += '='; output += urlEncode(fieldValue); } void JoeFavaloroLists::DataType::add(std::string &output, std::string const &fieldName, std::string const &id, double fieldValue) { if (!std::isfinite(fieldValue)) return; add(output, fieldName, id, formatPrice(fieldValue)); } void JoeFavaloroLists::DataType::add(std::string &output, std::string const &fieldName, std::string const &id, Integer fieldValue) { add(output, fieldName, id, ntoa(fieldValue)); } void JoeFavaloroLists::DataType::add(std::string &output, std::string const &fieldName, std::string const &id, int fieldValue) { add(output, fieldName, id, ntoa(fieldValue)); } ///////////////////////////////////////////////////////////////////// // JoeFavaloroLists::DataProvider // ///////////////////////////////////////////////////////////////////// //static const double NAN = std::numeric_limits< double >::quiet_NaN(); static const double PLUS_INF = std::numeric_limits< double >::infinity(); static const double MINUS_INF = -PLUS_INF; JoeFavaloroLists::DataProvider::DataProvider(DataNodeArgument const &args) : _symbol(args.getStringValue()), _previousClose(getPreviousClose(_symbol)), // This is not a valid value, if we ask. If we try to set high to a new // value, it will always be lower than this. So the first high of the day // will be recorded, but we will not report it as a new high. Any time the // high goes down, that's what we do. So the initial case and a correction // are one and the same. _lastHigh(PLUS_INF), _lastLow(MINUS_INF), _highCount(0), _lowCount(0) { addAutoLink(GenericTosDataNode::find(this, wTos, _tosData, _symbol)); } DataNodeLink *JoeFavaloroLists::DataProvider::find(DataProvider *&node, std::string const &fileName) { // I made this class a data node for a couple of reasons. This gives us // access to get submit time. And I can use addAutoLink in this class and // in the user of this class to help with cleanup. We don't provide a // callback because everything is done in a timer. return findHelper(NULL, 0, node, fileName); } JoeFavaloroLists::CurrentData JoeFavaloroLists::DataProvider::getCurrentData() const { CurrentData result; result.symbol = _symbol; TosData const &last = _tosData->getLast(); result.price = last.price; result.previousClose = _previousClose; result.high = last.high; result.low = last.low; result.highCount = _highCount; result.lowCount = _lowCount; result.volume = last.volume; return result; } void JoeFavaloroLists::DataProvider::onWakeup(int msgId) { switch (msgId) { case wTos: onTos(); break; } } void JoeFavaloroLists::DataProvider::onTos() { if (!_tosData->getValid()) return; TosData const &last = _tosData->getLast(); if ((last.high <= 0) || (last.low <= 0)) return; if (last.high > _lastHigh) _highCount++; _lastHigh = last.high; if (last.low < _lastLow) _lowCount++; _lastLow = last.low; } ///////////////////////////////////////////////////////////////////// // JoeFavaloroLists ///////////////////////////////////////////////////////////////////// void JoeFavaloroLists::sendMessage(std::string const &window, std::string const &message) { // A cache would be moot here. The SMB code uses a cache to reduce the // number of duplicate messages. But it always sends at least one message // every thirty seconds. This project only tries to send data once every // thirty seconds. So we'd always send the message even if we tried the // SMB algorithm. ReportAlertsThread::getInstance()->reportAlert("JoeFavaloro-" + window, message); } template < class IT > void JoeFavaloroLists::addFromIterator(std::string &output, DataType dataType, IT begin, IT end, int max, Rank &addTo) { for (int rank = 0; (begin != end) && (rank < max); rank++, begin++) { std::string const &symbol = begin->symbol; const int combinedRank = std::min(rank, getProperty(addTo, symbol, MAX_LIST_SIZE)); addTo[symbol] = combinedRank; dataType.dump(*begin, output, rank); } } void JoeFavaloroLists::buildAndSend(DataType dataType, CurrentData::List const &sorted, bool lowestFirst) { std::string window; if (lowestFirst) window = dataType.smallEndName(); else window = dataType.bigEndName(); if (window.empty()) return; bool bullish; if (lowestFirst) bullish = dataType.smallEndBullish(); else bullish = dataType.bigEndBullish(); Rank &sharedRanks = bullish?_bullishRanks:_bearishRanks; std::string message; if (lowestFirst) addFromIterator(message, dataType, sorted.begin(), sorted.end(), MAX_LIST_SIZE, sharedRanks); else addFromIterator(message, dataType, sorted.rbegin(), sorted.rend(), MAX_LIST_SIZE, sharedRanks); sendMessage(window, message); } void JoeFavaloroLists::checkForMessages() { // Compute all of the fields all at once. CurrentData::List allStocks; for (DataProvider::List::const_iterator it = _normalSymbols.begin(); it != _normalSymbols.end(); it++) allStocks.push_back((*it)->getCurrentData()); // Redo the ranking each time the timer goes off. _bullishRanks.clear(); _bearishRanks.clear(); // Then we look at each data type and filter and sort the items according to // each window type. for (DataType::List::const_iterator dataTypeIt = _dataTypes.begin(); dataTypeIt != _dataTypes.end(); dataTypeIt++) { DataType const &dataType = *dataTypeIt; // We create new lists, so we can start from the same unfiltered data // for each window. CurrentData::List validStocks; for (CurrentData::List::const_iterator it = allStocks.begin(); it != allStocks.end(); it++) if (dataType.valid(*it)) validStocks.push_back(*it); std::sort(validStocks.begin(), validStocks.end(), dataType); // Sending the top of the list is very similar to sending the bottom of // the list. We don't resort if that's the only difference between the // two lists. buildAndSend(dataType, validStocks, true); buildAndSend(dataType, validStocks, false); } } void JoeFavaloroLists::loadList(DataProvider::List &providers, std::string const &listName) { std::ifstream stream(listName.c_str(), std::ios_base::in); while(stream) { std::string symbol; std::getline(stream, symbol); symbol = strtoupper(trim(symbol, " \r")); if (!symbol.empty()) { DataProvider *node; addAutoLink(DataProvider::find(node, symbol)); providers.push_back(node); } } } JoeFavaloroLists::JoeFavaloroLists(DataNodeArgument const &args) : _timer(getOwnerChannel(), &getManager()->getTimerThread()) { assert(!args); DataType::getAll(_dataTypes); loadList(_normalSymbols, SYMBOL_LIST); registerForBroadcast(_timer, 0); const time_t now = time(NULL); const time_t first = // We want the timer to go off 15 seconds after the minute, and 45 seconds // after the minute. This will give us the first time which meets that // criteria. This might return the current time, or a time up to 29 // seconds in the future. midnight(now) + (secondOfTheDay(now) + 14) / 30 * 30 + 15; getManager()-> getTimerThread().requestPeriodicBroadcast(_timer, 30000, first); } void JoeFavaloroLists::onBroadcast(BroadcastMessage &message, int msgId) { // Timer! checkForMessages(); } double JoeFavaloroLists::getBullishScore(std::string const symbol) const { const int rank = getProperty(_bullishRanks, symbol, MAX_LIST_SIZE); return (MAX_LIST_SIZE - rank) / (double)MAX_LIST_SIZE; } double JoeFavaloroLists::getBearishScore(std::string const symbol) const { const int rank = getProperty(_bearishRanks, symbol, MAX_LIST_SIZE); return (MAX_LIST_SIZE - rank) / (double)MAX_LIST_SIZE; } ///////////////////////////////////////////////////////////////////// // Global ///////////////////////////////////////////////////////////////////// static std::vector< DataNodeLink * > cleanup; static void alertsForSymbol(std::string const &symbol) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__<