#include #include "../alert_framework/AlertBase.h" #include "../data_framework/GenericL1Data.h" #include "../data_framework/NewDayDetector.h" #include "../data_framework/RecentHighsAndLows.h" #include "../../shared/MarketHours.h" #include "../data_framework/SimpleMarketData.h" #include "HighLowBidAsk.h" //////////////////////////////////////////////////////////////////// // HighLowBidAskBase // // This is a common base class for the classes below. This takes // care of inheriting and/or creating all the common data nodes used // by all of these. This also takes care of some common functions, // like vetting the input data, and reporting results. //////////////////////////////////////////////////////////////////// class HighLowBidAskBase : public Alert { private: double safeBid(); double safeAsk(); std::string _msg; double _volatility; bool _up; bool _useBid; RecentHighsAndLows *_highLowData; GenericL1DataNode *_l1Data; // up, bid static std::string _stdMsg[2][2]; enum { wNewL1Data, wNewDay }; void onWakeup(int msgId); protected: virtual void onNewDay() =0; virtual void onNewL1Data() =0; static double safeMin(double a, double b); void reportChange(double priceChange); bool insideMarketValid(); double bidAskValue(); double highLowValue() { return _highLowData->getCurrentValue(); } double getVolatility() { return _volatility; } bool isMoreExtreme(double newer, double older); // True if Newer is more extreme than older. double moreExtremeValue(double a, double b); HighLowBidAskBase(DataNodeArgument const &args); }; // A lot of these 0 checks are from the old eSignal code. // ESignal would normally freeze high and low from the close and // keep them until they got new values at the open. Except for random // times when these values would be 0, which caused this code to // become a lot more complicated. // TAL will make the high and low undefined, which I convert to 0, // between the day rollover and the open. It doesn't appear to be // 0 at any other times. // It appears that TAL will sometimes make the bid 0.01 and the ask 0.00 // in the moring before it gets a good value. In that case we convert // both values to 0, below. double HighLowBidAskBase::safeMin(double a, double b) { if (a == 0.0) return b; if (b == 0.0) return a; return std::min(a, b); } void HighLowBidAskBase::reportChange(double priceChange) { // Do not report any alerts between 30 seconds before the open and one // minute after the open. static const time_t BLANK_START_TIME = MARKET_HOURS_OPEN - 30; static const time_t BLANK_END_TIME = MARKET_HOURS_OPEN + MARKET_HOURS_MINUTE; const time_t reportTime = secondOfTheDay(getSubmitTime()); if ((reportTime < BLANK_START_TIME) || (reportTime > BLANK_END_TIME)) { const std::string description = _msg + formatPrice(priceChange, true); const std::string altMsg = "p=" + formatPrice(priceChange); if (!insideMarketValid()) report(description, altMsg); else if (_useBid) report(description, altMsg, _l1Data->getCurrent().bidSize); else report(description, altMsg, _l1Data->getCurrent().askSize); } } // This should get rid of some of the silly cases. Sometimes the bid // goes to 0.01 and the ask goes to 0.00. In this case we don't report // a new low bid or ask because it's trash. Reporting this would prevent // us from reporting some legitimate stuff later. bool HighLowBidAskBase::insideMarketValid() { if (_l1Data->getValid()) { L1Data const ¤t = _l1Data->getCurrent(); return (current.bidPrice > 0) && (current.askPrice > 0); } return false; } double HighLowBidAskBase::bidAskValue() { if (_useBid) return safeBid(); return safeAsk(); } bool HighLowBidAskBase::isMoreExtreme(double newer, double older) { if (newer == 0) return false; if (_up) return newer > older; return newer < older; } double HighLowBidAskBase::moreExtremeValue(double a, double b) { if (_up) return std::max(a, b); return safeMin(a, b); } // up, bid std::string HighLowBidAskBase::_stdMsg[2][2] = { { "New Low Ask: ", "New Low Bid: "}, { "New High Ask: ", "New High Bid: "} }; HighLowBidAskBase::HighLowBidAskBase(DataNodeArgument const &args) { DataNodeArgumentVector const &argList = args.getListValue(); // Symbol, up, bid assert(argList.size() == 3); const std::string symbol = argList[0].getStringValue(); _up = argList[1].getBooleanValue(); _useBid = argList[2].getBooleanValue(); _volatility = getTickVolatility(symbol); _msg = _stdMsg[_up][_useBid]; addAutoLink(GenericL1DataNode::find(this, wNewL1Data, _l1Data, symbol)); addAutoLink(RecentHighsAndLows::find(NULL, 0, _highLowData, _up, symbol)); addAutoLink(NewDay::find(this, wNewDay, symbol)); } // Returns 0 if the bid or the ask is not valid. double HighLowBidAskBase::safeBid() { if (insideMarketValid()) return _l1Data->getCurrent().bidPrice; return 0.0; } // Returns 0 if the bid or the ask is not valid. double HighLowBidAskBase::safeAsk() { if (insideMarketValid()) return _l1Data->getCurrent().askPrice; return 0.0; } void HighLowBidAskBase::onWakeup(int msgId) { switch (msgId) { case wNewL1Data: onNewL1Data(); break; case wNewDay: onNewDay(); break; } } //////////////////////////////////////////////////////////////////// // EveryNewHighLowBidAsk //////////////////////////////////////////////////////////////////// class EveryNewHighLowBidAsk : public HighLowBidAskBase { private: bool _initialized; double _extreme; EveryNewHighLowBidAsk(DataNodeArgument const &args); friend class GenericDataNodeFactory; protected: virtual void onNewDay(); virtual void onNewL1Data(); }; void EveryNewHighLowBidAsk::onNewDay() { if (!insideMarketValid()) _initialized = false; else { _extreme = moreExtremeValue(bidAskValue(), highLowValue()); _initialized = _extreme > 0.0; } } void EveryNewHighLowBidAsk::onNewL1Data() { if (!_initialized) onNewDay(); else { const double currentBidAskPrice = bidAskValue(); if (isMoreExtreme(currentBidAskPrice, _extreme)) { if (_extreme > 0) reportChange(currentBidAskPrice - _extreme); _extreme = currentBidAskPrice; } } } EveryNewHighLowBidAsk::EveryNewHighLowBidAsk(DataNodeArgument const &args) : HighLowBidAskBase(args), _initialized(false), _extreme(0.0) { // Load the background information for comparison. onNewDay(); } //////////////////////////////////////////////////////////////////// // FilteredNewHighLowBidAsk //////////////////////////////////////////////////////////////////// class FilteredNewHighLowBidAsk : public HighLowBidAskBase { private: bool _initialized; double _interestingCutoff; double _extreme; double _previouslyDisplayedExtreme; time_t _previousTime; FilteredNewHighLowBidAsk(DataNodeArgument const &args); friend class GenericDataNodeFactory; protected: virtual void onNewDay(); virtual void onNewL1Data(); }; FilteredNewHighLowBidAsk::FilteredNewHighLowBidAsk (DataNodeArgument const &args) : HighLowBidAskBase(args), _initialized(false), _interestingCutoff(0.125), // A good default. _extreme(0.0), _previouslyDisplayedExtreme(0.0), _previousTime(0) { // Load the background information for comparison. if (getVolatility() >= MIN_VOLATILITY) // Expected volatility for 3 minutes. _interestingCutoff = getVolatility() / sqrt(5.0); onNewDay(); } void FilteredNewHighLowBidAsk::onNewDay() { if (!insideMarketValid()) _initialized = false; else { _extreme = moreExtremeValue(bidAskValue(), highLowValue()); _previouslyDisplayedExtreme = _extreme; _previousTime = 0; _initialized = _extreme > 0; } } void FilteredNewHighLowBidAsk::onNewL1Data() { static const time_t MIN_ALERT_TIME = MARKET_HOURS_MINUTE; if (!_initialized) onNewDay(); else { const double currentBidAskPrice = bidAskValue(); if (isMoreExtreme(currentBidAskPrice, _extreme)) { if ((fabs(currentBidAskPrice - _extreme) > currentBidAskPrice) || (getSubmitTime() > _previousTime + MIN_ALERT_TIME)) if (_extreme > 0) { reportChange(currentBidAskPrice - _previouslyDisplayedExtreme); _previouslyDisplayedExtreme = currentBidAskPrice; _previousTime = getSubmitTime(); } _extreme = currentBidAskPrice; } } } //////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////// void initializeHighLowBidAsk() { // Symbol, up, bid GenericDataNodeFactory::sf< EveryNewHighLowBidAsk > ("NewHighAsk", symbolPlaceholderObject, true, false); GenericDataNodeFactory::sf< EveryNewHighLowBidAsk > ("NewLowBid", symbolPlaceholderObject, false, true); GenericDataNodeFactory::sf< FilteredNewHighLowBidAsk > ("NewHighAskFiltered", symbolPlaceholderObject, true, false); GenericDataNodeFactory::sf< FilteredNewHighLowBidAsk > ("NewLowBidFiltered", symbolPlaceholderObject, false, true); GenericDataNodeFactory::sf< FilteredNewHighLowBidAsk > ("NewHighBidFiltered", symbolPlaceholderObject, true, true); GenericDataNodeFactory::sf< FilteredNewHighLowBidAsk > ("NewLowAskFiltered", symbolPlaceholderObject, false, false); }