#include #include "../../shared/SimpleLogFile.h" #include "../../shared/ReplyToClient.h" #include "../misc_framework/CsvFileDataNodes.h" #include "../misc_framework/DebugMessage.h" #include "SynchronizedTimers.h" #include "UseHistory.h" #include "StandardCandles.h" //////////////////////////////////////////////////////////////////// // StandardCandles::SingleCandle //////////////////////////////////////////////////////////////////// bool StandardCandles::SingleCandle::operator ==(SingleCandle const &other) const { return (open == other.open) && (high == other.high) && (low == other.low) && (close == other.close) && (volume == other.volume); } bool StandardCandles::SingleCandle::operator !=(SingleCandle const &other) const { return !(*this == other); } //////////////////////////////////////////////////////////////////// // StandardCandles //////////////////////////////////////////////////////////////////// bool StandardCandles::isActive() const { return _forceActive || (_barCounter->getCurrentBar() != BarCounter::UNKNOWN_TIME); } bool StandardCandles::isUpdating() const { return _barCounter->isUpdating(); } void StandardCandles::onTosData() { //TclList debug; // Ignore missing data. Assume it is small, and the result without the data // is close enough. We occasionally go down on a broken TCP/IP socket, and // come back up again very quickly. If we are down for a long time, // OnBarData will detect it and act accordingly. if (_barCounter->getCurrentBar() == BarCounter::UNKNOWN_TIME) { //debug<<__FILE__<<__FUNCTION__<<__LINE__ // <<"_barCounter->getCurrentBar() == BarCounter::UNKNOWN_TIME"; //sendToLogFile(debug); return; } if (!_tosData->getValid()) { //debug<<__FILE__<<__FUNCTION__<<__LINE__ // <<"!_tosData->getValid()"; //sendToLogFile(debug); return; } if (_tosData->getLast().time < _barCounter->getIgnoreIfBefore()) { // This happens sometimes, but not a lot. Usually several in a row, // right after the new candle starts, off by just a second. //debug<<__FILE__<<__FUNCTION__<<__LINE__ // <<"_tosData->getLast().time < _barCounter->getIgnoreIfBefore()" // <getLast().time) // <getIgnoreIfBefore()); //sendToLogFile(debug); return; } if (!_tosData->getLast().updatesLast) // We've seen specific examples where this is necessary. Espeically // obvious in the SMB price spike. There were no other indications of // a problem. The timestamp was the current time and there were no // corrections later in the day. return; //debug<<__FILE__<<__FUNCTION__<<__LINE__ // <<"we all good"; //sendToLogFile(debug); double lastPrice = _tosData->getLast().price; if (_currentCandleValid) { // Update existing candle. _currentCandle.high = std::max(_currentCandle.high, lastPrice); _currentCandle.low = std::min(_currentCandle.low, lastPrice); _currentCandle.close = lastPrice; _currentCandle.volume += _tosData->getLast().size; } else { // First print of the candle. _currentCandle.high = lastPrice; _currentCandle.low = lastPrice; _currentCandle.open = lastPrice; _currentCandle.close = lastPrice; _currentCandle.volume = _tosData->getLast().size; _currentCandleValid = true; } } void StandardCandles::onBarData() { //TclList debug; //debug<<__FILE__<<__FUNCTION__<<__LINE__ // <<"getTimePhase()"<<_barCounter->getTimePhase() // <<"getLastTransition()"<<_barCounter->getLastTransition() // <<"_currentCandleValid"<<_currentCandleValid; //sendToLogFile(debug); if (_barCounter->getTimePhase() == BarCounter::tpUpdate) { switch (_barCounter->getLastTransition()) { case BarCounter::bctNone: case BarCounter::bctSkip: // Skip suggests a data error; we were down for a long period of // time. For none this is redundant. reset(); break; case BarCounter::bctFirst: // A fresh start for the data. We know we're not in the middle of a // candle. _currentCandleValid = false; break; case BarCounter::bctNext: case BarCounter::bctEnd: if (_currentCandleValid) { // Add the candle we just finished to the list. addCurrentCandle(); } else if (_strict) { // If we have a stock with a candle with no data, we erase // everything and restart after the missing candle. This case // seems to be ignored by most books, so we report nothing. This // is consistant with the way we process historical candle data. reset(); } else { // Ignore the event. What people do in the case of a missing // candle varies from one trading platform to the next. And it's // not discussed at all in books. By default (i.e. when // _strict=true) we do a reset. If someone specifically // requests, then we will try to carry on. We chose to do // nothing because that seems like the simplist thing to do. The // only complaint has been that certain stocks are ignored after // we force a reset, and this should be good enough to avoid // that. //TclList msg; //msg<<__FILE__<<__LINE__<<__FUNCTION__ // <<"ignoring missing bar" // <<_tosData->symbol()<300,000 volume. } } } else if (_barCounter->getTimePhase() == BarCounter::tpNotify) notifyIfRequired(); } void StandardCandles::onWakeup(int msgId) { switch (msgId) { case wTosData: onTosData(); break; case wBarData: onBarData(); break; } } // This provide a convienent way to quickly manipulate the value stored // in this datanode. Listeners will be notified of any changes in an // appropriate way, as if the data was real. When you manipulate a // datanode this way, and you send real TOS data (even TOS data for a // different data node) there are no gaurentees made about the results. // Clearly this is only for development, for testing the complicated // formulas which use this data. void StandardCandles::onBroadcast(BroadcastMessage &message, int msgId) { DebugMessage const &m = dynamic_cast< DebugMessage const & >(message); PropertyList const &p = m.getProperties(); std::string minutesPerBarString = ntoa(getMinutesPerBar()); if (getPropertyDefault(p, "minutes_per_bar") != minutesPerBarString) return; const std::string action = getPropertyDefault(p, "action"); if (action == "clear") { // No data. reset(); notifyIfRequired(); addToOutputQueue(m.getSocketInfo(), "Clear.", m.getResponseMessageId()); } else if (action == "set") { // Load the normal data from the normal file, like we do in the morning. getHistoricalCandles(getPropertyDefault(p, "symbol"), minutesPerBarString, _history); _lastTransition = ltReset; _epoch++; std::string msg = "Setting to "; msg += ntoa(_history.size()); msg += " candles all at once."; addToOutputQueue(m.getSocketInfo(), msg, m.getResponseMessageId()); notifyListeners(); _doNotifySoon = false; } else if (action == "add") { // Load candles from the data file that we normally use to initialize // ourselves in the morning. But load them one at a time to simulate // live data. CandleArray testData; getHistoricalCandles(getPropertyDefault(p, "symbol"), minutesPerBarString, testData); std::string msg = "Sending "; msg += ntoa(testData.size()); msg += " individual candles from file."; addToOutputQueue(m.getSocketInfo(), msg, m.getResponseMessageId()); for (CandleArray::const_iterator it = testData.begin(); it != testData.end(); it++) { addCandle(*it); notifyIfRequired(); } addToOutputQueue(m.getSocketInfo(), "Done.", m.getResponseMessageId()); } else if (action == "single") { // Load one or more candles directly from the debug message, rather than // a file. Use the same format as one cell of the CSV file. Use a simi- // colon to seperate individual candles, and a colon to seperate values // within a candle. CandleArray testData; decode(getPropertyDefault(p, "candles"), testData, "manual request"); std::string msg = "Sending "; msg += ntoa(testData.size()); msg += " individual candles from debug message."; addToOutputQueue(m.getSocketInfo(), msg, m.getResponseMessageId()); for (CandleArray::const_iterator it = testData.begin(); it != testData.end(); it++) { addCandle(*it); notifyIfRequired(); } addToOutputQueue(m.getSocketInfo(), "Done.", m.getResponseMessageId()); } else if (action == "dump") { TclList allCandles; for (CandleArray::const_iterator it = _history.begin(); it != _history.end(); it++) { TclList current; current<open<high<low<close<volume; allCandles<getMinutesPerBar(); } static const std::string debugPrefix = "StandardCandles.debug."; StandardCandles::StandardCandles(DataNodeArgument const &args) : _currentCandleValid(false), _doNotifySoon(false), _forceActive(false), _lastTransition(ltNew), // Start the epoch at one. Listeners will, by default, all start from 0. // But we may contain data when we first start. _epoch(1) { DataNodeArgumentVector const &argList = args.getListValue(); assert((argList.size() == 2) || (argList.size() == 3)); // symbol, minutesPerBar, strict std::string const &symbol = argList[0].getStringValue(); const int minutesPerBar = argList[1].getIntValue(); if (argList.size() == 2) _strict = false; else _strict = argList[2].getBooleanValue(); addAutoLink(BarCounter::find(this, wBarData, _barCounter, minutesPerBar)); addAutoLink(GenericTosDataNode::find(this, wTosData, _tosData, symbol)); registerForBroadcast(debugPrefix + symbol, 0); // Ideally we start with the history which was automatically prepaired // for us by another task, then add realtime data. But if we start // late there will be a big hole. In that case it's better to use // no history and just the real time data. if (restoreHistory()) { // Ideally we should look in a different file, or something, depending on // the strict setting. Currently we're testing a new overnight process. // It always generates stuff which is correct for strict = false. // 6/11/2015 getHistoricalCandles(symbol, minutesPerBar, _history); if (!_history.empty()) _lastTransition = ltReset; } } bool StandardCandles::restoreHistory() { return !openHasPassed(); } DataNodeLink *StandardCandles::find(DataNodeListener *listener, int msgId, StandardCandles *&node, std::string const &symbol, int minutesPerBar, bool strict) { return findHelper(listener, msgId, node, argList(symbol, minutesPerBar, strict)); } DataNodeLink *StandardCandles::find(DataNodeListener *listener, int msgId, StandardCandles *&node, DataNodeArgument const &args) { return findHelper(listener, msgId, node, args); } static const double badDouble = std::numeric_limits< double >::min(); static const DataNode::Integer badInt = std::numeric_limits< DataNode::Integer >::min(); bool StandardCandles::decode(std::string const &encoded, SingleCandle &candle) { const std::vector< std::string > currentCandle = explode(":", encoded); if (currentCandle.size() != 5) return false; candle.open = strtodDefault(currentCandle[0], badDouble); candle.high = strtodDefault(currentCandle[1], badDouble); candle.low = strtodDefault(currentCandle[2], badDouble); candle.close = strtodDefault(currentCandle[3], badDouble); candle.volume = strtollDefault(currentCandle[4], badInt); return (candle.open != badDouble) && (candle.high != badDouble) && (candle.low != badDouble) && (candle.close != badDouble) && (candle.volume != badInt); } void StandardCandles::decode(std::string const &encoded, CandleArray &array, std::string const &debug) { array.clear(); if (encoded.empty()) return; const std::vector< std::string > allCandles = explode(";", encoded); for (std::vector< std::string >::const_iterator it = allCandles.begin(); it != allCandles.end(); it++) { SingleCandle current; if (!decode(*it, current)) { // The file is created by us, in another process, so this should // never happen. array.clear(); TclList msg; msg<<"StandardCandles.C" <<"invalid" <<*it; if (!debug.empty()) msg<getIgnoreIfBefore(); } void StandardCandles::registerDebugger(DataNodeManager *manager) { new Debugger("debug_standard_candles", 0, debugPrefix, manager); }