/* This comes from "The Five Minute Trader" by Oliver Velez. Oliver says we * can use this, as long as we credit him. * * Start with the definition of a "power bar". The range of the bar has to be * big. He's not any more specific than that. We're basing this on his * definition of a wide range bar, where we compare the range of this candle to * the average range of the last several bars. * * The close of the bar must be close to the high or the low. This sets the * direction of the trade. Close next to highs is bullish. Close next to lows * is bearish. He's not specific on how close. A lot of pictures show it * equal. Others show a small tail, maybe 5%, but really just the smallest * thing that can easily be drawn in the various pictures. * * The open must also be close to the high or the low. It doesn't matter * which. The bar might be almost completely full, which he calls an * "elephant". Or it might look like a hammer or an upsidedown hammer, which * he calls "tails". If the open and close are both on the same side of the * candle, it doesn't matter which is higher, or if they are equal. His * graphics make a big deal of that point. * * Setup: * 1) First 5 minute bar of the market (SPY and/or QQQQ) must be a power bar. * 2) First 5 minute bar of the stock must be a power bar going in the same * direction as the market. * * Action: * 1) If we're bullish, buy "1 tick" above the high of the first 5 minute bar, * whenever that occurs. * 2) Place a stop "1 tick" below the low of te first 5 minute bar. * We're not doing anything with the rest, but here it it. * 3) Move stop to breakeven after a two bar rally. * 4) Take profits on any multi-bar rally; * 5) Hold 1/2 with a boom or bust approach. * * He is not clear on what a "tick" is. The pictures suggest that he means as * soon as the price crosses above or below price. * * There is a very brief reference to a one minute setup, but otherwise he only * describes this for 5 minutes. */ #include "../../shared/SimpleLogFile.h" #include "../../shared/GlobalConfigFile.h" #include "../alert_framework/AlertBase.h" #include "../data_framework/TradeDirection.h" #include "../data_framework/GenericTosData.h" #include "../data_framework/StandardCandles.h" #include "../../shared/MarketHours.h" #include "OliverVelezOpen.h" static const std::string sQQQQ = "QQQ"; static const std::string sSPY = "SPY"; //////////////////////////////////////////////////////////////////// // OVPowerBarWatcher // // This implements the defintion of a power bar. We need this to be // a shared item because a whole lot of alerts will be watching SPY // and QQQQ. //////////////////////////////////////////////////////////////////// static bool verboseDebug; class OVPowerBarWatcher : public DataNode { public: enum Status { sWaiting, sNone, sBullish, sBearish }; private: StandardCandles *_candleData; Status _current; Status _lastReported; // Record this so we know when to report a new event. StandardCandles::Epoch _lastEpoch; // So we know if the candles changed. void updateStatus(); void onWakeup(int msgId); OVPowerBarWatcher(DataNodeArgument const &args); friend class DataNode; public: Status getStatus() { updateStatus(); return _current; } static DataNodeLink *find(DataNodeListener *listener, int msgId, OVPowerBarWatcher *&node, std::string const &symbol, int minutesPerBar = 5) { return findHelper(listener, msgId, node, argList(symbol, minutesPerBar)); } }; OVPowerBarWatcher::OVPowerBarWatcher(DataNodeArgument const &args) : _current(sWaiting), _lastReported(sWaiting), _lastEpoch(0) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, Minutes std::string const &symbol = argList[0].getStringValue(); const int minutes = argList[1].getIntValue(); addAutoLink(StandardCandles::find(this, 0, _candleData, symbol, minutes)); //_doReport = (symbol == sSPY) || (symbol == sQQQQ); } void OVPowerBarWatcher::onWakeup(int msgId) { updateStatus(); if (_lastReported != _current) { notifyListeners(); _lastReported = _current; } } void OVPowerBarWatcher::updateStatus() { if (_current != sWaiting) // For simplicity we assume that we are restarted each night. We don't // worry about replacing the previous value each day. Once we've found the // value we quit looking. return; if (_lastEpoch == _candleData->getEpoch()) // Nothing has changed. return; if (!_candleData->isActive()) // Presumably this is before the open. (It could be after the close, in // which case it would still be okay.) Mostly I'm worried about the first // call to check the status, which might come before the first candle // of the morning. return; if (!_candleData->historicalCandleCount()) // getLastCandleStartTime() has an undefined result if we don't check this // first. return; if (secondOfTheDay(_candleData->getCurrentCandleStartTime()) != MARKET_HOURS_OPEN + _candleData->getMinutesPerBar() * MARKET_HOURS_MINUTE) { // The first candle we see after the market opens should start at the // open. Otherwise, the stock did not have any data and the first candle // was missing. _current = sNone; if (verboseDebug) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<_candleData->getSymbol(); msg<<"sNone, not enough data"; sendToLogFile(msg); } return; } // The first candle of the day just finished! if (_candleData->historicalCandleCount() < 21) { // We need 20 candles of history to compare to the current candle. _current = sNone; return; if (verboseDebug) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<_candleData->getSymbol(); msg<<"sNone, not enough data"; sendToLogFile(msg); } } StandardCandles::CandleArray const &candles = _candleData->getHistory(); const int lastIndex = candles.size() - 1; double averageHeight = 0.0; for (int i = lastIndex - 8; i < lastIndex; i++) { StandardCandles::SingleCandle const ¤t = candles[i]; averageHeight += current.high - current.low; } // We look at the previous 8 bars to get the average height. This is // not documented anywhere. But Oliver mentioned it in a seminar on // 5/5/2010. We were using 20, based on the definition of a wide // range bar, also from Oliver. averageHeight /= 8; StandardCandles::SingleCandle const &lastCandle = candles[lastIndex]; const double lastCandleHeight = lastCandle.high - lastCandle.low; if (lastCandleHeight <= averageHeight * 1.75) { // This candle is not big enough to be a "power bar". Brad just made // this number up. The documents don't say how big is big. A wide range // bar is similar, but it has to be 2x as big, not 1.75x. _current = sNone; if (verboseDebug) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<_candleData->getSymbol(); msg<<"sNone, candle too small"; sendToLogFile(msg); } return; } // Brad suggested that near the top and near the bottom mean within 10% - // 15%. But some of Oliver's real world examples did not match that. One // was right around 19% and another was just a hair over 25%! const double nearTop = lastCandleHeight * 0.75 + lastCandle.low; const double nearBottom = lastCandleHeight * 0.25 + lastCandle.low; if ((lastCandle.open > nearBottom) && (lastCandle.open < nearTop)) { // Open was somewhere in the middle. _current = sNone; if (verboseDebug) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<_candleData->getSymbol(); msg<<"sNone found on open"; sendToLogFile(msg); } return; } if (lastCandle.close > nearTop) _current = sBullish; else if (lastCandle.close < nearBottom) _current = sBearish; else _current = sNone; if (verboseDebug) { TclList msg; msg<<__FILE__<<__LINE__<<__FUNCTION__ <<_candleData->getSymbol(); switch (_current) { case sBearish: msg<<"sBearish"; break; case sBullish: msg<<"sBullish"; break; case sNone: msg<<"sNone found on close"; break; default: // Avoid a compiler warning. msg<<"huh?"; break; } msg<<"open"<getStatus(); switch (powerBarStatus) { case OVPowerBarWatcher::sWaiting: return; case OVPowerBarWatcher::sNone: _state = sDone; return; case OVPowerBarWatcher::sBullish: if (_tradeDirection.isShort()) { _state = sDone; return; } break; case OVPowerBarWatcher::sBearish: if (_tradeDirection.isLong()) { _state = sDone; return; } break; } if ((powerBarStatus != _qqqqPowerBar->getStatus()) && (powerBarStatus != _spyPowerBar->getStatus())) { // This stock does not match the market as a whole. _state = sDone; return; } _state = sWaitingForCross; StandardCandles::SingleCandle const &last = _candleData->getHistory()[_candleData->historicalCandleCount() - 1]; const double oneTick = (last.high > 1.0)?0.01:0.0001; if (_tradeDirection.isLong()) { _toCross = last.high + oneTick/2; // Avoid roundoff error. _stopLoss = last.low - oneTick; // This will be rounded when we print it } else { _toCross = last.low - oneTick/2; _stopLoss = last.high + oneTick; } } void OVOpenAlert::onTos() { if (!_tosData->getValid()) return; if (!_tradeDirection.moreReportable(_tosData->getLast().price, _toCross)) return; report("Enter with stop loss at $" + formatPrice(_stopLoss)); _state = sDone; } OVOpenAlert::OVOpenAlert(DataNodeArgument const &args) : _state(sWaitingForPowerBar) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 3); // Symbol, Long, Minutes std::string const &symbol = argList[0].getStringValue(); _tradeDirection = argList[1].getBooleanValue(); const int minutes = argList[2].getIntValue(); addAutoLink(OVPowerBarWatcher::find(NULL, 0, _qqqqPowerBar, sQQQQ, minutes)); addAutoLink(OVPowerBarWatcher::find(NULL, 0, _spyPowerBar, sSPY, minutes)); addAutoLink(OVPowerBarWatcher::find(this, wPowerBar, _selfPowerBar, symbol, minutes)); addAutoLink(GenericTosDataNode::find(this, wTos, _tosData, symbol)); addAutoLink(StandardCandles::find(NULL, 0, _candleData, symbol, minutes)); } //////////////////////////////////////////////////////////////////// // Global //////////////////////////////////////////////////////////////////// void initializeOliverVelezOpen() { GenericDataNodeFactory::sf< OVOpenAlert >("OliverVelezOpen5Up", symbolPlaceholderObject, true, 5); GenericDataNodeFactory::sf< OVOpenAlert >("OliverVelezOpen5Down", symbolPlaceholderObject, false, 5); verboseDebug = getConfigItem("verbose_power_bar") == "1"; }