#include #include #include "../../shared/MiscSupport.h" #include "../../shared/SimpleLogFile.h" #include "../../shared/GlobalConfigFile.h" #include "../../shared/DatabaseWithRetry.h" #include "../misc_framework/GenericDataNodes.h" #include "../misc_framework/CsvFileDataNodes.h" #include "../data_framework/SynchronizedTimers.h" #include "../data_framework/GenericTosData.h" #include "../data_framework/RSI.h" #include "../data_framework/IntradaySma.h" #include "../data_framework/LinearRegression.h" #include "../data_framework/SimpleMarketData.h" #include "ReportAlertsThread.h" #include "WestonDatabaseMonitor.h" #include "TiqMisc.h" #include "WestonAlerts.h" ///////////////////////////////////////////////////////////////////// // WestonAlert ///////////////////////////////////////////////////////////////////// static bool verboseDebug = false; class WestonAlert : public DataNode { private: std::string _symbol; bool _long; GenericTosDataNode *_tosData; double _previousClose; void onWakeup(int msgId); protected: WestonAlert(DataNodeArgument const &args); friend class DataNode; // This is the preferred way to say which window. This is automatically set // by the WestonAlert contructor with info from the caller. It can be // modified by a data node, but ideally you would not throw it out and start // from scratch, because that would ignore what the caller requested. std::string _category; virtual void onTos() {} void report(PropertyList const &fields, std::string category = ""); std::string const &getSymbol() const { return _symbol; } bool isLong() const { return _long; } TosData const &getLast() const { return _tosData->getLast(); } double getPrevClose() const { return _previousClose; } bool marketHasOpened() const { return getLast().open; } bool moreReportable(double a, double b); double mostReportable(double a, double b); double leastReportable(); double mostReportable(); }; inline double WestonAlert::leastReportable() { if (_long) return -std::numeric_limits< double >::max(); else return std::numeric_limits< double >::max(); } inline double WestonAlert::mostReportable() { return -leastReportable(); } inline bool WestonAlert::moreReportable(double a, double b) { if (_long) return a > b; else return a < b; } inline double WestonAlert::mostReportable(double a, double b) { if (_long) return std::max(a, b); else return std::min(a,b); } void WestonAlert::report(PropertyList const &fields, std::string category) { std::string info = "symbol="; info += urlEncode(_symbol); if (_tosData->getValid() && _tosData->getLast().price) { info += "&last="; info += urlEncode(formatPrice(_tosData->getLast().price)); } if (getPrevClose()) { info += "&prev_close="; info += urlEncode(formatPrice(getPrevClose())); } for (PropertyList::const_iterator it = fields.begin(); it != fields.end(); it++) { info += '&'; info += urlEncode(it->first); info += '='; info += urlEncode(it->second); } if (category == "") category = _category; ReportAlertsThread::getInstance()->reportAlert(category, info); } void WestonAlert::onWakeup(int msgId) { // TOS event. if (_tosData->getValid()) onTos(); } WestonAlert::WestonAlert(DataNodeArgument const &args) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 3); // Symbol, Long, Category _symbol = argList[0].getStringValue(); _long = argList[1].getBooleanValue(); _category = argList[2].getStringValue(); _previousClose = getPreviousClose(_symbol); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, _symbol)); } ///////////////////////////////////////////////////////////////////// // WestonQuickStrike ///////////////////////////////////////////////////////////////////// class WestonQuickStrike : public WestonAlert { private: GenericDataNode *_rsi5; GenericDataNode *_stdDev1; GenericDataNode *_sma1; GenericDataNode *_stdDev5; GenericDataNode *_sma5; GenericDataNode *_stdDev15; GenericDataNode *_sma15; double _20DaySma; double _priceToBeat; bool _marketHasOpened; bool _exclude; time_t _nextDebug; void dumpDebug(); WestonQuickStrike(DataNodeArgument const &args); friend class DataNode; protected: void onTos(); void onBroadcast(BroadcastMessage &message, int msgId); public: static DataNodeLink *find(std::string const &symbol, bool up, std::string const &category) { WestonQuickStrike *node; return findHelper(NULL, 0, node, argList(symbol, up, category)); } }; void WestonQuickStrike::dumpDebug() { if (!verboseDebug) return; const time_t now = time(NULL); if (now < _nextDebug) return; _nextDebug = now + 60; TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<"symbol"<getDouble(valid, value); if (valid) msg<<"rsi5"<getDouble(valid, value); if (valid) msg<<"stdDev1"<getDouble(valid, value); if (valid) msg<<"sma1"<getDouble(valid, value); if (valid) msg<<"stdDev5"<getDouble(valid, value); if (valid) msg<<"sma5"<getDouble(valid, value); if (valid) msg<<"stdDev15"<getDouble(valid, value); if (valid) msg<<"sma15"<(message); const bool exclude = list.quickstrikeList.count(getSymbol()); if (exclude != _exclude) { _exclude = exclude; TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<"QuickStrike"; if (exclude) msg<<"exclude"; else msg<<"include"; msg<::max(); DataNodeArgument factory; factory = createRsiFactory(5, 14, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_rsi5)); factory = LinearRegressionBase::stdDevFactory(1, 20, false, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_stdDev1)); factory = createIntradaySmaFactory(1, 20, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_sma1)); factory = LinearRegressionBase::stdDevFactory(5, 20, false, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_stdDev5)); factory = createIntradaySmaFactory(5, 20, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_sma5)); factory = LinearRegressionBase::stdDevFactory(15, 20, false, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_stdDev15)); factory = createIntradaySmaFactory(15, 20, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_sma15)); _20DaySma = strtodDefault(FileOwnerDataNode::getStringValue ("TC_OvernightData.csv", "20 Day SMA", getSymbol()), 0.0); registerForBroadcast(WestonDatabaseMonitor::getExcludeListChannel(), 0); } void WestonQuickStrike::onTos() { dumpDebug(); PropertyList fields; std::vector< std::string > success; const double last = getLast().price; // Reset the highs and lows at the open. if (_marketHasOpened != marketHasOpened()) { _priceToBeat = mostReportable(); _marketHasOpened = marketHasOpened(); } if (_exclude) // Manual setting from outside of this program. return; if (isLong()) { // For longs the trigger is a new low. if (last >= _priceToBeat) return; } else { // For shorts the trigger is a new high. if (last <= _priceToBeat) return; } if (isLong()) { // stock must be down 5% on the day (from previous day’s close) if (last >= getPrevClose() * 0.95) return; } else { if (last <= getPrevClose() * 1.05) return; } double sma1, stdDev1; bool sma1_valid, stdDev1_valid; _sma1->getDouble(sma1_valid, sma1); _stdDev1->getDouble(stdDev1_valid, stdDev1); // at least 50 cents from the 20 period SMA on the 1 minute chart // This is called "divergence" in the requirements. if (!sma1_valid) return; if (!((isLong() && (last < sma1 - 0.50)) || ((!isLong()) && (last > sma1 + 0.50)))) return; if (sma1_valid && stdDev1_valid) { fields["sma1"] = ntoa(sma1); fields["stdDev1"] = ntoa(stdDev1); bool penetrating; if (isLong()) { // stock must be penetrating the 1 min lower Bollinger Band penetrating = last < sma1 - 2*stdDev1; } else { penetrating = last > sma1 + 2*stdDev1; } //if (penetrating) //success.push_back("penetrating 1 min BB"); if (!penetrating) return; } double sma5, stdDev5; bool sma5_valid, stdDev5_valid; _sma5->getDouble(sma5_valid, sma5); _stdDev5->getDouble(stdDev5_valid, stdDev5); if (sma5_valid && stdDev5_valid) { fields["sma5"] = ntoa(sma5); fields["stdDev5"] = ntoa(stdDev5); bool penetrating; if (isLong()) { // stock must be penetrating the 5 min lower Bollinger Band penetrating = last < sma5 - 2*stdDev5; } else { penetrating = last > sma5 + 2*stdDev5; } if (penetrating) success.push_back("penetrating 5 min BB"); } double sma15, stdDev15; bool sma15_valid, stdDev15_valid; _sma15->getDouble(sma15_valid, sma15); _stdDev15->getDouble(stdDev15_valid, stdDev15); if (false && sma15_valid && stdDev15_valid) { // This is disabled as per Brad's request from the morning of 1/26/2010 fields["sma15"] = ntoa(sma15); fields["stdDev15"] = ntoa(stdDev15); bool penetrating; if (isLong()) { // stock must be penetrating the 15 min lower Bollinger Band penetrating = last < sma15 - 2*stdDev15; } else { penetrating = last > sma15 + 2*stdDev15; } if (penetrating) success.push_back("penetrating 15 min BB"); } double rsi5; bool rsi5_valid; _rsi5->getDouble(rsi5_valid, rsi5); if (rsi5_valid) { fields["rsi5"] = ntoa(rsi5); if (isLong()) { // RSI on the 5 minute chart is below 30 for stocks moving down if (rsi5 < 30) success.push_back("RSI oversold"); } else { // RSI on the 5 minute chart is above 75 for stocks moving up if (rsi5 > 75) success.push_back("RSI overbought"); } } if (_20DaySma) { if (isLong()) { // confirm trend on the daily chart, stock must be below the 20 day // SMA (for long) if (last < _20DaySma) success.push_back("below 20 day SMA"); } else { if (last > _20DaySma) success.push_back("above 20 day SMA"); } } if (success.size() >= 1) { std::string join; for (std::vector< std::string >::const_iterator it = success.begin(); it != success.end(); it++) { if (!join.empty()) join += ", "; join += *it; } fields["reasons"] = join; if (sma1_valid) // This defination comes from Brad, but it does not match the picture. fields["sma_divergence"] = formatPrice(last - sma1); fields["time"] = formatTime(time(NULL)); report(fields); _priceToBeat = last; } } ///////////////////////////////////////////////////////////////////// // WestonBollingerBands ///////////////////////////////////////////////////////////////////// class WestonBollingerBands : public WestonAlert { private: GenericDataNode *_stdDev1; GenericDataNode *_sma1; PropertyList _fields; double _lastReported; bool _exclude; bool currentlyOn() const { return _lastReported; } void reportOn(double distFromBB); void reportOff(); void getState(bool &on, double &distFromBB); WestonBollingerBands(DataNodeArgument const &args); friend class DataNode; protected: void onTos(); void onBroadcast(BroadcastMessage &message, int msgId); public: static DataNodeLink *find(std::string const &symbol, bool up, std::string const &category) { WestonBollingerBands *node; return findHelper(NULL, 0, node, argList(symbol, up, category)); } }; void WestonBollingerBands::onBroadcast(BroadcastMessage &message, int msgId) { WestonExcludeList const &list = dynamic_cast< WestonExcludeList const & >(message); const bool exclude = list.bollingerList.count(getSymbol()); if (exclude != _exclude) { _exclude = exclude; TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<"Bollinger"; if (exclude) msg<<"exclude"; else msg<<"include"; msg<().find(_stdDev1)); factory = createIntradaySmaFactory(1, 20, false).replace(symbolPlaceholder, getSymbol()); addAutoLink(factory.getValue< GenericDataNodeFactory >().find(_sma1)); registerForBroadcast(WestonDatabaseMonitor::getExcludeListChannel(), 0); const bool nasdaq = getListedExchange(getSymbol()) == "NASD"; _category += nasdaq?"_nasdaq":"_nyse"; } void WestonBollingerBands::getState(bool &on, double &distFromBB) { on = false; // The common case. distFromBB = 0.0; // We don't promise what this value will be unless on is true. const double last = getLast().price; if (isLong()) { // stock must be up on the day if (last <= getPrevClose()) return; } else { if (last >= getPrevClose()) return; } bool valid; double sma1; _sma1->getDouble(valid, sma1); if (!valid) return; double stdDev1; _stdDev1->getDouble(valid, stdDev1); if (!valid) return; if (isLong()) { // stock must be penetrating LOWER BB (ON A 1 MINUTE CHART) by .05 cents const double lower_bb = sma1 - 2*stdDev1; const double bb_penetration = lower_bb - last; if (bb_penetration < 0.02) // Might return to 0.03 return; if (sma1 - last < 0.15) // Might return to 0.20 return; on = true; // This should be negative, to match the picture. distFromBB = -bb_penetration; _fields["sma1"] = formatPrice(sma1); _fields["stdDev1"] = formatPrice(stdDev1); _fields["lower_bb"] = formatPrice(lower_bb); } else { const double upper_bb = sma1 + 2*stdDev1; const double bb_penetration = last - upper_bb; if (bb_penetration < 0.02) // Was 0.05, then 0.03, may return to 0.03. return; if (last - sma1 < 0.15) // Was originally 0.30, and then 0.20, may go back to 20 return; on = true; // This should be positive to match the picture. distFromBB = bb_penetration; _fields["sma1"] = formatPrice(sma1); _fields["stdDev1"] = formatPrice(stdDev1); _fields["upper_bb"] = formatPrice(upper_bb); } } void WestonBollingerBands::reportOn(double distFromBB) { const double price = getLast().price; if (_lastReported != price) { // Report every time the price changes. _lastReported = price; _fields["action"] = "add"; _fields["dist_from_bb"] = formatPrice(distFromBB); report(_fields); } } void WestonBollingerBands::reportOff() { if (currentlyOn()) { _lastReported = 0.0; _fields["action"] = "delete"; report(_fields); } } void WestonBollingerBands::onTos() { if (_exclude) // Manual setting from outside of this program. return; _fields.clear(); bool on; double distFromBB; getState(on, distFromBB); if (on) reportOn(distFromBB); else reportOff(); } ///////////////////////////////////////////////////////////////////// // WestonBreakHiLo ///////////////////////////////////////////////////////////////////// class WestonBreakHiLo : public WestonAlert { private: double _lastHighLow; void onValidHighLow(double value); WestonBreakHiLo(DataNodeArgument const &args); friend class DataNode; protected: void onTos(); public: static DataNodeLink *find(std::string const &symbol, bool up, std::string const &category) { WestonBreakHiLo *node; return findHelper(NULL, 0, node, argList(symbol, up, category)); } }; void WestonBreakHiLo::onTos() { TosData const &last = getLast(); if (last.high && last.low) { // If the normal highs and lows are available, use them exclusively. const double price = isLong()?last.high:last.low; if (price > 0) onValidHighLow(price); } else { // Try to compute the premarket highs and lows ourselves. if (!last.newPrint) // Don't make an alert just because we connected to the datafeed. return; const double price = last.price; if (price <= 0) return; if (moreReportable(price, _lastHighLow)) // Note: With the normal highs and lows we can recover from a bad // print. If the high drops and then goes up again, we'll report it // again on the up swing. For premarket we have no way to know about // a bad print. If the price goes down, we just say it's not a high. onValidHighLow(price); } } void WestonBreakHiLo::onValidHighLow(double price) { if (moreReportable(price, _lastHighLow)) { double cutoff = isLong()?1.05:0.95; if (moreReportable(price, getPrevClose() * cutoff)) report(PropertyList()); else { cutoff = isLong()?1.04:0.96; if (moreReportable(price, getPrevClose() * cutoff)) report(PropertyList(), _category + "_4"); else { cutoff = isLong()?1.03:0.97; if (moreReportable(price, getPrevClose() * cutoff)) report(PropertyList(), _category + "_3"); } } } _lastHighLow = price; } WestonBreakHiLo::WestonBreakHiLo(DataNodeArgument const &args) : WestonAlert(args), _lastHighLow(leastReportable()) { } ///////////////////////////////////////////////////////////////////// // WestonBreakHiLo345 // // This is a variation on the WestonBreakHiLo that was created at // the request of lee futes leefutes@aol.com // // This is just like the other breakhi / breaklo, but... // 1) The current volume today is 100,000. // 2) The minimum price is $10. // 3) One of the alert (pairs) displays stocks between 5% and 6% up // (or down) vs. the standard version which looks for 5% or more. ///////////////////////////////////////////////////////////////////// class WestonBreakHiLo345 : public WestonAlert { private: double _lastHighLow; void onValidHighLow(double value, Integer volume); WestonBreakHiLo345(DataNodeArgument const &args); friend class DataNode; protected: void onTos(); public: static DataNodeLink *find(std::string const &symbol, bool up, std::string const &category) { WestonBreakHiLo345 *node; return findHelper(NULL, 0, node, argList(symbol, up, category)); } }; void WestonBreakHiLo345::onTos() { TosData const &last = getLast(); if (last.high && last.low) { // If the normal highs and lows are available, use them exclusively. const double price = isLong()?last.high:last.low; if (price > 0) onValidHighLow(price, last.volume); } else { // Try to compute the premarket highs and lows ourselves. if (!last.newPrint) // Don't make an alert just because we connected to the datafeed. return; const double price = last.price; if (price <= 0) return; if (moreReportable(price, _lastHighLow)) // Note: With the normal highs and lows we can recover from a bad // print. If the high drops and then goes up again, we'll report it // again on the up swing. For premarket we have no way to know about // a bad print. If the price goes down, we just say it's not a high. onValidHighLow(price, last.volume); } } void WestonBreakHiLo345::onValidHighLow(double price, Integer volume) { if ((volume > 100000) && moreReportable(price, _lastHighLow)) { double cutoff = isLong()?1.06:0.94; if (moreReportable(price, getPrevClose() * cutoff)) { // Do nothing! } else { cutoff = isLong()?1.05:0.95; if (moreReportable(price, getPrevClose() * cutoff)) // Between 5 and 6 report(PropertyList(), _category + "_5a"); else { cutoff = isLong()?1.04:0.96; if (moreReportable(price, getPrevClose() * cutoff)) // Between 4 and 5 report(PropertyList(), _category + "_4a"); else { cutoff = isLong()?1.03:0.97; if (moreReportable(price, getPrevClose() * cutoff)) // Between 3 and 4 report(PropertyList(), _category + "_3a"); } } } } _lastHighLow = price; } WestonBreakHiLo345::WestonBreakHiLo345(DataNodeArgument const &args) : WestonAlert(args), _lastHighLow(leastReportable()) { } ///////////////////////////////////////////////////////////////////// // WestonVolumeBar // // Requirements: // Weston – volume bar for single guy Brad is POC. // Explanation: After the first 5 minutes of trading, benchmark the 5 minute // volume bar. When that volume bar is exceeded (mid candle), trigger an // alert in a window that just contains SYMBOL.(like break high). The next // subsequent alert will be triggered when the new high volume bar is exceeded. // will you also add to the criteria that it be a minimum of a 100k share 5 // min bar. // // We use the BarCounter to tell us when the time changes for each bar. We // chose that for several reasons, including that fact that it should work // during a debug playback. // // The candles that are normally associated with a BarCounter do not tell you // much about the bar in progress. So we keep track of the volume ourselves. // This is based on the way ShortTermCandles works, although much simpler. // It's not clear which of those candle models handles volume more accurately. ///////////////////////////////////////////////////////////////////// class WestonVolumeBar : public DataNode { private: const std::string _symbol; GenericTosDataNode *_tosData; BarCounter *_barCounter; Integer _previousEndingVolume; Integer _reportPoint; bool _canReportThisPeriod; enum { wTos, wNewBar }; void onWakeup(int msgId); void onTos(); void onNewBar(); WestonVolumeBar(DataNodeArgument const &args); friend class DataNode; public: static DataNodeLink *find(std::string const &symbol) { WestonVolumeBar *node; return findHelper(NULL, 0, node, symbol); } }; WestonVolumeBar::WestonVolumeBar(DataNodeArgument const &args) : _symbol(args.getStringValue()), _previousEndingVolume(-1), _reportPoint(99999), _canReportThisPeriod(false) { addAutoLink(BarCounter::find(this, wNewBar, _barCounter, 5)); addAutoLink(GenericTosDataNode::find(this, wTos, _tosData, _symbol)); } void WestonVolumeBar::onTos() { if (!_canReportThisPeriod) return; const Integer currentVolume = _tosData->getValid()?_tosData->getLast().volume:-1; if ((_previousEndingVolume >= 0) && (currentVolume >= 0)) { const Integer candleVolume = currentVolume - _previousEndingVolume; if (candleVolume > _reportPoint) { // Report the alert static const std::string category = "WESTON-volume_bar"; std::string info = "symbol="; info += urlEncode(_symbol); info += "&w_id=9¤t_volume="; // The rest is only used for debugging. info += ntoa(currentVolume); info += "&candle_volume="; info += ntoa(candleVolume); info += "&report_point="; info += ntoa(_reportPoint); ReportAlertsThread::getInstance()->reportAlert(category, info); // Report at most once per period. _canReportThisPeriod = false; } else // In case of a correction volume could go backwards. _previousEndingVolume = std::min(_previousEndingVolume, currentVolume); } } void WestonVolumeBar::onNewBar() { if (_barCounter->getTimePhase() != BarCounter::tpNotify) return; const Integer currentVolume = _tosData->getValid()?_tosData->getLast().volume:-1; if ((_previousEndingVolume >= 0) && (currentVolume >= 0)) { const Integer candleVolume = currentVolume - _previousEndingVolume; _reportPoint = std::max(_reportPoint, candleVolume); } _previousEndingVolume = currentVolume; switch (_barCounter->getLastTransition()) { case BarCounter::bctFirst: // The requirements specifically say that we don't report on the first // bar. Since we have a built in minimum, there's no other reason why we // couldn't. _canReportThisPeriod = false; break; case BarCounter::bctNext: // Finished a normal bar and starting another. _canReportThisPeriod = true; break; case BarCounter::bctEnd: // Finished the normal bars, now it's after hours. _canReportThisPeriod = false; break; default: // Unknown state. This shouldn't happen. Just to be safe: _canReportThisPeriod = false; } //TclList msg; //msg<<__FILE__<<__LINE__<<__FUNCTION__ // <<_symbol<<_previousEndingVolume; //if (_canReportThisPeriod) // msg<<"can report"; //else // msg<<"CAN'T REPORT"; //msg<<_reportPoint; //sendToLogFile(msg); } void WestonVolumeBar::onWakeup(int msgId) { switch (msgId) { case wTos: onTos(); break; case wNewBar: onNewBar(); break; } } ///////////////////////////////////////////////////////////////////// // KoyoteBreakPostHiLo ///////////////////////////////////////////////////////////////////// class KoyoteBreakPostHiLo : public WestonAlert { private: static std::string _date; static DatabaseWithRetry *_candleDatabase; // The first session is durning the premarket. We are comparing current // prices to yesterday's post market prices. // The second session is durning the normal and post market. We are // comparing current prices to the range that includes yesterday's post // market and today's pre market. We can signal one alert durning each // session. bool _firstDone; bool _secondDone; double _firstPriceToBeat; double _secondPriceToBeat; KoyoteBreakPostHiLo(DataNodeArgument const &args); friend class DataNode; protected: void onTos(); public: static DataNodeLink *find(std::string const &symbol, bool up, std::string const &category) { KoyoteBreakPostHiLo *node; return findHelper(NULL, 0, node, argList(symbol, up, category)); } }; std::string KoyoteBreakPostHiLo::_date = ""; DatabaseWithRetry *KoyoteBreakPostHiLo::_candleDatabase = NULL; KoyoteBreakPostHiLo::KoyoteBreakPostHiLo(DataNodeArgument const &args) : WestonAlert(args), _firstDone(false), _secondDone(false), _firstPriceToBeat(mostReportable()), _secondPriceToBeat(_firstPriceToBeat) { if (!_candleDatabase) { // Do some one time initialization. _candleDatabase = new DatabaseWithRetry("@candles", "candles"); _date = _candleDatabase->tryQueryUntilSuccess ("SELECT DATE(MAX(start_time)) FROM bars_pre_post", "last candle date") ->getStringField(0); sendToLogFile(TclList()<<"_date"<<_date); } if (_date == "") { // No data in the database. _firstDone = true; _secondDone = true; return; } std::string sql; if (isLong()) sql = "SELECT ROUND(MAX(high),4) FROM bars_pre_post WHERE start_time > '"; else sql = "SELECT ROUND(MIN(low),4) FROM bars_pre_post WHERE start_time > '"; sql += _date; sql += "' AND symbol = '"; sql += mysqlEscapeString(getSymbol()); sql += "'"; _firstPriceToBeat = _candleDatabase->tryQueryUntilSuccess(sql, "post high low") ->getDoubleField(0, mostReportable()); _secondPriceToBeat = _firstPriceToBeat; TclList msg; msg< cleanup; static void alertsForSymbol(std::string const &symbol, std::string const &suffix) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__<<"weston"<