#include #include "../../shared/SimpleLogFile.h" #include "../alert_framework/AlertBase.h" #include "../data_framework/GenericTosData.h" #include "../data_framework/RecentHighsAndLows.h" #include "../misc_framework/CsvFileDataNodes.h" #include "../misc_framework/ImportData.h" #include "../../shared/MarketHours.h" #include "../data_framework/SimpleMarketData.h" #include "../data_framework/TradeDirection.h" #include "DailyHighsAndLows.h" //////////////////////////////////////////////////////////////////// // Global //////////////////////////////////////////////////////////////////// // This is meant to replace Delphi's DateToStr function. static std::string cdate(time_t date) { struct tm brokenDown; localtime_r(&date, &brokenDown); std::string result = ntoa(brokenDown.tm_mon + 1); result += '/'; result += ntoa(brokenDown.tm_mday); result += '/'; result += ntoa(brokenDown.tm_year + 1900); return result; } //////////////////////////////////////////////////////////////////// // PreviousStats //////////////////////////////////////////////////////////////////// class PreviousStats : public DataNode { private: bool _highs; // This is a list of prices and dates, sorted by price. Because of the way // we build this, it should also be sorted by date, but we we only search by // price. typedef std::map< double, time_t > Items; Items _items; time_t _nextDate; void readFromFile(std::string const &symbol); PreviousStats(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(PreviousStats *&node, std::string const &symbol, bool highs); void get(double todaysExtreme, double &price, time_t &date, bool &priceValid, bool &dateValid) const; void get(double todaysExtreme, double &quality, bool &qualityValid, std::string &description, std::string &altDescription) const ; }; void PreviousStats::readFromFile(std::string const &symbol) { static const std::string FILE = "TC_OvernightData.csv"; _items.clear(); _nextDate = 0; std::string allAsString = FileOwnerDataNode::getStringValue(FILE, _highs?"Highs":"Lows", symbol); std::vector< std::string > allAsList = explode(",", allAsString); if (allAsList.size() % 2) return; std::vector< std::string >::const_iterator it = allAsList.begin(); try { _nextDate = importTime(FileOwnerDataNode::getStringValue(FILE, "EarliestDate", symbol)); if (!_nextDate) throw 0; _nextDate--; while (it != allAsList.end()) { double price; nfroma(price, *it); it++; time_t date; date = importTime(*it); if (!date) throw 0; it++; _items[price] = date; } } catch (InvalidConversionException const &) { _items.clear(); _nextDate = 0; return; } } DataNodeLink *PreviousStats::find(PreviousStats *&node, std::string const &symbol, bool highs) { return findHelper(NULL, 0, node, argList(symbol, highs)); } PreviousStats::PreviousStats(DataNodeArgument const &args) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, Highs std::string const &symbol = argList[0].getStringValue(); _highs = argList[1].getBooleanValue(); readFromFile(symbol); } void PreviousStats::get(double todaysExtreme, double &price, time_t &date, bool &priceValid, bool &dateValid) const { price = 0; date = 0; priceValid = false; dateValid = false; if (_nextDate) { dateValid = true; Items::const_iterator it; if (_highs) { // The smallest price which is greater than or equal to this price. it = _items.lower_bound(todaysExtreme); } else { // The largest price which is less than or equal to this price. // There isn't a built in function for that. In particular, // upper_bound() is not a mirror image of lower_bound(). It would // return the smallest price which is greater than (and not equal to) // this price. it = _items.upper_bound(todaysExtreme); if (it == _items.begin()) // This is strange. This case is only needed if the list has // exactly 1 item. Otherwise it-- will move from the first item // to end(). If we have exactly 1 item in the list, however, and // it is pointing to it, --it will still point to that item! it = _items.end(); else it--; // We do not need a special case when it started at end(). At that // point --it will point to the last item, or to end() if there are // no items. } priceValid = it != _items.end(); if (priceValid) { price = it->first; date = it->second; } else date = _nextDate; } } void PreviousStats::get(double todaysExtreme, double &quality, bool &qualityValid, std::string &description, std::string &altDescription) const { static const std::string prefix = " "; qualityValid = false; description.clear(); altDescription.clear(); double price; time_t date; bool priceValid; bool dateValid; get(todaysExtreme, price, date, priceValid, dateValid); if (dateValid) { quality = (midnight(getSubmitTime()) - date); quality /= MARKET_HOURS_DAY; quality = round(quality); // Think about daylight savings time. quality--; qualityValid = true; if (priceValid) { description = prefix; if (_highs) description += "Next resistance "; else description += "Next support "; const std::string formattedPrice = Alert::formatPrice(price); description += formattedPrice; description += " from "; const std::string formattedDate = cdate(date); description += formattedDate; description += '.'; altDescription = "p="; altDescription += formattedPrice; altDescription += "&mdy="; altDescription += formattedDate; } } } //////////////////////////////////////////////////////////////////// // NewHighLowAlert //////////////////////////////////////////////////////////////////// class NewHighLowAlert : public Alert { private: const std::string _symbol; GenericTosDataNode *_tosData; protected: // Comming into a new event, this is the previous // high or low. It gets updated before the // event it finished. If an alert is reported, // this is updated to the new value, the value // which caused the alert, before the notification. double _currentExtreme; bool inputsValidP(); GenericTosDataNode *getTosData() const { return _tosData; } std::string const &getSymbol() const { return _symbol; } NewHighLowAlert(DataNodeArgument const &args); }; bool NewHighLowAlert::inputsValidP() { if (!_tosData->getValid()) return false; return (_tosData->getLast().high > 0) && (_tosData->getLast().low > 0); } NewHighLowAlert::NewHighLowAlert(DataNodeArgument const &args) : _symbol(args.getStringValue()) { addAutoLink(GenericTosDataNode::find(this, 0, _tosData, _symbol)); } //////////////////////////////////////////////////////////////////// // NewHigh //////////////////////////////////////////////////////////////////// class NewHigh : public NewHighLowAlert { private: PreviousStats *_history; bool _initialized; void onWakeup(int msgId); NewHigh(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; NewHigh::NewHigh(DataNodeArgument const &args) : NewHighLowAlert(args), _initialized(false) { addAutoLink(PreviousStats::find(_history, getSymbol(), true)); } void NewHigh::onWakeup(int msgId) { if (!inputsValidP()) _initialized = false; else { const double previousHigh = _currentExtreme; _currentExtreme = getTosData()->getLast().high; if (_initialized) { if (previousHigh < _currentExtreme) { double quality; bool qualityValid; std::string descriptionOfHistorical; std::string altMsg; _history->get(_currentExtreme, quality, qualityValid, descriptionOfHistorical, altMsg); std::string msg = "New High: "; msg += formatPrice(_currentExtreme - previousHigh, true); msg += '.'; msg += descriptionOfHistorical; if (!altMsg.empty()) altMsg += '&'; altMsg += "u="; // "u" for "up". altMsg += formatPrice(_currentExtreme - previousHigh); if (qualityValid) report(msg, altMsg, quality); else report(msg, altMsg); } } _initialized = true; } } //////////////////////////////////////////////////////////////////// // NewLow //////////////////////////////////////////////////////////////////// class NewLow : public NewHighLowAlert { private: PreviousStats *_history; bool _initialized; void onWakeup(int msgId); NewLow(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; NewLow::NewLow(DataNodeArgument const &args) : NewHighLowAlert(args), _initialized(false) { addAutoLink(PreviousStats::find(_history, getSymbol(), false)); } void NewLow::onWakeup(int msgId) { if (!inputsValidP()) _initialized = false; else { const double previousLow = _currentExtreme; _currentExtreme = getTosData()->getLast().low; if (_initialized) { if (previousLow > _currentExtreme) { double quality; bool qualityValid; std::string descriptionOfHistorical; std::string altMsg; _history->get(_currentExtreme, quality, qualityValid, descriptionOfHistorical, altMsg); std::string msg = "New Low: "; msg += formatPrice(_currentExtreme - previousLow, true); msg += '.'; msg += descriptionOfHistorical; if (!altMsg.empty()) altMsg += '&'; altMsg += "u="; // "u" for "up". So this will be negative! altMsg += formatPrice(_currentExtreme - previousLow); if (qualityValid) report(msg, altMsg, quality); else report(msg, altMsg); } } _initialized = true; } } //////////////////////////////////////////////////////////////////// // NewHighFiltered //////////////////////////////////////////////////////////////////// class NewHighFiltered : public NewHighLowAlert { private: PreviousStats *_history; double _interestingCutoff; bool _initialized; double _previousHigh; double _previousReportedHigh; time_t _previousTime; void onWakeup(int msgId); NewHighFiltered(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; NewHighFiltered::NewHighFiltered(DataNodeArgument const &args) : NewHighLowAlert(args), _initialized(false), _previousTime(0) { addAutoLink(PreviousStats::find(_history, getSymbol(), true)); const double volatility = getTickVolatility(getSymbol()); if (volatility >= MIN_VOLATILITY) // Expected volatility for 3 minutes. _interestingCutoff = volatility / sqrt(5); else _interestingCutoff = 1/8; // default; } static const time_t MIN_FILTERED_ALERT_TIME = MARKET_HOURS_MINUTE; void NewHighFiltered::onWakeup(int msgId) { if (!inputsValidP()) _initialized = false; else { const double currentHigh = getTosData()->getLast().high; if (_initialized) { if ((_previousHigh < currentHigh) && ((getSubmitTime() > _previousTime + MIN_FILTERED_ALERT_TIME) || (currentHigh - _previousHigh > _interestingCutoff))) { double quality; bool qualityValid; std::string descriptionOfHistorical; std::string altMsg; _history->get(currentHigh, quality, qualityValid, descriptionOfHistorical, altMsg); std::string msg = "New High: "; msg += formatPrice(currentHigh - _previousReportedHigh, true); msg += '.'; msg += descriptionOfHistorical; if (!altMsg.empty()) altMsg += '&'; altMsg += "u="; // "u" for "up". altMsg += formatPrice(currentHigh - _previousReportedHigh); if (qualityValid) report(msg, altMsg, quality); else report(msg, altMsg); _previousReportedHigh = currentHigh; _previousTime = getSubmitTime(); } if (currentHigh < _previousReportedHigh) _previousReportedHigh = currentHigh; } else { _initialized = true; _previousReportedHigh = currentHigh; } _previousHigh = currentHigh; } } //////////////////////////////////////////////////////////////////// // NewLowFiltered //////////////////////////////////////////////////////////////////// class NewLowFiltered : public NewHighLowAlert { private: PreviousStats *_history; double _interestingCutoff; bool _initialized; double _previousLow; double _previousReportedLow; time_t _previousTime; void onWakeup(int msgId); NewLowFiltered(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; NewLowFiltered::NewLowFiltered(DataNodeArgument const &args) : NewHighLowAlert(args), _initialized(false), _previousTime(0) { addAutoLink(PreviousStats::find(_history, getSymbol(), false)); const double volatility = getTickVolatility(getSymbol()); if (volatility >= MIN_VOLATILITY) // Expected volatility for 3 minutes. _interestingCutoff = volatility / sqrt(5); else _interestingCutoff = 1/8; // default; } void NewLowFiltered::onWakeup(int msgId) { if (!inputsValidP()) _initialized = false; else { const double currentLow = getTosData()->getLast().low; if (_initialized) { if ((_previousLow > currentLow) && ((getSubmitTime() > _previousTime + MIN_FILTERED_ALERT_TIME) || (_previousLow - currentLow > _interestingCutoff))) { double quality; bool qualityValid; std::string descriptionOfHistorical; std::string altMsg; _history->get(currentLow, quality, qualityValid, descriptionOfHistorical, altMsg); std::string msg = "New Low: "; msg += formatPrice(currentLow - _previousReportedLow, true); msg += '.'; msg += descriptionOfHistorical; if (!altMsg.empty()) altMsg += '&'; altMsg += "u="; // "u" for "up". altMsg += formatPrice(currentLow - _previousReportedLow); if (qualityValid) report(msg, altMsg, quality); else report(msg, altMsg); _previousReportedLow = currentLow; _previousTime = getSubmitTime(); } if (currentLow > _previousReportedLow) _previousReportedLow = currentLow; } else { _initialized = true; _previousReportedLow = currentLow; } _previousLow = currentLow; } } //////////////////////////////////////////////////////////////////// // NewDailyHighLowLevel //////////////////////////////////////////////////////////////////// /* The previous version of this was a filter on top of the new high alert and the new low alert. This would skip the first alert of the day for each stock. For every other alert, it would see if the quality was higher than the previous alert, and if so, it would generate one of these alerts. The notes below describe the problems with this method. I knew about these from the beginning, but I didn't realize how bad it was until recent complaints. The primed logic leaves a lot to be desired. Really we want to say that if a new high is higher than yesterday's high, then we print it. Unfortunately, if this is a Monday, everything will say that it was as good as any price we've seen in the last two days. This primed flag just allows us to skip the first new high of the day. Most of the time that's better than not skipping it. However, if the opening print was less than yesterday's high, and the new new high after that is greater than yesterday's high, then we really should signal an alert. A worse problem is that the opening print could be higher than yesterday's high. This should reported. Because this is not a new high, we don't even have the opportunity to investigate. The new version uses RecentHighsAndLows. In particular, it takes advantage of the fact that RecentHighsAndLows will tell us the yesterday's high or low, if you check early enough in the morning. Yuck! But that seems to work in other filters. Note: RecentHighsAndLows now reads the previous day's high and low from a more reliable place. Ignore the "Yuck!" above. */ class NewDailyHighLowLevel : public Alert { private: static const std::string levelPrefix[2]; RecentHighsAndLows *_highLowData; PreviousStats *_stats; std::string _descriptionPrefix; double _previousQuality; double _initialQuality; bool _previousQualityValid; bool _initialQualityValid; void onWakeup(int msgId); NewDailyHighLowLevel(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; const std::string NewDailyHighLowLevel::levelPrefix[2] = { "Crossed daily lows support.", "Crossed daily highs resistance." }; void NewDailyHighLowLevel::onWakeup(int msgId) { if (!_highLowData->getPreviouslyValid()) return; const double highLowValue = _highLowData->getCurrentValue(); if (highLowValue == 0.0) return; double newQuality; bool newQualityValid; std::string description; std::string altDescription; _stats->get(highLowValue, newQuality, newQualityValid, description, altDescription); if (_previousQualityValid && newQualityValid && (newQuality > _previousQuality) && (newQuality > _initialQuality)) report(_descriptionPrefix + description, altDescription, newQuality); _previousQuality = newQuality; _previousQualityValid = newQualityValid; if (newQualityValid && !_initialQualityValid) { // Note: PreviousQualityValid implies InitialQualityValid, but not the // other way around. InitialQuality should be yesterday's high. We save // that because we don't want an alert that is lower than yesterday's // value. However, during the day, if a value goes down we assume that // the previous value was a mistake. _initialQuality = newQuality; _initialQualityValid = true; } } NewDailyHighLowLevel::NewDailyHighLowLevel(DataNodeArgument const &args) : _previousQuality(0.0), _initialQuality(0.0), _previousQualityValid(false), _initialQualityValid(false) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, Highs std::string const &symbol = argList[0].getStringValue(); const bool highs = argList[1].getBooleanValue(); addAutoLink(RecentHighsAndLows::find(this, 0, _highLowData, highs, symbol)); addAutoLink(PreviousStats::find(_stats, symbol, highs)); _descriptionPrefix = levelPrefix[highs]; onWakeup(0); } //////////////////////////////////////////////////////////////////// // PreMarketHighLow //////////////////////////////////////////////////////////////////// // MidDay represents the time half way between the open and the close. static const time_t MID_DAY = (MARKET_HOURS_OPEN + MARKET_HOURS_CLOSE) / 2; class PreMarketHighLow : public Alert { private: bool _high; PreviousStats *_history; GenericTosDataNode *_tosData; double _currentExtreme; void onWakeup(int msgId); PreMarketHighLow(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void PreMarketHighLow::onWakeup(int msgId) { // New TOS data. const int time = secondOfTheDay(getSubmitTime()); if (time > MID_DAY) { _currentExtreme = 0.0; return; } if (!_tosData->getValid()) return; if (!(_tosData->getLast().newPrint && _tosData->getLast().formT)) return; if (time > MARKET_HOURS_OPEN + 2 * MARKET_HOURS_MINUTE) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<"formT late in the day"<< _tosData->symbol()<<_tosData->getLast().dump(); sendToLogFile(msg); // It's tempting to return here, rather than going on. For now I just // want to see what's up. } const double price = _tosData->getLast().price; // Typically we'll start in the middle of the night, so we don't // have to worry about a previous print. In particular, we don't // have to worry about weather we're looking at a print from // last night or earlier this morning. If we do start late, then // we take the simple path, possibly ignoring the first print that // we could have used. We've already lost some, what's one more. if (_currentExtreme <= 0.0) { // This is the first print that we see. _currentExtreme = price; return; } if ((_high && (_currentExtreme < price)) || ((!_high) && (_currentExtreme > price))) { // This is a new extreme. _currentExtreme = price; double quality; bool qualityValid; std::string description; std::string altDescription; _history->get(price, quality, qualityValid, description, altDescription); std::string msg = "New premarket "; if (_high) msg += "high"; else msg += "low"; msg += '.'; msg += description; if (qualityValid) report(msg, altDescription, quality); else report(msg, altDescription); } } PreMarketHighLow::PreMarketHighLow(DataNodeArgument const &args) : _currentExtreme(0.0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, High std::string const &symbol = argList[0].getStringValue(); _high = argList[1].getBooleanValue(); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); addAutoLink(PreviousStats::find(_history, symbol, _high)); } //////////////////////////////////////////////////////////////////// // PostMarketHighLow //////////////////////////////////////////////////////////////////// class PostMarketHighLow : public Alert { private: bool _high; PreviousStats *_history; GenericTosDataNode *_tosData; double _currentExtreme; void onWakeup(int msgId); PostMarketHighLow(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void PostMarketHighLow::onWakeup(int msgId) { // New TOS data. if (secondOfTheDay(getSubmitTime()) < MID_DAY) { _currentExtreme = 0.0; return; } if (!_tosData->getValid()) return; if (!(_tosData->getLast().newPrint && _tosData->getLast().formT)) return; const double price = _tosData->getLast().price; if (_currentExtreme <= 0.0) { // This is the first print that we see. _currentExtreme = price; return; } if ((_high && (_currentExtreme < price)) || ((!_high) && (_currentExtreme > price))) { // This is a new extreme. _currentExtreme = price; std::string description = ""; std::string altDescription; bool qualityValid = false; double quality = 0; const double currentHigh = _tosData->getLast().high; const double currentLow = _tosData->getLast().low; if ((currentHigh > 0) && (currentLow > 0)) { qualityValid = true; if ((_high && (_currentExtreme > currentHigh)) || ((!_high) && (_currentExtreme < currentLow))) // Qualilty is at least 1. Description to be filled in later. quality = 1; else { if (_high) description = " Approaching today's high."; else description = " Approaching today's low."; quality = 0; } } if (qualityValid && (quality > 0)) { // Quality is at least one, check for more. _history->get(price, quality, qualityValid, description, altDescription); if (qualityValid) // is this necessary? Quality valid was true a moment ago. // Could it really turn false here? quality++; } std::string msg = "New postmarket "; if (_high) msg += "high"; else msg += "low"; msg += '.'; msg += description; if (qualityValid) report(msg, altDescription, quality); else report(msg, altDescription); } } PostMarketHighLow::PostMarketHighLow(DataNodeArgument const &args) : _currentExtreme(0.0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, High std::string const &symbol = argList[0].getStringValue(); _high = argList[1].getBooleanValue(); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); addAutoLink(PreviousStats::find(_history, symbol, _high)); } //////////////////////////////////////////////////////////////////// // PreMarketHighLowFilter //////////////////////////////////////////////////////////////////// class PreMarketHighLowFilter : public GenericDataNode { private: TradeDirection _tradeDirection; GenericTosDataNode *_tosData; double _currentExtreme; void onWakeup(int msgId); void checkData(time_t dateAndTime); PreMarketHighLowFilter(DataNodeArgument const &args); friend class GenericDataNodeFactory; public: // Override form GenericDataNode virtual void getDouble(bool &valid, double &value) const; bool isValid() const { return _currentExtreme> 0.0; } }; void PreMarketHighLowFilter::getDouble(bool &valid, double &value) const { valid = isValid(); value = _currentExtreme; } PreMarketHighLowFilter::PreMarketHighLowFilter(DataNodeArgument const &args) : _currentExtreme(0.0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, High std::string const &symbol = argList[0].getStringValue(); _tradeDirection = argList[1].getBooleanValue(); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); checkData(time(NULL)); // Try to read the initial data! } void PreMarketHighLowFilter::onWakeup(int msgId) { // New TOS data. checkData(getSubmitTime()); } void PreMarketHighLowFilter::checkData(time_t dateAndTime) { const int time = secondOfTheDay(dateAndTime); if (time > MID_DAY) // Ignore the post market prints. Do NOT clear anything. // Note: This NEVER resets except when the program restarts. return; if (!_tosData->getValid()) // I.e. the data feed is down. return; if (!_tosData->getLast().formT) return; if ((!_tosData->getLast().newPrint) && isValid()) // Normally we only look at new prints. But if we don't have any data yet // we use any message that we get. return; const double price = _tosData->getLast().price; if ((!isValid()) // This is the first print that we've seen. || _tradeDirection.moreReportable(price, _currentExtreme)) { _currentExtreme = price; notifyListeners(); } } //////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////// void initializeDailyHighsAndLows() { GenericDataNodeFactory::storeStandardFactory< NewHigh >("NewHigh"); GenericDataNodeFactory::storeStandardFactory< NewLow >("NewLow"); GenericDataNodeFactory::storeStandardFactory< NewHighFiltered > ("NewHighFiltered"); GenericDataNodeFactory::storeStandardFactory< NewLowFiltered > ("NewLowFiltered"); GenericDataNodeFactory::sf< NewDailyHighLowLevel > ("CrossedDailyHighsResistance", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< NewDailyHighLowLevel > ("CrossedDailyLowsSupport", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< PreMarketHighLow > ("PreMarketHigh", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< PreMarketHighLow > ("PreMarketLow", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< PostMarketHighLow > ("PostMarketHigh", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< PostMarketHighLow > ("PostMarketLow", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< PreMarketHighLowFilter > ("PreMarketHighFilter", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< PreMarketHighLowFilter > ("PreMarketLowFilter", symbolPlaceholderObject, false); }