#include #include #include "../alert_framework/AlertBase.h" #include "../data_framework/GenericL1Data.h" #include "../data_framework/GenericTosData.h" #include "../data_framework/AverageHistoricalVolume.h" #include "../data_framework/NormalVolumeBreakBars.h" #include "../../shared/MarketHours.h" #include "../data_framework/SimpleMarketData.h" #include "../data_framework/StandardCandles.h" #include "../data_framework/IntradaySma.h" #include "../data_framework/IntradayEma.h" #include "../data_framework/LinearRegression.h" #include "../data_framework/TradeDirection.h" #include "../data_framework/IntradayStochastic.h" #include "../misc_framework/Timers.h" #include "../../shared/SimpleLogFile.h" #include "../data_framework/UseHistory.h" #include "../nasdaq_vf/StockTwitsDataNode.h" #include "../data_framework/GenericHaltData.h" #include "MiscAlerts.h" /* This is just a collection of very simple alert definitions. Each one * is a class defining the alerts, with no other support classes. * * Any alert requiring a class heirarchy or other support classes was * moved to its own file. */ //////////////////////////////////////////////////////////////////// // HighVolume // // This is an older alert. We know the volume for an average 15 // minute period for each stock. Every time we see that much volume // we check the time required to accumulate that volume. If it's // less than 15 minutes, then we know that we are accumulating // volume faster than normal. If it's significantly faster than // normal, we report an alert. // // This suffers from the same problem as the short term running up // and down alerts. It doesn't accumulate the quality. If volume // is coming in very quickly, we will report several of these in // a row rather than reporting on big one. That should be fixed. //////////////////////////////////////////////////////////////////// class HighVolume : public Alert { private: NormalVolumeBreakBars *_barData; AverageHistoricalData *_volumeData; void onWakeup(int msgId); int lowerTimeFrame(time_t dateTime); int higherTimeFrame(time_t dateTime); Integer maxExpectedVolumeByPeriod(int firstPeriod, int lastPeriod); Integer maxExpectedVolumeByTime(time_t firstTime, time_t lastTime); HighVolume(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; // Returns the period that contains the time. If the time // is on the boundary of two periods, it chooses the lower one. int HighVolume::lowerTimeFrame(time_t dateTime) { const time_t time = secondOfTheDay(dateTime); int result = (time - MARKET_HOURS_OPEN) / AHV_PERIOD; if (result < AHV_PRE_MARKET) return AHV_PRE_MARKET; if (result > AHV_POST_MARKET) return AHV_POST_MARKET; return result; } // Returns the period that containst the time. If the time // is on the boundary of two periods, it chooses the higher one. int HighVolume::higherTimeFrame(time_t dateTime) { const time_t time = secondOfTheDay(dateTime); int result = (time - MARKET_HOURS_OPEN + AHV_PERIOD - 1) / AHV_PERIOD; if (result < AHV_PRE_MARKET) return AHV_PRE_MARKET; if (result > AHV_POST_MARKET) return AHV_POST_MARKET; return result; } DataNode::Integer HighVolume::maxExpectedVolumeByPeriod(int firstPeriod, int lastPeriod) { // We don't have good numbers for pre or post market! Use adjacent data. if (firstPeriod <= AHV_PRE_MARKET) firstPeriod = AHV_PRE_MARKET + 1; if (lastPeriod >= AHV_POST_MARKET) lastPeriod = AHV_POST_MARKET - 1; // Find the max of all time periods. Integer result = std::numeric_limits< Integer >::min(); for (int i = firstPeriod; i <= lastPeriod; i++) result = std::max(result, _volumeData->getVolume(i)); return result; } DataNode::Integer HighVolume::maxExpectedVolumeByTime(time_t firstTime, time_t lastTime) { if (lastTime < firstTime) // This is nonsense. We just get rid of it here so we don't have to think // about it. return 0; if (lastTime - firstTime >= MARKET_HOURS_DAY) // A whole day or more. This is a little silly. I can't imagine how this // would happen in reality. If we were looking at historical data, rather // than live data, when we might get a bar which wraps around. But in // that case we'd have to think about holiday's and weekends. return maxExpectedVolumeByPeriod(AHV_PRE_MARKET, AHV_POST_MARKET); const int firstPeriod = lowerTimeFrame(firstTime); const int lastPeriod = higherTimeFrame(lastTime); if (lastPeriod < firstPeriod) // Presumably we started one day and finished the next day. Again, I'm // not expecting this. This was a very old alert so maybe I didn't know // what was so important back then. At one time we discussed leaving // these things running for days. return std::max(maxExpectedVolumeByPeriod(firstPeriod, AHV_POST_MARKET), maxExpectedVolumeByPeriod(AHV_PRE_MARKET, lastPeriod)); // The normal, expected case: return maxExpectedVolumeByPeriod(firstPeriod, lastPeriod); } void HighVolume::onWakeup(int msgId) { VolumeBlocks const &bars = _barData->getBlocks(); if (bars.empty()) return; const VolumeBlock lastBar = *bars.rbegin(); const time_t duration = lastBar.endTime - lastBar.startTime; static const double MAX_QUALITY = 100.0; if (duration <= 0) // Volume / time = infinity! report("High instantaneous volume.", "m=1", MAX_QUALITY); else { const Integer expected = maxExpectedVolumeByTime(lastBar.startTime, lastBar.endTime); if (expected > 0) { std::string altMsg; const Integer threshold = (Integer)(1.5 * expected * duration / AHV_PERIOD); if (_barData->getGroupBy() > threshold) { std::string msg; if (_barData->getGroupBy() > threshold * 2) { msg = "Very high"; altMsg = "m=2"; } else { msg = "High"; altMsg = "m=3"; } double quality; try { quality = std::min((_barData->getGroupBy() + 0.0) / duration / expected * AHV_PERIOD, MAX_QUALITY); } catch (...) { quality = MAX_QUALITY; } msg += " relative volume during the last "; msg += durationString(lastBar.startTime, getSubmitTime()); msg += '.'; altMsg += "&d="; altMsg += ntoa(getSubmitTime() - lastBar.startTime); report(msg, altMsg, quality); } } } } HighVolume::HighVolume(DataNodeArgument const &args) { std::string const &symbol = args.getStringValue(); addAutoLink(NormalVolumeBreakBars::find(this, 0, _barData, symbol)); addAutoLink(AverageHistoricalData::find(_volumeData, symbol)); } //////////////////////////////////////////////////////////////////// // GapDownReversal //////////////////////////////////////////////////////////////////// class GapDownReversal : public Alert { private: GenericTosDataNode *_tosData; double _previousClose; bool _primed; void onWakeup(int msgId); GapDownReversal(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void GapDownReversal::onWakeup(int msgId) { if (_tosData->getValid()) { TosData const &last = _tosData->getLast(); if ((last.high <= 0) || (last.low <= 0) || (last.open <= 0)) _primed = false; else if (_previousClose <= last.open) _primed = false; // Gapped up or no gap at all. else if ((last.high > _previousClose) && _primed) { // Currently crossed, previously not crossed _primed = false; std::string msg = "Gap reversal, Gap="; std::string altMsg = "g="; msg += formatPrice(last.open - _previousClose, true); // Do not send the + in the altMsg, consistent with what we normally do. altMsg += formatPrice(last.open - _previousClose); msg += ", "; if (last.open > last.low) { // Gap continuation. msg += formatPrice(last.open - last.low, true); altMsg += "&c="; altMsg += ntoa(last.open - last.low); } else msg += "No"; msg += " gap continuation."; report(msg, altMsg, _previousClose - last.low); } else if (last.high < _previousClose) // We have just verified that we have been below the // close all day. _primed = true; } else { // If the crossing happens while the datafeed is dead, the event // is lost. Without this, we might report large numbers of // crossings if we've been down for a while, and the pullback // would be wrong (in addition to the time). _primed = false; } } GapDownReversal::GapDownReversal(DataNodeArgument const &args) : _primed(false) { std::string const &symbol = args.getStringValue(); _previousClose = getPreviousClose(symbol); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // GapUpReversal //////////////////////////////////////////////////////////////////// class GapUpReversal : public Alert { private: GenericTosDataNode *_tosData; double _previousClose; bool _primed; void onWakeup(int msgId); GapUpReversal(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void GapUpReversal::onWakeup(int msgId) { if (_tosData->getValid()) { TosData const &last = _tosData->getLast(); if ((last.high <= 0) || (last.low <= 0) || (last.open <= 0)) _primed = false; else if (_previousClose >= last.open) _primed = false; // Gapped down or no gap at all. else if ((last.low < _previousClose) && _primed) { // Currently crossed, previously not crossed _primed = false; std::string msg = "Gap reversal, Gap="; std::string altMsg = "g="; msg += formatPrice(last.open - _previousClose, true); altMsg += formatPrice(last.open - _previousClose); msg += ", "; if (last.open < last.high) { // gap continuation msg += formatPrice(last.high - last.open, true); altMsg += "&c="; altMsg += ntoa(last.high - last.open); } else msg += "No"; msg += " gap continuation."; report(msg, altMsg, last.high - _previousClose); } else if (last.low > _previousClose) // We have just verified that we have been above the // close all day. _primed = true; } else // If the crossing happens while the datafeed is dead, the event // is lost. Without this, we might report large numbers of // crossings at once if we've been down for a while, and the // pullbacks would be wrong (in addition to the time). _primed = false; } GapUpReversal::GapUpReversal(DataNodeArgument const &args) : _primed(false) { std::string const &symbol = args.getStringValue(); _previousClose = getPreviousClose(symbol); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // BlockPrint //////////////////////////////////////////////////////////////////// class BlockPrint : public Alert { private: GenericTosDataNode *_tosData; GenericL1DataNode *_l1Data; Integer _minQuality; bool _skip; void onWakeup(int msgId); BlockPrint(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; BlockPrint::BlockPrint(DataNodeArgument const &args) { std::string const &symbol = args.getStringValue(); addAutoLink(GenericL1DataNode::find(_l1Data, symbol)); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); const Integer averageDailyVolume = getAverageDailyVolume(symbol); if ((averageDailyVolume <= 0) || (averageDailyVolume > 1000000)) // The default _minQuality = 20000; else // For small stocks _minQuality = 5000; // some indexes report huge volumes and would thus trigger every // print if (symbol.find("$",0) == 0) _skip = true; } void BlockPrint::onWakeup(int msgId) { if (_skip) return; if (!_tosData->getValid()) return; TosData const &last =_tosData->getLast(); // block trades by their nature often don't set last // so we are trying without the following check due // to complaints about missing data. //if (!last.newPrint) // return; if (last.size < _minQuality) return; std::string msg = "Block trade."; std::string altMsg; if (_l1Data->getValid()) { const double bid = _l1Data->getCurrent().bidPrice; const double ask = _l1Data->getCurrent().askPrice; if ((bid > 0) && (ask > 0) && (last.price > 0)) { if ((last.price < bid) && (last.price < ask)) { msg += " Trading below."; altMsg += "t=below"; } else if ((last.price > bid) && (last.price > ask)) { msg += " Trading above."; altMsg += "t=above"; } else if (ask > bid) { if (last.price == bid) { msg += " At the bid."; altMsg += "t=bid"; } else if (last.price < ask) { msg += " Trading between."; altMsg += "t=between"; } else if (last.price == ask) { msg += " At the ask."; altMsg += "t=ask"; } } } } std::string const &exchange = longExchangeName(last.exchange); if (!exchange.empty()) { // The Delphi code also checks for things surrounded by [ and ]. // Those were internal codes from TAL which we didn't know how to // translate. msg += " ("; msg += exchange; msg += ')'; if (!altMsg.empty()) altMsg += '&'; altMsg += "e="; altMsg += urlEncode(exchange); } report(msg, altMsg, last.size); } //////////////////////////////////////////////////////////////////// // IntraDayHighLow //////////////////////////////////////////////////////////////////// class IntraDayHighLow : public Alert { private: bool _highs; time_t _period; GenericTosDataNode *_tosData; std::string _msg; time_t _lastPrintTime; double _bestPriceThisPeriod; double _bestPriceLastPeriod; bool _reportedThisPeriod; bool _thisPeriodValid; bool _lastPeriodValid; void onWakeup(int msgId); bool moreReportable(double a, double b); IntraDayHighLow(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void IntraDayHighLow::onWakeup(int msgId) { if (!_tosData->getValid()) _thisPeriodValid = false; else { TosData const &last = _tosData->getLast(); if (last.newPrint) { if ((_lastPrintTime != 0) && (secondOfTheDay(_lastPrintTime) / _period != secondOfTheDay(getSubmitTime()) / _period)) { // Start a new candle. _bestPriceLastPeriod = _bestPriceThisPeriod; _lastPeriodValid = _thisPeriodValid; _bestPriceThisPeriod = last.price; _thisPeriodValid = true; _reportedThisPeriod = false; } else if (_thisPeriodValid && moreReportable(last.price, _bestPriceThisPeriod)) // Update the current candle. _bestPriceThisPeriod = last.price; if (_lastPeriodValid && (!_reportedThisPeriod) && moreReportable(last.price, _bestPriceLastPeriod)) { report(_msg); _reportedThisPeriod = true; } _lastPrintTime = getSubmitTime(); } } } bool IntraDayHighLow::moreReportable(double a, double b) { if (_highs) return a > b; else return a < b; } IntraDayHighLow::IntraDayHighLow(DataNodeArgument const &args) : _lastPrintTime(0), _bestPriceThisPeriod(0.0), _bestPriceLastPeriod(0.0), _reportedThisPeriod(false), _thisPeriodValid(false), _lastPeriodValid(false) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 3); // Symbol, Highs, Minutes std::string const &symbol = argList[0].getStringValue(); _highs = argList[1].getBooleanValue(); const int minutes = argList[2].getIntValue(); _period = minutes * MARKET_HOURS_MINUTE; _msg = "New "; _msg += ntoa(minutes); _msg += " minute "; if (_highs) _msg += "high"; else _msg += "low"; _msg += '.'; addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // LargeBidAsk // // This is only recommended for stocks trading under 1,000,000 shares // per day. At one time this data node would only work for those // stocks. Now we expect a higher level of code to take care of that. //////////////////////////////////////////////////////////////////// class LargeBidOrAsk : public Alert { private: bool _bidSide; GenericL1DataNode *_l1Data; enum CurrentLevel { clTriggered, clProbablyTriggered, clUnknown, clProbablyNotTriggered, clNotTriggered }; CurrentLevel _currentLevel; time_t _lastTriggeredTime; double _lowestTriggeredPrice; double _highestTriggeredPrice; Integer _highestTriggeredSize; int _alertsThisTrigger; Integer _expectedValue; static const Integer INVALID; void getValues(double &price, Integer &size); std::string baseDescription(); void onWakeup(int msgId); static Integer getExpectedBidAskSize(std::string symbol); LargeBidOrAsk(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void LargeBidOrAsk::getValues(double &price, Integer &size) { L1Data const ¤t = _l1Data->getCurrent(); if (_bidSide) { price = current.bidPrice; size = current.bidSize; } else { price = current.askPrice; size = current.askSize; } } std::string LargeBidOrAsk::baseDescription() { if (_bidSide) return "Large Bid Size"; else return "Large Ask Size"; } void LargeBidOrAsk::onWakeup(int msgId) { static const time_t CUT_OFF_TIME = 3 * MARKET_HOURS_MINUTE; enum NextLevel { nlTriggered, nlUnknown, nlNotTriggered }; NextLevel nextLevel = nlUnknown; if (_l1Data->getValid()) { double price; Integer size; getValues(price, size); if ((price > 0) && (size > 0)) { if (size < _expectedValue) nextLevel = nlNotTriggered; else { nextLevel = nlTriggered; if ((_currentLevel == clTriggered) || (_currentLevel == clProbablyTriggered) || (getSubmitTime() - _lastTriggeredTime < CUT_OFF_TIME)) { // We were already triggered. Let's see if the trigger // condition has changed enough to report another alert. if ((_alertsThisTrigger < 5) && ((price < _lowestTriggeredPrice) || (price > _highestTriggeredPrice) || (size > _highestTriggeredSize))) { _alertsThisTrigger++; std::string alertMessage = baseDescription(); std::string altMsg; if (size > _highestTriggeredSize) { _highestTriggeredSize = size; alertMessage += " (Size increasing)"; altMsg += "si=1"; } if (price < _lowestTriggeredPrice) { _lowestTriggeredPrice = price; alertMessage += " (Price dropping)"; if (!altMsg.empty()) altMsg += '&'; altMsg += "pd=1"; } else if (price > _highestTriggeredPrice) { _highestTriggeredPrice = price; alertMessage += " (Price rising)"; if (!altMsg.empty()) altMsg += '&'; altMsg += "pr=1"; } report(alertMessage, altMsg, size); } } else if (_currentLevel != clUnknown) { // This is a brand new trigger. report(baseDescription(), size); _lowestTriggeredPrice = price; _highestTriggeredPrice = price; _highestTriggeredSize = size; _alertsThisTrigger = 1; } } } } switch (nextLevel) { case nlTriggered : _currentLevel = clTriggered; break; case nlUnknown : if (_currentLevel == clTriggered) _currentLevel = clProbablyTriggered; else if (_currentLevel == clNotTriggered) _currentLevel = clProbablyNotTriggered; break; case nlNotTriggered : if ((_currentLevel == clTriggered) || (_currentLevel == clProbablyTriggered)) _lastTriggeredTime = getSubmitTime(); _currentLevel = clNotTriggered; } } DataNode::Integer LargeBidOrAsk::getExpectedBidAskSize(std::string symbol) { if (symbolIsIndex(symbol)) // Futures should also return INVALID. return INVALID; const Integer averageDailyVolume = getAverageDailyVolume(symbol); if (averageDailyVolume <= 0) // Volume not specified, so we have no way to guess return INVALID; if (averageDailyVolume <= 1000000) return 6000; else return 10000; // Remember that AlertMainControl::normalStartImpl() in AlertMainControl.C // will filter out stocks with an average daily volume above 3,000,000 // shares per day. For those stocks we'd never even create one of these // objects, much less run this method. There were some notes in earlier // versions, espeically AlertsData.pas, about other ways to handle stocks // with a large average daily volume. } LargeBidOrAsk::LargeBidOrAsk(DataNodeArgument const &args) : _currentLevel(clUnknown), _lastTriggeredTime(0), _lowestTriggeredPrice(0.0), _highestTriggeredPrice(0.0), _highestTriggeredSize(0), _alertsThisTrigger(0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, BidSide std::string const &symbol = argList[0].getStringValue(); _bidSide = argList[1].getBooleanValue(); _expectedValue = getExpectedBidAskSize(symbol); if (_expectedValue < INVALID) addAutoLink(GenericL1DataNode::find(this, 0, _l1Data, symbol)); } const DataNode::Integer LargeBidOrAsk::INVALID = std::numeric_limits< Integer >::max(); //////////////////////////////////////////////////////////////////// // PercentChangeForTheDay //////////////////////////////////////////////////////////////////// class PercentChangeForTheDay : Alert { private: bool _up; double _previousClose; GenericTosDataNode *_tosData; time_t _lastEventDate; double _lastExtremeValue; int _lastPercentReported; bool _previousHighLowValid; void onWakeup(int msgId); PercentChangeForTheDay(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void PercentChangeForTheDay::onWakeup(int msgId) { static const int MIN_PERCENT_CHANGE = 3; bool nowValid = false; const time_t currentDate = midnight(getSubmitTime()); if (currentDate != _lastEventDate) { _lastEventDate = currentDate; if (_up) _lastExtremeValue = 0; else _lastExtremeValue = std::numeric_limits< double >::max(); _lastPercentReported = 0; } if (_tosData->getValid()) { TosData const &last = _tosData->getLast(); const double price = _up?last.high:last.low; if (price > 0) { nowValid = true; const double priceChange = _up?(price - _lastExtremeValue):(_lastExtremeValue - price); if (priceChange != 0.0) { _lastExtremeValue = price; const double changeForDay = _up?(price-_previousClose):(_previousClose-price); const int newPercent = std::max(0, (int)(changeForDay / _previousClose * 100.0)); if (priceChange > 0) { if ((newPercent > _lastPercentReported) && (newPercent >= MIN_PERCENT_CHANGE)) { if (_previousHighLowValid) { std::string msg; if (_up) msg = "Up "; else msg = "Down "; msg += ntoa(newPercent); msg += "% for the day."; report(msg, newPercent); } _lastPercentReported = newPercent; } } else // priceChange < 0 // A correction. The high dropped, or the low rose. _lastPercentReported = newPercent; } } } _previousHighLowValid = nowValid; } PercentChangeForTheDay::PercentChangeForTheDay(DataNodeArgument const &args) : _lastEventDate(0), _lastExtremeValue(0.0), _lastPercentReported(0), _previousHighLowValid(false) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, up std::string const &symbol = argList[0].getStringValue(); _up = argList[1].getBooleanValue(); _previousClose = getPreviousClose(symbol); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // StrongVolume //////////////////////////////////////////////////////////////////// class StrongVolume : public Alert { private: GenericTosDataNode *_tosData; Integer _historicalVolume; bool _lastVolumeGood; Integer _lastMultipleReported; void onWakeup(int msgId); StrongVolume(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void StrongVolume::onWakeup(int msgId) { static const Integer MINIMUM_MULTIPLE = 1; Integer currentVolume = 0; if (_tosData->getValid()) currentVolume = _tosData->getLast().volume; if (currentVolume <= 0) _lastVolumeGood = false; else { const Integer currentMultiple = currentVolume / _historicalVolume; if ((currentMultiple > _lastMultipleReported) && (currentMultiple >= MINIMUM_MULTIPLE) && _lastVolumeGood) report("Currently at " + ntoa(currentMultiple) + " x average daily volume.", currentMultiple); _lastMultipleReported = currentMultiple; _lastVolumeGood = true; } } StrongVolume::StrongVolume(DataNodeArgument const &args) : _lastVolumeGood(false), _lastMultipleReported(0) { std::string const &symbol = args.getStringValue(); _historicalVolume = getAverageDailyVolume(symbol); if (_historicalVolume > 0) addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // OpeningRangeBreak // // The idea is that a 5 minute breakout watches the prices for the // first 5 minutes of trading, records the highest price during that // time, and then reports the first time the price goes over that // extreme. Other times and the other direction are all possible, // and are both controlled by parameters. // // We used to block out any alerts right after the time period // ends. It seems like there would be a lot of trash in the frist // few seconds after the period otherwise. Some stocks just move // up, and that's not really a breakout. // // This was a mess under TAL. It was written 3 times from scratch // and still had trouble. One version only watched the highs and // the lows. This was nice because it could deal with bad data. // But it was bad because if we lost a message from TAL (which we // often did) it would not see some of the news highs and lows. // Another version only dealt with TOS data. That had trouble // because one bad print in the opening period could cause a mess. // We can usually deal with a bad print using the NBBO filter. // But that only works with the print that sets off the alert. It // doesn't help with the prints that set up the pattern. // // Originally we started with the first opening bell. Later we // had to change the definition to start with the first print. // Then we changed it again so NYSE stocks would only start with // the first print from the NYSE exchange. For NASAQ we only // required a non-formT print. However, that was not enough. We // got some strange prints early in the morning. So now we also // check the clock for all prints. That seems redundant, but it // should help. // // Now we watch the TOS feed to get us started. Then we use a // timer event to say when the initial period ends. Then we look // at the high or low from the L1 data to set the extreme. Then // we start watching the TOS stream again for a breakout. We // use the TOS at the end for simplicity. Ideally we'd watch the // L1 data, and we'd watch for values which move backward. Then // we could report some stocks a second time if the first time was // a mistake. // // If we start after the open, we disable these alerts. We do that // for simplicity. We could attempt to make some decent guesses // and do something. //////////////////////////////////////////////////////////////////// class OpeningRangeBreak : public Alert { private: bool _up; bool _NYSE; int _initialMinutes; GenericTosDataNode *_tosData; enum State { // Waiting for first print of comparison candle. orbsNoData, // Waiting to expand the comparison candle. orbsWaitingForEndOfCandle, // Waiting for a print outside of the candle. orbsWaitingForAlert, // Reported at least once today. Waiting for a data correction or a new // day. orbsReported }; State _state; double _extremeValue; bool moreReportable(double a, double b); void onWakeup(int msgId); void onBroadcast(BroadcastMessage &message, int msgId); OpeningRangeBreak(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; bool OpeningRangeBreak::moreReportable(double a, double b) { if (_up) return a > b; else return a < b; } void OpeningRangeBreak::onWakeup(int msgId) { // New TOS data. if (!_tosData->getValid()) return; TosData const &last = _tosData->getLast(); if (last.formT || !last.newPrint) return; const time_t eventTime = getSubmitTime(); switch (_state) { case orbsNoData: if (eventTime < MARKET_HOURS_OPEN) return; // commented out per Brad's request //if (_NYSE && (last.exchange != nyseOnDataFeed())) //return; // After the end of the period we wait this long before we report an // alert. If we see a print which breaks out before then we don't report // anything. We assume that the stock is just going streight up or // streight down rather than creating a support or resistance line and // later moving through it. getManager()->getTimerThread().requestBroadcastAfter (getOwnerChannel(), _initialMinutes * MARKET_HOURS_MINUTE * 1000); registerForBroadcast(getOwnerChannel(), 0); _state = orbsWaitingForEndOfCandle; break; case orbsWaitingForAlert: if (moreReportable(last.price, _extremeValue)) { _state = orbsReported; std::string msg = "Opening range break"; if (_up) msg += "out"; else msg += "down"; msg += ". ("; msg += ntoa(_initialMinutes); msg += " minutes)"; report(msg); } break; case orbsReported: case orbsWaitingForEndOfCandle: // Nothing to do. break; } } void OpeningRangeBreak::onBroadcast(BroadcastMessage &message, int msgId) { // The end of the candle. if (_tosData->getValid()) { TosData const &last = _tosData->getLast(); _extremeValue = _up?last.high:last.low; if (_extremeValue > 0) { _state = orbsWaitingForAlert; return; } } // No valid data. _state = orbsReported; } OpeningRangeBreak::OpeningRangeBreak(DataNodeArgument const &args) : _state(orbsNoData), _extremeValue(0.0) { // For simplicity we don't reset at midnight. We assume that we will not // be running overnight. However, it is possible that we go down and have // to come up after the open. In that case, for simplicity, we just refuse // to report any alerts. if (openHasPassed()) return; DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 3); // Symbol, Up, Minutes std::string const &symbol = argList[0].getStringValue(); _up = argList[1].getBooleanValue(); _initialMinutes = argList[2].getIntValue(); _NYSE = getListedExchange(symbol) == s_NYSE; addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // BrightBands // // This code is based on the PercentChangeForTheDay alerts. But // instead of moving up 1%, the stock must move up 1 standard // deviation. We look at a years worth of daily data to form the // standard deviation, but the more recent stuff is more heavily // weighted. // // These were originally called the Bright Band breakout and Bright // Band breakdown alerts. The names were changed to make some of // our partners happy. They are now called Standard deviation // breakout / breakdown alerts. However, the icons remain the same, // and the on-line help still references Bright Trading. //////////////////////////////////////////////////////////////////// class BrightBands : Alert { private: bool _up; GenericTosDataNode *_tosData; double _previousClose; time_t _lastEventDate; double _lastExtremeValue; double _oneStandardDeviation; int _lastCountReported; bool _previousHighLowValid; void onWakeup(int msgId); BrightBands(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void BrightBands::onWakeup(int msgId) { static const int MIN_CHANGE = 1; bool nowValid = false; const time_t currentDate = midnight(getSubmitTime()); if (currentDate != _lastEventDate) { _lastEventDate = currentDate; if (_up) _lastExtremeValue = 0; else _lastExtremeValue = std::numeric_limits< double >::max(); _lastCountReported = 0; } if (_tosData->getValid()) { TosData const &last = _tosData->getLast(); const double price = _up?last.high:last.low; if (price > 0) { nowValid = true; const double priceChange = _up?(price - _lastExtremeValue):(_lastExtremeValue - price); if (priceChange != 0.0) { _lastExtremeValue = price; const double changeForDay = _up?(price-_previousClose):(_previousClose-price); const int newCount = std::max(0, (int)(changeForDay / _oneStandardDeviation / _previousClose)); if (priceChange > 0) { if ((newCount > _lastCountReported) && (newCount >= MIN_CHANGE)) { if (_previousHighLowValid) { std::string msg; if (_up) msg = "Up "; else msg = "Down "; msg += ntoa(newCount); msg += " standard deviation"; if (newCount > 1) msg += 's'; msg += " for the day."; report(msg, newCount); } _lastCountReported = newCount; } } else // priceChange < 0 // A correction. The high dropped, or the low rose. _lastCountReported = newCount; } } } _previousHighLowValid = nowValid; } BrightBands::BrightBands(DataNodeArgument const &args) : _lastEventDate(0), _lastExtremeValue(0.0), _lastCountReported(0), _previousHighLowValid(false) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, up std::string const &symbol = argList[0].getStringValue(); _up = argList[1].getBooleanValue(); _previousClose = getPreviousClose(symbol); _oneStandardDeviation = strtodDefault(FileOwnerDataNode::getStringValue ("OvernightData.csv", "Bright Volatility", symbol), 0.0); if (_oneStandardDeviation > 0) addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // VwapDivergenceAlert //////////////////////////////////////////////////////////////////// class VwapDivergenceAlert : public Alert { private: bool _up; GenericTosDataNode *_tosData; GenericDataNode *_vwapData; double _lastReported; void onWakeup(int msgId); VwapDivergenceAlert(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void VwapDivergenceAlert::onWakeup(int msgId) { if (!_tosData->getValid()) return; bool valid; double currentVwap; _vwapData->getDouble(valid, currentVwap); if (!valid) return; if (currentVwap <= 0) return; const double currentPrice = _tosData->getLast().price; if (currentPrice <= 0) return; double divergence = (currentPrice - currentVwap) / currentVwap * 100.0; if (!_up) divergence = -divergence; divergence = floor(divergence); if (divergence > _lastReported) { // If the integer percent divergence is higher than before, // we print the value, and we raise the minimum. The next // report will have to be at least 1.0 higher. const std::string direction = _up?"above":"below"; std::string msg; if (divergence < 0.5) msg = "Crossed " + direction + " VWAP"; else msg = "Trading " + ntoa((int)divergence) + "% " + direction + " VWAP"; report(msg, divergence); _lastReported = divergence; } else if (divergence < _lastReported - 1.25) // If the divergence goes back enough then we lower the minimum. Maybe // it was a bad print, so we don't want that to ecplipse a good print, or // maybe the market is really moving that much, so the move is still // interesting. Either way, we can't go below -0.5, so we always report // 0% as a minimum. _lastReported = std::max(-0.5, divergence); } VwapDivergenceAlert::VwapDivergenceAlert(DataNodeArgument const &args) : _lastReported(0.0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, up std::string const &symbol = argList[0].getStringValue(); _up = argList[1].getBooleanValue(); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); addAutoLink(GenericDataNodeFactory::replaceAndFind (GenericDataNodeFactory::findFactory("VWAP"), NULL, 0, _vwapData, symbolPlaceholder, symbol)); } //////////////////////////////////////////////////////////////////// // TradingAboveBelow //////////////////////////////////////////////////////////////////// class TradingAboveBelow : public Alert { private: bool _above; bool _specialistOnly; GenericTosDataNode *_tosData; GenericDataNode *_bidData; GenericDataNode *_askData; void findGeneric(GenericDataNode *&node, std::string const &type, std::string const &symbol); int _savedCount; int _requiredCount; time_t _firstSavedEvent; time_t _lastPrintedEvent; void onWakeup(int msgId); void standardReport(std::string const &exchangeCode, double quality, std::string suffix = "", std::string altSuffix = ""); TradingAboveBelow(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void TradingAboveBelow::standardReport(std::string const &exchangeCode, double quality, std::string suffix, std::string altSuffix) { std::string msg = "Trading "; std::string altMsg; if (_above) msg += "above"; else msg += "below"; if (_specialistOnly) msg += " specialist"; std::string const &exchange = longExchangeName(exchangeCode); if (!exchange.empty()) { // The Delphi code also checks for things surrounded by [ and ]. // Those were internal codes from TAL which we didn't know how to // translate. msg += " ("; msg += exchange; msg += ')'; altMsg = "ex=" + urlEncode(exchange); } msg += suffix; addToUrlEncoded(altMsg, altSuffix); report(msg, altMsg, quality); } void TradingAboveBelow::onWakeup(int msgId) { if (!_tosData->getValid()) return; if (!_tosData->getLast().newPrint) return; if (_specialistOnly && _tosData->getLast().formT) return; bool valid; double bid, ask; _bidData->getDouble(valid, bid); if (!valid) return; _askData->getDouble(valid, ask); if (!valid) return; const double price = _tosData->getLast().price; if (!(price && bid && ask)) // We were getting a lot of trading above specialist alerts for all those // stocks where we couldn't find the specialist! Bid and ask were 0. // Also, valid might be faked for some of these data nodes. return; if (_above) { if ((price <= bid) || (price <= ask)) return; } else { if ((price >= bid) || (price >= ask)) return; } // Something was trading above or below. Apply filters to say if we're going // to display it immediately or save it for later. static const time_t MAX_WAIT_TIME = 3 * MARKET_HOURS_MINUTE; const time_t currentTime = getSubmitTime(); if (currentTime - _lastPrintedEvent >= MAX_WAIT_TIME) { _requiredCount = 0; _firstSavedEvent = 0; _lastPrintedEvent = currentTime; _savedCount = 0; standardReport(_tosData->getLast().exchange, 1); } else { if (_savedCount >= _requiredCount) { if (_savedCount >= 1) { const std::string times = ntoa(_savedCount + 1); const std::string suffix = ' ' + times + " times in " + durationString(_firstSavedEvent, currentTime); const std::string altSuffix = "d=" + ntoa(currentTime - _firstSavedEvent) // c == count + "&c=" + times; standardReport(_tosData->getLast().exchange, (_savedCount + 1) * 2, suffix, altSuffix ); } else standardReport(_tosData->getLast().exchange, 2); _lastPrintedEvent = currentTime; _requiredCount = _requiredCount * 2 + 1; _firstSavedEvent = 0; _savedCount = 0; } else { _savedCount++; if (_firstSavedEvent == 0) _firstSavedEvent = currentTime; } } } void TradingAboveBelow::findGeneric(GenericDataNode *&node, std::string const &type, std::string const &symbol) { DataNodeArgument factory = GenericDataNodeFactory::findFactory(type); factory = factory.replace(symbolPlaceholder, symbol); addAutoLink(factory.getValue< GenericDataNodeFactory >() .find(NULL, 0, node)); } TradingAboveBelow::TradingAboveBelow(DataNodeArgument const &args) : _savedCount(0), _requiredCount(0), _firstSavedEvent(0), _lastPrintedEvent(0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 3); // Symbol, above, specialist only std::string const &symbol = argList[0].getStringValue(); _above = argList[1].getBooleanValue(); _specialistOnly = argList[2].getBooleanValue(); if (symbolIsIndex(symbol)) return; if (_specialistOnly) { const std::string exchange = getListedExchange(symbol); if ((exchange != s_NYSE) && (exchange != s_AMEX)) // This alert is only define for NYSE and AMEX stocks. // What about ARCA? That didn't exist when we wrote these // requirements. I'm going to say to ignore it mostly because this // is such an old idea which shouldn't grow over time. return; findGeneric(_bidData, "NyseBid", symbol); findGeneric(_askData, "NyseAsk", symbol); } else { // Normal L1 data. findGeneric(_bidData, "BidPrice", symbol); findGeneric(_askData, "AskPrice", symbol); } addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // NR7 //////////////////////////////////////////////////////////////////// class NR7 : public Alert { private: StandardCandles *_candleData; void onWakeup(int msgId); bool checkForNR7Once(int skip, StandardCandles::CandleArray const &candles); static double range(StandardCandles::SingleCandle const &candle) { return candle.high - candle.low; } NR7(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; NR7::NR7(DataNodeArgument const &args) { // Symbol, minutes per bar addAutoLink(StandardCandles::find(this, 0, _candleData, args)); } bool NR7::checkForNR7Once(int skip, StandardCandles::CandleArray const &candles) { // NR 7 Means to compare the 7th bar to the previous 6. static const int LOOK_BACK = 6; if ((unsigned int)(skip + LOOK_BACK) >= candles.size()) return false; const int lastIndex = candles.size() - 1 - skip; const double compare = range(candles[lastIndex]); for (int i = lastIndex - LOOK_BACK; i < lastIndex; i++) if (compare >= range(candles[i])) return false; return true; } void NR7::onWakeup(int msgId) { StandardCandles::CandleArray const &candles = _candleData->getHistory(); int quality = 0; while (checkForNR7Once(quality, candles)) quality++; if (quality == 1) report("NR7", 1); else if (quality > 1) report("NR7-" + ntoa(quality), quality); } //////////////////////////////////////////////////////////////////// // UnusualNumberOfPrints //////////////////////////////////////////////////////////////////// class UnusualNumberOfPrints : Alert { private: AverageHistoricalData *_volumeData; GenericTosDataNode *_tosData; double _sharesPerPrint; time_t _resetTime; // Give up on the previous alert, start counting again. int _previousPeriod; // Review PrintsRequired when the period changes. int _expect; // Number of prints required to be interesting. int _printsRequired; // Number of prints before our next check. int _levelToBeat; // Quality required for an alert. int _count; // Number of groups of prints in this time slice. bool _recentlyAlerted; // The last time we reset was from prints, not time. void onWakeup(int msgId); UnusualNumberOfPrints(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void UnusualNumberOfPrints::onWakeup(int msgId) { static const int MAX_TIME = 3 * MARKET_HOURS_MINUTE; if (!_tosData->getValid()) return; if (!_tosData->getLast().newPrint) return; const time_t submitTime = getSubmitTime(); if (submitTime >= _resetTime) { _resetTime = submitTime + MAX_TIME; int period = AverageHistoricalData::higherTimeFrame(submitTime); if (period != _previousPeriod) { _previousPeriod = period; if (period == AHV_PRE_MARKET) period++; else if (period == AHV_POST_MARKET) period--; _expect = (int)(_volumeData->getVolume(period) /_sharesPerPrint + 0.5); if (_expect < 5) _expect = 0; } // Reset the search. _printsRequired = _expect; if (_recentlyAlerted) _levelToBeat = _count; else _levelToBeat--; _levelToBeat = std::max(0, _levelToBeat); _recentlyAlerted = false; _count = 0; } if (_expect > 0) { _printsRequired--; if (_printsRequired < 1) { _count++; if (_count > _levelToBeat) report("Unusual number of prints", _count * 5.0); _printsRequired = _expect; _recentlyAlerted = true; } } } UnusualNumberOfPrints::UnusualNumberOfPrints(DataNodeArgument const &args) : _resetTime(0), _previousPeriod(AHV_NONE), _expect(0), _printsRequired(0), _levelToBeat(0), _count(0), _recentlyAlerted(false) { std::string const &symbol = args.getStringValue(); _sharesPerPrint = strtodDefault(FileOwnerDataNode::getStringValue ("OvernightData.csv", "Shares per Print", symbol), 0.0); if (_sharesPerPrint > 0) { addAutoLink(AverageHistoricalData::find(_volumeData, symbol)); if (_volumeData->isValid()) addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } } //////////////////////////////////////////////////////////////////// // LargeSpread // // This alert is only defined for NYSE stocks. A "large spread" is defined // as at least 50 cents. We only report on the transition from the ready // state to the triggered state. Furthermore, we do not report an alert // unless we have been in the ready state for at least 30 seconds. Finally, // if the data feed is down, including when we first start, that's a third // state. This state does not cause an alert, but is also supresses future // alerts, just like the trigged state does. //////////////////////////////////////////////////////////////////// class LargeSpread : public Alert { private: GenericDataNode *_bidData; GenericDataNode *_askData; void findGeneric(GenericDataNode *&node, std::string const &type, std::string const &symbol); bool _triggered; time_t _lastTriggerTime; void onWakeup(int msgId); LargeSpread(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void LargeSpread::findGeneric(GenericDataNode *&node, std::string const &type, std::string const &symbol) { DataNodeArgument factory = GenericDataNodeFactory::findFactory(type); factory = factory.replace(symbolPlaceholder, symbol); addAutoLink(factory.getValue< GenericDataNodeFactory >() .find(this, 0, node)); } void LargeSpread::onWakeup(int msgId) { static const time_t MIN_BREAK_TIME = 30; enum {nsReady, nsTriggered, nsNeither } newState; bool bidValid; double bid; _bidData->getDouble(bidValid, bid); bool askValid; double ask; _askData->getDouble(askValid, ask); if (!(bidValid && askValid)) newState = nsNeither; else if (ask - bid > 0.499999) newState = nsTriggered; else newState = nsReady; if ((newState == nsTriggered) && (!_triggered) && (getSubmitTime() - _lastTriggerTime > MIN_BREAK_TIME)) report("Large Spread"); if (_triggered) _lastTriggerTime = getSubmitTime(); _triggered = newState != nsReady; } LargeSpread::LargeSpread(DataNodeArgument const &args) : _triggered(false), _lastTriggerTime(0) { std::string const &symbol = args.getStringValue(); const std::string exchange = getListedExchange(symbol); if (exchange != s_NYSE) return; findGeneric(_bidData, "NyseBid", symbol); findGeneric(_askData, "NyseAsk", symbol); } //////////////////////////////////////////////////////////////////// // TGenericCross // // This has been coming for a long time. You can insert any two factories // that return doubles. When one crosses above the other, we report a // predefined messages. Similar things have existed -- see // ConfirmedCrossing.pas -- but this is the first completely generic one. // // Message -- This is a fixed message to be displayed any time we fire // the alert. // ReportFactory -- This is the data which needs to cross above the // other data. It must move from below the other data // to above the other data. It may be equal in between. // CompareFactory -- We compare the data in reportFactory to this data. // IgnoreEvents -- 0 means that we check any time either data node // changes. 1 means that we ignore events from // ReportFactory (the first factory) and only watch // CompareFactory (the second factory). 2 means that we // watch the first one and ignore the second one. // This is an optimization for the SMAs because we know // they update at the same time. And we know that the // shorter one will always be valid if the longer one // is. In some of the previous comparisons, we // specifically defined it so that the price had to move, // not the reference number, to generate an alert. // // Note that symbol is not explicitly included as an input. Presumably // it is an imput in both ReportFactory and CompareFactory. However, it // doesn't have to be. // // Note: A few alerts like this will have one common data node watching // for both cases, then simple adapters around it to generate the up or down // alerts. Because there is so little work in this data node, there // wasn't any real nead for that. In fact, it probably would have been // more complicated that way. Also, the initial case for this alert is // a pair of intraday sma objects. And those already have a lot of // caching to ensure that they don't do a lot of work if you call them // multiple times. //////////////////////////////////////////////////////////////////// class GenericCross : public Alert { private: std::string _message; GenericDataNode *_reportData; GenericDataNode *_compareData; bool _primed; void onWakeup(int msgId); GenericCross(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void GenericCross::onWakeup(int msgId) { double reportValue; double compareValue; bool valid; //TclList msg; //msg<<"onWakeup() starting"<<"_primed"<<_primed<<"_message"<<_message // <<_reportData->getDebugInfo()<<_compareData->getDebugInfo(); //sendToLogFile(msg); _reportData->getDouble(valid, reportValue); if (!valid) { _primed = false; return; } _compareData->getDouble(valid, compareValue); if (!valid) { _primed = false; return; } //msg.clear(); //msg<<"onWakeup() got data"<<"_primed"<<_primed<<"_message"<<_message // <<"reportValue"< 0) { if (_primed) { report(_message); _primed = false; } } else if (difference < 0) _primed = true; } GenericCross::GenericCross(DataNodeArgument const &args) : _primed(false) { DataNodeArgumentVector const &argList = args.getListValue(); // Message, ReportFactory, CompareFactory, IgnoreEvents assert(argList.size() == 4); _message = argList[0].getStringValue(); const int ignoreEvents = argList[3].getIntValue(); addAutoLink(argList[1].getValue< GenericDataNodeFactory >() .find((ignoreEvents==1)?NULL:this, 0, _reportData)); addAutoLink(argList[2].getValue< GenericDataNodeFactory >() .find((ignoreEvents==2)?NULL:this, 0, _compareData)); } // This always returns a GenericDataNodeFactory wrapped in a DataNodeArgument. static DataNodeArgument smaCrossFactory(int faster, bool above, int slower, int period) { std::string msg = ntoa(faster); msg += " crossed "; if (above) msg += "above "; else msg += "below "; msg += ntoa(slower); msg += " ("; msg += ntoa(period); msg += " minute)"; const DataNodeArgument fastFactory = createIntradaySmaFactory(period, faster); const DataNodeArgument slowFactory = createIntradaySmaFactory(period, slower); DataNodeArgument aboveFactory; DataNodeArgument belowFactory; int ignore; if (above) { aboveFactory = fastFactory; belowFactory = slowFactory; ignore = 1; } else { aboveFactory = slowFactory; belowFactory = fastFactory; ignore = 2; } return GenericDataNodeFactory::create< GenericCross > (argList(msg, aboveFactory, belowFactory, ignore)); } static void storeSmaCrossFactory(int faster, bool above, int slower, int period) { std::string name = "Sma"; name += ntoa(faster); if (above) name += "Above"; else name += "Below"; name += ntoa(slower); name += '_'; name += ntoa(period); GenericDataNodeFactory::storeFactory (name, smaCrossFactory(faster, above, slower, period)); } //////////////////////////////////////////////////////////////////// // NyseImbalance //////////////////////////////////////////////////////////////////// class NyseImbalance : public Alert { private: GenericTosDataNode *_tosData; GenericDataNode *_imbalanceData; bool _moreBuyers; Integer _lastReport; void onWakeup(int msgId); NyseImbalance(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void NyseImbalance::onWakeup(int msgId) { if (!_tosData->getValid()) return; const Integer volume = _tosData->getLast().volume; if (volume <= 0) return; bool valid; Integer imbalance; _imbalanceData->getInteger(valid, imbalance); if (!valid) return; if (!_moreBuyers) imbalance = -imbalance; if (imbalance > _lastReport) { _lastReport = imbalance; const std::string shares = ntoa(imbalance); std::string msg; if (_moreBuyers) msg = "Buy Imbalance. "; else msg = "Sell Imbalance. "; msg += shares; msg += " shares."; report(msg, "s=" + shares, imbalance * 100.0 / volume); } if (imbalance <= 0) _lastReport = 0; } NyseImbalance::NyseImbalance(DataNodeArgument const &args) : _lastReport(0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, more buyers std::string const &symbol = argList[0].getStringValue(); if (getListedExchange(symbol) == s_NYSE) { _moreBuyers = argList[1].getBooleanValue(); addAutoLink(GenericTosDataNode::find(_tosData, symbol)); DataNodeArgument factory = GenericDataNodeFactory::findFactory("NyseImbalance"); factory = factory.replace(symbolPlaceholder, symbol); addAutoLink(factory.getValue< GenericDataNodeFactory >() .find(this, 0, _imbalanceData)); } } //////////////////////////////////////////////////////////////////// // GapRetracementArcUp // a.k.a. False gap up retracement //////////////////////////////////////////////////////////////////// class GapRetracementArcUp : public Alert { private: GenericTosDataNode *_tosData; double _volatility; double _previousClose; bool _primed; void onWakeup(int msgId); GapRetracementArcUp(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void GapRetracementArcUp::onWakeup(int msgId) { if (!_tosData->getValid()) { // If the crossing happens while the datafeed is dead, the event // is lost. Without this, we might report large numbers of // crossings if we've been down for a while, and the pullback // would be wrong (in addition to the time). _primed = false; return; } TosData const &l = _tosData->getLast(); if ((l.high <= 0) || (l.low <= 0) || (l.open <= 0)) _primed = false; else if ((_previousClose + _volatility) >= l.open) // Gapped down, no gap at all, or did not gap up high enough. _primed = false; else if ((l.high > l.open + _volatility) && (l.low > _previousClose) && (l.low < l.open) && _primed) { // Currently crossed, previously not crossed _primed = false; const double percentClosure = (l.open - l.low) / (l.open - _previousClose) * 100; report("Crossed above open after " + percentToString(percentClosure) + " gap closure.", percentClosure); } else if (l.high <= (l.open + _volatility)) _primed = true; else _primed = false; } GapRetracementArcUp::GapRetracementArcUp(DataNodeArgument const &args) : _primed(false) { std::string const &symbol = args.getStringValue(); _previousClose = getPreviousClose(symbol); _volatility = getTickVolatility(symbol); if (_volatility < MIN_VOLATILITY) _volatility = 0; else addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // GapRetracementArcDown // a.k.a. False gap down retracement //////////////////////////////////////////////////////////////////// class GapRetracementArcDown : public Alert { private: GenericTosDataNode *_tosData; double _volatility; double _previousClose; bool _primed; void onWakeup(int msgId); GapRetracementArcDown(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void GapRetracementArcDown::onWakeup(int msgId) { if (!_tosData->getValid()) { // If the crossing happens while the datafeed is dead, the event // is lost. Without this, we might report large numbers of // crossings if we've been down for a while, and the pullback // would be wrong (in addition to the time). _primed = false; return; } TosData const &l = _tosData->getLast(); if ((l.high <= 0) || (l.low <= 0) || (l.open <= 0) || (_previousClose <= 0)) _primed = false; else if (_previousClose <= l.open + _volatility) // Gapped up, no gap at all, or did not gap down enough. _primed = false; else if ((l.low < l.open - _volatility) && (l.high < _previousClose) && (l.high > l.open) && _primed) { // Currently crossed, previously not crossed _primed = false; const double percentClosure = (l.high - l.open) / (_previousClose - l.open) * 100; report("Crossed below open after " + percentToString(percentClosure) + " gap closure.", percentClosure); } else if (l.low >= (l.open - _volatility)) _primed = true; else _primed = false; } GapRetracementArcDown::GapRetracementArcDown(DataNodeArgument const &args) : _primed(false) { std::string const &symbol = args.getStringValue(); _previousClose = getPreviousClose(symbol); _volatility = getTickVolatility(symbol); if (_volatility < MIN_VOLATILITY) _volatility = 0; else addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } //////////////////////////////////////////////////////////////////// // PtsEntrySignals // // Precision Trading System / Mel // Draw a channel +/- 1 (23 period) standard deviation around a (23 // period) linear regression line. // 3 period EMA, 3 period MLR, and price are all going the same way. All // are moving toward the mean of the 23 period channel, and all are within // the one standard deviation channel. 3 period MLR is further in that // direction than the 3 period EMA. Quality is the distance between // the price and one standard deviation on the far side of the mean. // // To do this right we need to use the candles which are in progress, not // just the last historical. With an EMA or a MRL of only 3 periods, this // makes a big difference. // // Test code: // telnet fogell 127.0.0.1 // command=debug_standard_candles&symbol=DELL&minutes_per_bar=5&action=force_active // command=debug_standard_candles&symbol=DELL&minutes_per_bar=5&action=dump // command=debug_standard_candles&symbol=DELL&minutes_per_bar=5&action=set // command=debug_tos&symbol=DELL&set=1&price=15 // This is far from complete, but it at least lets us see what's going on. //////////////////////////////////////////////////////////////////// class PtsEntrySignals : public Alert { private: bool _up; std::string _baseMsg; LinearRegressionBase *_channelData; LinearRegressionBase *_mlrData; IntradayEma *_emaData; GenericTosDataNode *_tosData; StandardCandles *_timerData; // This seems to go off a lot. I'm changing it so it will not report more // than once per candle. bool _reportedThisPeriod; // This is where we were at end the of the last candle. Every time we talk // about the direction of an indicator, we are comparing this to the current // values. bool _previouslyValid; // If not, none of the following make sense. double _previousPrice; double _previousEma3; double _previousMlr3; // These could be recomputed every time we have a print. But I don't think // it would make a big difference either way. Mostly I'm reading these // values in the candle routine to verify that we have enough data to compute // them. That way we can filter out a lot of stocks very quickly and avoid // a lot of work. double _previousStdDev23; double _previousMlr23; // This is what we saw the last time we looked at the last print. Because // this is a somewhat complicated alert, we don't want to compute everything // again unless the price really chnaged. This can be set to NAN to force // us to examine the next print. Use forceCheckForAlert() to do that. double _lastPrice; void forceCheckForAlert(); // We don't want to report this alert more than once per candle. bool _recentlyTriggered; // Mel's conditions all describe a state, not an event. We turn these into // an event by reporting when the state changes from false to true. This // variable is needed to know if we've changed. We don't properly check // this when we first start. For simplicily it is initially false, so we // report everything that's in the right state at least once per day. bool _previouslyTriggered; time_t _lastReportTime; void saveBaseline(); void checkForAlert(); bool correctDirection(double direction); int directionScaler(); enum { wCandle, wTos }; void onWakeup(int msgId); void dumpState(); PtsEntrySignals(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void PtsEntrySignals::dumpState() { TclList msg; const int count = _timerData->historicalCandleCount(); msg<<"candle count"<getEpoch(); msg<<"last ema"; if (_emaData->getLastValid()) msg<<_emaData->getLastValue(); else msg<<'!'; bool valid; double mlr, stdDev; _channelData->get(false, valid, mlr, stdDev); msg<<"last mlr 23"; if (valid) msg<get(false, valid, mlr); msg<<"last mlr 3"; if (valid) msg<getValid()) msg<<_tosData->getLast().price; else msg<<'!'; msg<<"last close"; if (count > 0) msg<<_timerData->getHistory().rbegin()->close; else msg<<'!'; msg<<"current ema"; if (_emaData->getCurrentValid()) msg<<_emaData->getCurrentValue(); else msg<<'!'; _mlrData->get(true, valid, mlr); msg<<"current mlr 3"; if (valid) msg<getLastValid()) return; _channelData->get(false, _previouslyValid, _previousMlr23, _previousStdDev23); if (!_previouslyValid) return; _mlrData->get(false, _previouslyValid, _previousMlr3); if (!_previouslyValid) return; // Assume we have this much data or else the previous items would have // failed. Use the candles, rather than the TOS, for when we first // initialize before the market opens. _previousPrice = _timerData->getHistory().rbegin()->close; _previousEma3 = _emaData->getLastValue(); } void PtsEntrySignals::checkForAlert() { // Don't check unless something interesting has changed. const double currentPrice = _tosData->getLast().price; if (currentPrice == _lastPrice) return; _lastPrice = currentPrice; if (!_timerData->isActive()) return; // If we abort early, assume the formula did not match. const bool savePreviouslyTriggered = _previouslyTriggered; // Set this to false even if we're not sure. For example, if the data was // not valid before, and now it's valid and conforms to the the formula, then // we report an alert. This is consistent with the constructor. If we // match the formulas when we start, then we alway report, even if there was // no change from last night. _previouslyTriggered = false; // Get the remaining data and check that the inputs are all valid. if (!_previouslyValid) return; if (!_emaData->getCurrentValid()) return; if (!_tosData->getValid()) return; bool valid; double currentMlr3; _mlrData->get(true, valid, currentMlr3); if (!valid) return; const double currentEma3 = _emaData->getCurrentValue(); // We have all the data. Now let's check the formula. if (!(correctDirection(currentEma3 - _previousEma3) && correctDirection(currentPrice - _previousPrice) && correctDirection(currentMlr3 - _previousMlr3) && correctDirection(currentMlr3 - currentEma3) && correctDirection(_previousMlr23 - currentPrice) && correctDirection(_previousMlr23 - currentMlr3) // This next one is in the written requirements, is implied by the // previous two lines, so we don't have to explictly test it. // && correctDirection(_previousMlr23 - currentEma3) )) return; // We match the formula! _previouslyTriggered = true; // But this alert is edge triggered so we don't report it every time. if (savePreviouslyTriggered) return; // Try not to spam the user every time the condition goes from false to true. if (_reportedThisPeriod) return; _reportedThisPeriod = true; // Report it. std::string msg = _baseMsg; std::string altMsg; if (_lastReportTime) { msg += " Previously reported "; msg += durationString(_lastReportTime, getSubmitTime()); msg += " ago."; altMsg = "d=" + ntoa(getSubmitTime() - _lastReportTime); } const double toBound = _previousMlr23 + _previousStdDev23 * directionScaler(); const double quality = (toBound - currentPrice) * directionScaler(); report(msg, altMsg, quality); _lastReportTime = getSubmitTime(); } void PtsEntrySignals::forceCheckForAlert() { _lastPrice = std::numeric_limits< double >::quiet_NaN(); } inline bool PtsEntrySignals::correctDirection(double direction) { return _up?(direction > 0.0):(direction < 0.0); } inline int PtsEntrySignals::directionScaler() { return _up?1:-1; } void PtsEntrySignals::onWakeup(int msgId) { switch (msgId) { case wCandle: _reportedThisPeriod = false; saveBaseline(); // Check immeidately, without waiting for a print, to best simulate // what someone with a trading app would see on his screen. checkForAlert(); break; case wTos: checkForAlert(); break; } } PtsEntrySignals::PtsEntrySignals(DataNodeArgument const &args) : _reportedThisPeriod(false), _previouslyValid(false), _previouslyTriggered(false), _lastReportTime(0) { DataNodeArgumentVector const &argL = args.getListValue(); assert(argL.size() == 3); // Symbol, Up, Minutes std::string const &symbol = argL[0].getStringValue(); const int minutesPerBar = argL[1].getIntValue(); _up = argL[2].getBooleanValue(); _baseMsg = "PTS "; if (_up) _baseMsg += "long"; else _baseMsg += "short"; _baseMsg += " entry signal, "; _baseMsg += ntoa(minutesPerBar); _baseMsg += " minute chart."; addAutoLink(LinearRegressionBase::find(_channelData, symbol, minutesPerBar, 23)); addAutoLink(LinearRegressionBase::find(_mlrData, symbol, minutesPerBar, 3)); addAutoLink(IntradayEma::find(_emaData, symbol, minutesPerBar, 3)); addAutoLink(GenericTosDataNode::find(this, wTos, _tosData, symbol)); addAutoLink(StandardCandles::find(this, wCandle, _timerData, argList(symbol, minutesPerBar))); saveBaseline(); } //////////////////////////////////////////////////////////////////// // OneMinuteVolumeSpike // // We're making this as simple as possible. We look at the volume for // the last minute, breaking on exact minutes. So we're looking at the // last 0 to 59.999 seconds worth of data. // // This will look a lot like running up now, but with volume instead of // prices. When the volume gets to 200% of expected, we report an alert. // And then again at 250%. And then again at 300%. Etc. Those numbers // are constants in this code. The user can use the quality filter to // limit these further. Initially we report each time the quality goes up 50%, // but as the quality goes up, so does this requirement. // // To keep this thing from going crazy, we have a second constraint. // The quality should be at least 1/2 as big as we reported last time, // or we won't report it. Last time is usually the last minute. If // trading drops significantly, then we use 1/4 of the value from 2 times // ago, or 1/8 the value from 3 times ago, etc. Whichever is the largest // we have to beat. // // We use the standard historical volume expectations, which change during // the day. These are a little simpler here than in some places because // we are always resetting at exact one minute boundaries. Note that // we require the quality, not the volume, to be twice as much as the // previous candle. //////////////////////////////////////////////////////////////////// class OneMinuteVolumeSpike : public Alert { private: AverageHistoricalData *_volumeData; GenericTosDataNode *_tosData; double _minQualityToReport; double _expectedVolume; time_t _newCandleTime; Integer _startingVolume; int _alertCountThisMinute; int _printCountThisMinute; int _printCountToday; void onWakeup(int msgId); OneMinuteVolumeSpike(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; OneMinuteVolumeSpike::OneMinuteVolumeSpike(DataNodeArgument const &args) : _minQualityToReport(0.0), _expectedVolume(0.0), _newCandleTime(0), _startingVolume(0), _alertCountThisMinute(0), // am= _printCountThisMinute(0), // pm= _printCountToday(0) // pt= { std::string const &symbol = args.getStringValue(); addAutoLink(AverageHistoricalData::find(_volumeData, symbol)); if (_volumeData->isValid()) addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); } void OneMinuteVolumeSpike::onWakeup(int msgId) { if (!_tosData->getValid()) return; const Integer volume = _tosData->getLast().volume; const time_t time = getSubmitTime(); if (_tosData->getLast().newPrint) { _printCountThisMinute++; _printCountToday++; } if (time < _newCandleTime) { // Same candle. Keep comparing to the baseline. if (volume <= _startingVolume) // If there was a correction, the volume could go backwards. // This would be a reasonable response. _startingVolume = volume; else if (_expectedVolume > 0) { const double quality = (volume - _startingVolume) / _expectedVolume; if (quality > _minQualityToReport) { std::string msg = "Trading at "; msg += ntoa((int)(quality * 100 + 0.5)); msg += "% of historical volume."; _alertCountThisMinute++; std::string moreFields = "am="; moreFields += ntoa(_alertCountThisMinute); moreFields += "&pm="; moreFields += ntoa(_printCountThisMinute); moreFields += "&pt="; moreFields += ntoa(_printCountToday); report(msg, moreFields, quality); double reportInterval; if (quality < 10) // This needs to be 0.5 higher than before. But we always // push it back down to a round number. So if we were // expecing a min of 3.0 this time, and we got 3.0 or // 3.499, next time we need a min of 3.5. reportInterval = 0.5; else if (quality < 25) // Report when we cross an integer. reportInterval = 1.0; else if (quality < 100) reportInterval = 5.0; else if (quality < 1000) // This scale is pretty aribtrary. Ideally we'd have // some cap where we stop reporting, or a formula that // would keep trimming these forever. reportInterval = 10.0; else if (quality < 10000) reportInterval = 100.0; else reportInterval = 1000.0; _minQualityToReport = (floor(quality / reportInterval) + 1) * reportInterval; } } } else { // Start a new candle. _newCandleTime = time + MARKET_HOURS_MINUTE - (secondOfTheDay(time) % MARKET_HOURS_MINUTE); _startingVolume = volume; // Note, if we skip a period due to lack of activity, we don't // make a special case. So perhaps the volume number decreases // more slowly than expected. That's just not an interesting // case. _minQualityToReport = std::max(2.0, _minQualityToReport/2.0); int timeFrame = _volumeData->higherTimeFrame(time); if (timeFrame == AHV_PRE_MARKET) timeFrame++; else if (timeFrame == AHV_POST_MARKET) timeFrame--; _expectedVolume = _volumeData->getVolume(timeFrame) / 15.0; _alertCountThisMinute = 0; _printCountThisMinute = 0; //TclList msg; // msg<<__FILE__<<__LINE__<<__FUNCTION__<<"Start new candle" // <<_tosData->symbol()<<_minQualityToReport<<_expectedVolume; //sendToLogFile(msg); } } //////////////////////////////////////////////////////////////////// // StochasticReturnToNormal //////////////////////////////////////////////////////////////////// class StochasticReturnToNormal : public Alert { private: IntradayStochastic *_stochasticData; TradeDirection _tradeDirection; bool _primed; void onWakeup(int msgId); StochasticReturnToNormal(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void StochasticReturnToNormal::onWakeup(int msgId) { bool valid; double pK, pD; // %K, %D _stochasticData->get(valid, pK, pD); if (!valid) { _primed = false; return; } const double value = pD; if (_tradeDirection.isShort()) { if (value > 85.0) _primed = true; else if (_primed && (value < 80.0)) { _primed = false; report("No longer overbought."); } } else { if (value < 15.0) _primed = true; else if (_primed && (value > 20.0)) { _primed = false; report("No longer oversold."); } } } StochasticReturnToNormal::StochasticReturnToNormal (DataNodeArgument const &args) : _primed(false) { DataNodeArgumentVector const &argL = args.getListValue(); assert(argL.size() == 3); // Symbol, Minutes, Up std::string const &symbol = argL[0].getStringValue(); const int minutesPerBar = argL[1].getIntValue(); _tradeDirection = argL[2].getBooleanValue(); addAutoLink(IntradayStochastic::findFast(this, 0, _stochasticData, symbol, minutesPerBar, 14, 3)); } //////////////////////////////////////////////////////////////////// // StockTwitsActivitySpike // // This is based heavily on the StrongVolume alert. It looks at // the number of tweets, rather than the number of shares, of // course. // // There is one interesting difference. Many stocks have an average // of less than one tweet per day. We don't want every tweet for // a stock to set off an alert. So you need a minimum of 5 tweets // or 1x normal for the day, to set off an alert, whichever is // larger. //////////////////////////////////////////////////////////////////// class StockTwitsActivitySpike : public Alert { private: StockTwitsDataNode *_stockTwitsData; Integer _next; void onWakeup(int msgId); void updateNext(); StockTwitsActivitySpike(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void StockTwitsActivitySpike::StockTwitsActivitySpike::onWakeup(int msgId) { if (!_stockTwitsData->getValid()) // We have seen this!!! The proxy might not have given us the initial // value yet. return; if (_next < 0) { // Do our initialization now. updateNext(); return; } const Integer current = _stockTwitsData->getData().messageCount; if (current >= _next) { const double currentQuality = current / _stockTwitsData->getDailyAverage(); report("Currently at " + ntoa((Integer)currentQuality) + " x average daily social activity", currentQuality); updateNext(); } } void StockTwitsActivitySpike::StockTwitsActivitySpike::updateNext() { if (_stockTwitsData->getDailyAverage() <= 0) _next = std::numeric_limits< Integer >::max(); else _next = _stockTwitsData->getData().messageCount + std::max((Integer)5, (Integer)ceil(_stockTwitsData->getDailyAverage())); } StockTwitsActivitySpike::StockTwitsActivitySpike (DataNodeArgument const &args) : _next(-1) { const std::string symbol = args.getStringValue(); addAutoLink(StockTwitsDataNode::find(this, 0, _stockTwitsData, symbol)); } //////////////////////////////////////////////////////////////////// // HaltResume //////////////////////////////////////////////////////////////////// class HaltResume : public Alert { private: enum HaltState {hltNone, hltHalted, hltResumed }; HaltState _lastHaltState; bool _resume; GenericHaltDataNode *_haltData; void onWakeup(int msgId); HaltResume(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; void HaltResume::onWakeup(int msgId) { HaltData current = _haltData->getLast(); HaltState newHaltState; std::string haltString; switch (current.type) { case 0: newHaltState = hltResumed; haltString = "Resumed"; if (!_resume) return; break; case 1: newHaltState = hltHalted; haltString = "Open delayed"; if (_resume) return; break; case 2: newHaltState = hltHalted; haltString = "Halted"; if (_resume) return; break; case 3: newHaltState = hltHalted; haltString = "No open, no resume"; if (_resume) return; break; default: return; } std::string priceString; if (current.price > 0.00001) { priceString = " at " + formatPrice(current.price, false); } std::string reasonString; switch (current.reason) { case 0: break; case 1: reasonString = " because of news"; break; case 2: reasonString = " because of disseminated news"; break; case 3: reasonString = " because of order imbalance"; break; case 4: reasonString = " because of equipment change"; break; case 5: reasonString = " (pending additional information)"; break; case 6: reasonString = " (suspension)"; break; case 7: reasonString = " (SEC)"; break; case 8: reasonString = ""; break; case 9: reasonString = " because of limit up/down pause"; break; case 10: reasonString = " because of market wide halt (level 1)"; break; case 11: reasonString = " because of market wide halt (level 2)"; break; case 12: reasonString = " because of market wide halt (level 3)"; break; case 13: reasonString = " (market wide)"; break; case 14: reasonString = " because of limit up/down straddle"; break; case 15: reasonString = " because of extraordinary market activity"; break; case 16: reasonString = " (ETF)"; break; case 17: reasonString = " because of non-compliance"; break; case 18: reasonString = " because of filings not current"; break; case 19: reasonString = " because of operations"; break; case 20: reasonString = " because of pending IPO"; break; case 21: reasonString = " because of corporate action"; break; case 22: reasonString = " because quote unavailable"; break; case 23: reasonString = " because of single stock pause"; break; case 24: reasonString = " because of single stock pause resume"; break; case 25: reasonString = " because of order influx"; break; case 26: reasonString = " because of volatility"; break; case 27: reasonString = " because of sub-penny trading"; break; case 28: reasonString = " because of news and resumption times"; break; case 29: reasonString = " because of qualifications resolved"; break; case 30: reasonString = " because of filings resolved"; break; case 31: reasonString = " because issuer news not forthcoming"; break; case 32: reasonString = " because of requirements met"; break; case 33: reasonString = " because of filings met"; break; case 34: reasonString = " (concluded by other authority)"; break; case 35: reasonString = " because new issue available"; break; case 36: reasonString = " because issue available"; break; case 37: reasonString = " (IPO - release for quotation)"; break; case 38: reasonString = " (IPO - window extension)"; break; case 39: reasonString = " because of market wide circuit breaker (carryover from prior day)"; break; case 40: reasonString = " because post-close"; break; case 41: reasonString = " because pre-cross"; break; case 42: reasonString = " because of cross"; break; case 43: reasonString = " because of SEC revocation"; break; case 44: reasonString = " (foreign market regulatory)"; break; case 45: reasonString = " because of secuirty deletion"; break; case 46: reasonString = " because of extraordinary events"; break; case 47: reasonString = " because of internal halt"; break; case 48: reasonString = " (was frozen)"; break; case 49: reasonString = " (was delayed)"; break; case 50: reasonString = " (as-of update)"; break; case 51: reasonString = " because of related security news dissemination"; break; case 52: reasonString = " because of related security news pending"; break; case 53: reasonString = " because of related security"; break; case 54: reasonString = " (in view of common)"; break; case 55: reasonString = " (exchange specific)"; break; case 56: reasonString = " (no open, no resume)"; break; case 57: reasonString = " because not available for trading"; break; case 58: reasonString = " (component derivative of exchange listed)"; break; default: reasonString = " because of reason #" + ntoa(current.reason); break; } report(haltString + priceString + reasonString,0); _lastHaltState = newHaltState; TclList msg; msg << "HaltResume" << "price" << current.price << "formatPrice" << formatPrice(current.price, false); sendToLogFile(msg); } HaltResume::HaltResume(DataNodeArgument const &args) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, IsResume std::string const &symbol = argList[0].getStringValue(); _resume = argList[1].getBooleanValue(); addAutoLink(GenericHaltDataNode::find(this, 0, _haltData, symbol)); } //////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////// void initializeMiscAlerts() { GenericDataNodeFactory::storeStandardFactory< HighVolume > ("HighRelativeVolume"); GenericDataNodeFactory::storeStandardFactory< GapDownReversal > ("GapDownReversal"); GenericDataNodeFactory::storeStandardFactory< GapUpReversal > ("GapUpReversal"); GenericDataNodeFactory::storeStandardFactory< BlockPrint > ("BlockPrint"); GenericDataNodeFactory::sf< IntraDayHighLow > ("5MinHigh", symbolPlaceholderObject, true, 5); GenericDataNodeFactory::sf< IntraDayHighLow > ("5MinLow", symbolPlaceholderObject, false, 5); GenericDataNodeFactory::sf< IntraDayHighLow > ("10MinHigh", symbolPlaceholderObject, true, 10); GenericDataNodeFactory::sf< IntraDayHighLow > ("10MinLow", symbolPlaceholderObject, false, 10); GenericDataNodeFactory::sf< IntraDayHighLow > ("15MinHigh", symbolPlaceholderObject, true, 15); GenericDataNodeFactory::sf< IntraDayHighLow > ("15MinLow", symbolPlaceholderObject, false, 15); GenericDataNodeFactory::sf< IntraDayHighLow > ("30MinHigh", symbolPlaceholderObject, true, 30); GenericDataNodeFactory::sf< IntraDayHighLow > ("30MinLow", symbolPlaceholderObject, false, 30); GenericDataNodeFactory::sf< IntraDayHighLow > ("60MinHigh", symbolPlaceholderObject, true, 60); GenericDataNodeFactory::sf< IntraDayHighLow > ("60MinLow", symbolPlaceholderObject, false, 60); GenericDataNodeFactory::sf< LargeBidOrAsk > ("LargeBidSize", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< LargeBidOrAsk > ("LargeAskSize", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< PercentChangeForTheDay > ("PercentUpForDay", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< PercentChangeForTheDay > ("PercentDownForDay", symbolPlaceholderObject, false); GenericDataNodeFactory::storeStandardFactory< StrongVolume > ("StrongVolume"); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakout1", symbolPlaceholderObject, true, 1); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakdown1", symbolPlaceholderObject, false, 1); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakout2", symbolPlaceholderObject, true, 2); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakdown2", symbolPlaceholderObject, false, 2); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakout5", symbolPlaceholderObject, true, 5); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakdown5", symbolPlaceholderObject, false, 5); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakout10", symbolPlaceholderObject, true, 10); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakdown10", symbolPlaceholderObject, false, 10); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakout15", symbolPlaceholderObject, true, 15); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakdown15", symbolPlaceholderObject, false, 15); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakout30", symbolPlaceholderObject, true, 30); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakdown30", symbolPlaceholderObject, false, 30); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakout60", symbolPlaceholderObject, true, 60); GenericDataNodeFactory::sf< OpeningRangeBreak > ("OpeningRangeBreakdown60", symbolPlaceholderObject, false, 60); GenericDataNodeFactory::sf< BrightBands > ("BrightBandsUp", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< BrightBands > ("BrightBandsDown", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< VwapDivergenceAlert > ("VwapDivergenceUp", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< VwapDivergenceAlert > ("VwapDivergenceDown", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< TradingAboveBelow > ("TradingAbove", symbolPlaceholderObject, true, false); GenericDataNodeFactory::sf< TradingAboveBelow > ("TradingBelow", symbolPlaceholderObject, false, false); GenericDataNodeFactory::sf< TradingAboveBelow > ("TradingAboveSpecialist", symbolPlaceholderObject, true, true); GenericDataNodeFactory::sf< TradingAboveBelow > ("TradingBelowSpecialist", symbolPlaceholderObject, false, true); GenericDataNodeFactory::sf< NR7 >("NR7-1", symbolPlaceholderObject, 1); GenericDataNodeFactory::sf< NR7 >("NR7-2", symbolPlaceholderObject, 2); GenericDataNodeFactory::sf< NR7 >("NR7-5", symbolPlaceholderObject, 5); GenericDataNodeFactory::sf< NR7 >("NR7-10", symbolPlaceholderObject, 10); GenericDataNodeFactory::sf< NR7 >("NR7-15", symbolPlaceholderObject, 15); GenericDataNodeFactory::sf< NR7 >("NR7-30", symbolPlaceholderObject, 30); GenericDataNodeFactory::storeStandardFactory< UnusualNumberOfPrints > ("UnusualNumberOfPrints"); GenericDataNodeFactory::storeStandardFactory< LargeSpread >("LargeSpread"); storeSmaCrossFactory(8, true, 20, 2); storeSmaCrossFactory(8, true, 20, 5); storeSmaCrossFactory(8, true, 20, 15); storeSmaCrossFactory(8, false, 20, 2); storeSmaCrossFactory(8, false, 20, 5); storeSmaCrossFactory(8, false, 20, 15); storeSmaCrossFactory(20, true, 200, 2); storeSmaCrossFactory(20, true, 200, 5); storeSmaCrossFactory(20, true, 200, 15); storeSmaCrossFactory(20, false, 200, 2); storeSmaCrossFactory(20, false, 200, 5); storeSmaCrossFactory(20, false, 200, 15); storeSmaCrossFactory(5, true, 8, 1); storeSmaCrossFactory(5, true, 8, 2); storeSmaCrossFactory(5, true, 8, 4); storeSmaCrossFactory(5, true, 8, 5); storeSmaCrossFactory(5, true, 8, 10); storeSmaCrossFactory(5, true, 8, 20); storeSmaCrossFactory(5, true, 8, 30); storeSmaCrossFactory(5, false, 8, 1); storeSmaCrossFactory(5, false, 8, 2); storeSmaCrossFactory(5, false, 8, 4); storeSmaCrossFactory(5, false, 8, 5); storeSmaCrossFactory(5, false, 8, 10); storeSmaCrossFactory(5, false, 8, 20); storeSmaCrossFactory(5, false, 8, 30); GenericDataNodeFactory::sf< NyseImbalance > ("NyseBuyImbalance", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< NyseImbalance > ("NyseSellImbalance", symbolPlaceholderObject, false); GenericDataNodeFactory::storeStandardFactory< GapRetracementArcUp > ("GapRetracementArcUp"); GenericDataNodeFactory::storeStandardFactory< GapRetracementArcDown > ("GapRetracementArcDown"); GenericDataNodeFactory::sf< PtsEntrySignals > ("PtsEntryUp5", symbolPlaceholderObject, 5, true); GenericDataNodeFactory::sf< PtsEntrySignals > ("PtsEntryDown5", symbolPlaceholderObject, 5, false); GenericDataNodeFactory::sf< PtsEntrySignals > ("PtsEntryUp15", symbolPlaceholderObject, 15, true); GenericDataNodeFactory::sf< PtsEntrySignals > ("PtsEntryDown15", symbolPlaceholderObject, 15, false); GenericDataNodeFactory::sf< PtsEntrySignals > ("PtsEntryUp30", symbolPlaceholderObject, 30, true); GenericDataNodeFactory::sf< PtsEntrySignals > ("PtsEntryDown30", symbolPlaceholderObject, 30, false); GenericDataNodeFactory::sf< PtsEntrySignals > ("PtsEntryUp90", symbolPlaceholderObject, 90, true); GenericDataNodeFactory::sf< PtsEntrySignals > ("PtsEntryDown90", symbolPlaceholderObject, 90, false); GenericDataNodeFactory::storeStandardFactory< OneMinuteVolumeSpike > ("VolumeSpike1"); GenericDataNodeFactory::sf< StochasticReturnToNormal > ("StochReturnUp1", symbolPlaceholderObject, 1, true); GenericDataNodeFactory::sf< StochasticReturnToNormal > ("StochReturnUp5", symbolPlaceholderObject, 5, true); GenericDataNodeFactory::sf< StochasticReturnToNormal > ("StochReturnUp15", symbolPlaceholderObject, 15, true); GenericDataNodeFactory::sf< StochasticReturnToNormal > ("StochReturnUp60", symbolPlaceholderObject, 60, true); GenericDataNodeFactory::sf< StochasticReturnToNormal > ("StochReturnDown1", symbolPlaceholderObject, 1, false); GenericDataNodeFactory::sf< StochasticReturnToNormal > ("StochReturnDown5", symbolPlaceholderObject, 5, false); GenericDataNodeFactory::sf< StochasticReturnToNormal > ("StochReturnDown15", symbolPlaceholderObject, 15, false); GenericDataNodeFactory::sf< StochasticReturnToNormal > ("StochReturnDown60", symbolPlaceholderObject, 60, false); GenericDataNodeFactory::storeStandardFactory< StockTwitsActivitySpike > ("StockTwitsActivitySpike"); GenericDataNodeFactory::sf< HaltResume > ("Halt", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< HaltResume > ("Resume", symbolPlaceholderObject, true); /* Gone and not returing: Woodie's stuff Stepping Down. */ }