#include #include "../../shared/MarketHours.h" #include "../data_framework/StandardCandles.h" #include "../data_framework/GenericTosData.h" #include "../alert_framework/AlertBase.h" #include "MACD.h" //////////////////////////////////////////////////////////////////// // MACD // // This is computed at the end of each candle. // // For performance and flexibility I use a simple and common model. // Every time the underlying candles change, I update my internal // state, then I notify any listeners. I also check for updates // any time a listener asks for data. Currently that's overkill, // but this would allow us to use this data node in other places. //////////////////////////////////////////////////////////////////// class MACD : public DataNode { private: static const int SLOW_EMA_PERIODS = 26; static const int FAST_EMA_PERIODS = 12; static const int SIGNAL_PERIODS = 9; // We could declare these inline if we were allowed to use constexpr. // For the time being, I want to be able to compile this on fogel and jules, // which are too old to understand constexpr. Maybe bring this back later? // TODO static const double SLOW_EMA_FACTOR /*= (2.0 / (SLOW_EMA_PERIODS + 1))*/; static const double FAST_EMA_FACTOR /*= (2.0 / (FAST_EMA_PERIODS + 1))*/; static const double SIGNAL_FACTOR /*= (2.0 / (SIGNAL_PERIODS + 1))*/; StandardCandles *_candleData; StandardCandles::Epoch _previousEpoch; bool _valid; // This is only valid when both of the numbers below are valid. double _slowEma; double _fastEma; double _macdSignal; // This is the EMA of the macd; double getMacd() const { return _fastEma - _slowEma; } void onWakeup(int msgId); void addOne(double newPrice); void verifyCurrent(); MACD(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(DataNodeListener *listener, int msgId, MACD *&node, std::string const &symbol, int minutesPerBar) { return findHelper(listener, msgId, node, argList(symbol, minutesPerBar)); } // This is the value at the end of the last candle. void getLast(bool &valid, double &macd, double &macdSignal); // This allows you to test what the value will change to. We assume that // the next candle will end with "current" as the price. void getWith(double current, bool &valid, double &macd, double &macdSignal); // We only want to look at things during regular market hours. The candles // feeding this data node already do that. But if you watch the TOS data and // call getWith() bool isActive() { return _candleData->isActive(); } }; // Note: In C++11 we could keep these up in the class, but we'd need to use // the constexpr const double MACD::SLOW_EMA_FACTOR = (2.0 / (SLOW_EMA_PERIODS + 1)); const double MACD::FAST_EMA_FACTOR = (2.0 / (FAST_EMA_PERIODS + 1)); const double MACD::SIGNAL_FACTOR = (2.0 / (SIGNAL_PERIODS + 1)); void MACD::onWakeup(int msgId) { verifyCurrent(); notifyListeners(); } void MACD::verifyCurrent() { if (_previousEpoch == _candleData->getEpoch()) return; _previousEpoch = _candleData->getEpoch(); if (_candleData->historicalCandleCount() <= SLOW_EMA_PERIODS + SIGNAL_PERIODS) // This is a standard assumption for EMA's. It's a bit arbitrary, but it // will do. The less data we have, the more difference there could be // between us and someone who had more data. And we can totally avoid // stocks with not a lot of data. _valid = false; else if (_valid && (_candleData->getLastTransition() == StandardCandles::ltNewCandle)) // This is an optimization. The next case would have been sufficient. addOne(_candleData->getHistory().rbegin()->close); else { // Start from scratch. StandardCandles::CandleArray const &candles = _candleData->getHistory(); _slowEma = candles[0].close; _fastEma = _slowEma; _macdSignal = 0; for (StandardCandles::CandleArray::const_iterator it = candles.begin(); it != candles.end(); it ++) addOne(it->close); _valid = true; } } void updateEma(double &accumulator, double newValue, double factor) { accumulator += (newValue - accumulator) * factor; } void MACD::addOne(double newPrice) { updateEma(_slowEma, newPrice, SLOW_EMA_FACTOR); updateEma(_fastEma, newPrice, FAST_EMA_FACTOR); updateEma(_macdSignal, getMacd(), SIGNAL_FACTOR); } MACD::MACD(DataNodeArgument const &args) : _previousEpoch(0), _valid(false) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, Minutes/Bar std::string const &symbol = argList[0].getStringValue(); const int minutesPerBar = argList[1].getIntValue(); addAutoLink(StandardCandles::find(this, 0, _candleData, symbol, minutesPerBar)); MACD::verifyCurrent(); } void MACD::getLast(bool &valid, double &macd, double &macdSignal) { verifyCurrent(); valid = _valid; if (valid) { macd = getMacd(); macdSignal = _macdSignal; } } void MACD::getWith(double current, bool &valid, double &macd, double &macdSignal) { verifyCurrent(); valid = _valid; // This is not perfect. Ideally this would report valid one candle before // getLast() reported that it was valid. if (valid) { double slowEma = _slowEma; updateEma(slowEma, current, SLOW_EMA_FACTOR); double fastEma = _fastEma; updateEma(fastEma, current, FAST_EMA_FACTOR); macd = fastEma - slowEma; macdSignal = _macdSignal; updateEma(macdSignal, macd, SIGNAL_FACTOR); } } //////////////////////////////////////////////////////////////////// // MacdAlert //////////////////////////////////////////////////////////////////// class MacdAlert : public Alert { private: MACD *_macdData; GenericTosDataNode *_tosData; AboveOrBelow _stateToReport; bool _histogram; std::string _message; time_t _blackout; AboveOrBelow _lastState; double _lastChecked; bool _alreadyReported; void checkNow(); enum { wCandles, wTos }; void onWakeup(int msgId); MacdAlert(DataNodeArgument const &args); friend class GenericDataNodeFactory; public: static void factory(int minutesPerBar, bool up, bool histogram); static void factory(int minutesPerBar); }; MacdAlert::MacdAlert(DataNodeArgument const &args) : _lastState(ccEqual) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 5); // Symbol, Minutes/Bar, up, histogram, message std::string const &symbol = argList[0].getStringValue(); const int minutesPerBar = argList[1].getIntValue(); const bool up = argList[2].getBooleanValue(); _stateToReport = up?ccAbove:ccBelow; _histogram = argList[3].getBooleanValue(); _message = argList[4].getStringValue(); _blackout = midnight(time(NULL)) + MARKET_HOURS_OPEN + 30; addAutoLink(MACD::find(this, wCandles, _macdData, symbol, minutesPerBar)); addAutoLink(GenericTosDataNode::find(this, wTos, _tosData, symbol)); onWakeup(wCandles); } void MacdAlert::checkNow() { if (_alreadyReported) // Report at most once per candle. In large part this is to avoid a lot of // noisy signals. Also, our caching system (_lastChecked) would need some // adjustment if we decided to display every cross. return; if (!_tosData->getValid()) { _lastState = ccEqual; return; } const double current = _tosData->getLast().price; if (current == 0.0) { _lastState = ccEqual; return; } // This is an optimization. For either type of crossed above alert, if we // were below, and the price dropped, we will still be below. If the price // goes up, then it's possible that we crossed above. For the crossed // below alerts, we only need to look again if the price drops. After we // get a new candle, we always need to look again. if (_stateToReport == ccAbove) { if (current <= _lastChecked) return; } else if (_stateToReport == ccBelow) { if (current >= _lastChecked) return; } _lastChecked = current; bool valid; double macd; double macdSignal; _macdData->getWith(current, valid, macd, macdSignal); if (!valid) { _lastState = ccEqual; return; } //report("Possible, MACD=" + ntoa(macd) + ", Signal=" + ntoa(macdSignal)); const double value = _histogram?(macd-macdSignal):macd; AboveOrBelow newState; if (value > 0) newState = ccAbove; else if (value < 0) newState = ccBelow; else newState = ccEqual; if ((newState == _lastState) || (newState == ccEqual)) // No change. return; if (newState == _stateToReport) { if (time(NULL) > _blackout) report(_message); // Is this really required? _lastChecked may be sufficient. _alreadyReported = true; } //report(_message + ", MACD=" + ntoa(macd) + ", Signal=" + ntoa(macdSignal)); _lastState = newState; } void MacdAlert::onWakeup(int msgId) { switch (msgId) { case wCandles: // Allow at most one alert per candle. _alreadyReported = false; // Force a new check. Our smart caching optimization only works within // a candle. if (_stateToReport == ccAbove) // Every price will be above this. _lastChecked = -std::numeric_limits< double >::max(); else // Every price will be below this. _lastChecked = std::numeric_limits< double >::max(); // Check for an alert immediately. That will best match a person // watching the data change on a typical charting application. checkNow(); break; case wTos: // Since we only look at historical data from market hours, we need to // restrict this to market hours, too. if (_macdData->isActive()) checkNow(); break; } } void MacdAlert::factory(int minutesPerBar, bool up, bool histogram) { std::string message; message = ntoa(minutesPerBar); message += " minute MACD crossed "; message += up?"above":"below"; message += ' '; message += histogram?"signal":"zero"; message += " line"; std::string name; name = "Macd"; name += up?"Above":"Below"; name += histogram?"Signal":"Zero"; name += '-'; name += ntoa(minutesPerBar); GenericDataNodeFactory::sf< MacdAlert > (name, symbolPlaceholderObject, minutesPerBar, up, histogram, message); } void MacdAlert::factory(int minutesPerBar) { MacdAlert::factory(minutesPerBar, true, true); MacdAlert::factory(minutesPerBar, true, false); MacdAlert::factory(minutesPerBar, false, true); MacdAlert::factory(minutesPerBar, false, false); } //////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////// void initializeMACD() { MacdAlert::factory(5); MacdAlert::factory(10); MacdAlert::factory(15); MacdAlert::factory(30); MacdAlert::factory(60); }