#include #include #include "../../shared/SimpleLogFile.h" #include "../data_framework/GenericTosData.h" #include "../data_framework/SimpleMarketData.h" #include "../alert_framework/AlertBase.h" #include "TrailingStop.h" //////////////////////////////////////////////////////////////////// // TrailingStop // // Each row of history represents a range of prices. The most profitable // price is the first price. The most reportable price is the next place // where we want to create an alert. Creating an alert causes the // next price field to move to the more reportable side, expanding the // range. // // New ranges are always added to the bottom. Index 0 is always the oldest // range. The age of the range depends on the first print that created the // range. // // The range for each row must completely fit inside the range for the row // above it. After handling a new print, the print should be within all of // the ranges. The print must be more reportable than the most profitable // print, and more profitable than the most reportable price. // // If a new range has the same most profitable price as the range above it, // the new range is deleted. If a new range has a most profitable price more // profitable than the row above it, the row above is deleted. Many rows may // be deleted at once. // // If a range has a most reportable point which is more reportable than // ranges above (older than) it, the new range is deleted. If a new print // could cause multiple alerts, we print the alert from the oldest range. // When we extend the range for this row, that will cause us to delete the // other rows which could have reported. // // Following the logic above, you never have to delete a row from the middle. // If a print causes us to delete multiple rows, it will always delete all // rows from a certain point down, all the way to the bottom, possibly // excepting one new row which was created by the same print. We choose to // create rows only after all of the required deletions, to make the // deletions simple. // // Potentially, any print could create a new row. However, most of the time // the new row would immediately be deleted. For effeciency the code never // creates a row if it knows that it will have to immediately delete it. // // The idea is to create a new record when we have gone as far to the // profitable side as we are going, and we are about to switch to the // reportable side. As we move to the more profitable stide, we keep // updating the last record, pushing it in the right direction. Then, // when we start moving in the other direction, the last record stays in // place. Small jitters do not always cause new rows, because they are // too close to the reportable side of the rows above. //////////////////////////////////////////////////////////////////// class TrailingStop : public Alert { private: enum Compare { tsMoreProfitable, tsEqual, tsMoreReportable }; static __thread bool _randInitialized; static __thread drand48_data _randomState; double dRand(); // Down means that we were long, so we report when we go down. bool _down; GenericTosDataNode *_tosData; struct HistoryRec { double initialPrice; time_t initialTime; double nextPrice; }; typedef std::vector< HistoryRec > History; History _history; bool _previouslyValid; void newTosData(); void onWakeup(int msgId); Compare compare(double a, double b); void findLevels(double startingPoint, double currentPoint, std::string &brokenLevelText, double &brokenLevelQuality, double &next); void findFirstLevel(double startingPoint, double &nextLevel, bool &success); protected: TrailingStop(DataNodeArgument const &args); int moreReportableDirection(); virtual double getStepSize(double startingPoint) =0; virtual std::string descriptionOfMove(double priceIncrease, int steps) =0; virtual double qualityFromSteps(int steps) =0; }; __thread bool TrailingStop::_randInitialized = false; __thread drand48_data TrailingStop::_randomState; TrailingStop::TrailingStop(DataNodeArgument const &args) : _previouslyValid(false) { DataNodeArgumentVector const &argList = args.getListValue(); assert(argList.size() == 2); // Symbol, down. const std::string symbol = argList[0].getStringValue(); _down = argList[1].getBooleanValue(); addAutoLink(GenericTosDataNode::find(this, 0, _tosData, symbol)); if (!_randInitialized) { srand48_r(TimeVal(true).asMicroseconds(), &_randomState); _randInitialized = true; } } double TrailingStop::dRand() { double result; drand48_r(&_randomState, &result); return result; } inline int TrailingStop::moreReportableDirection() { return (_down?-1:1); } TrailingStop::Compare TrailingStop::compare(double a, double b) { if (a == b) return tsEqual; else if ((a > b) ^ _down) // A is more reportable than B // A = 10.00, B = 9.99, Direction is Up // (We are short, so we report when it goes up, against us.) // A = 9.99, B = 10.00, Direction is Down // (We are long, so we report when it goes down, against us.) return tsMoreReportable; else return tsMoreProfitable; } void TrailingStop::onWakeup(int msgId) { newTosData(); } void TrailingStop::newTosData() { if (!(_tosData->getValid() && _tosData->getLast().price)) _previouslyValid = false; else try { const double lastPrice = _tosData->getLast().price; for (History::iterator it = _history.begin(); it != _history.end(); it++) if (compare(lastPrice, it->initialPrice) != tsMoreReportable) { // Get rid of this entry and all after it. _history.erase(it, _history.end()); break; } bool wasReportable = false; for (History::iterator it = _history.begin(); it != _history.end(); it++) if (compare(lastPrice, it->nextPrice) != tsMoreProfitable) { std::string brokenLevelText; double brokenLevelQuality; findLevels(it->initialPrice, lastPrice, brokenLevelText, brokenLevelQuality, it->nextPrice); if (_previouslyValid) { std::string msg = "Moved: "; msg += brokenLevelText; msg += " in "; msg += durationString(it->initialTime, getSubmitTime()); msg += '.'; std::string altMsg = "m="; // some examples: -5.25%, -5.5%, +5.5%, +5.00, -5.00 altMsg += urlEncode(brokenLevelText); altMsg += "&d="; altMsg += ntoa(it->initialTime - getSubmitTime()); report(msg, altMsg, brokenLevelQuality); // Get rid of every entry aftre this one. _history.erase(it+1, _history.end()); wasReportable = true; break; } } if (!wasReportable) { bool canCreateNew; double nextPriceLevel; findFirstLevel(lastPrice, nextPriceLevel, canCreateNew); if (canCreateNew && (_history.empty() || ((compare(nextPriceLevel, _history.rbegin()->nextPrice) == tsMoreProfitable) && (lastPrice != _history.rbegin()->initialPrice)))) { HistoryRec r; r.initialPrice = lastPrice; r.initialTime = getSubmitTime(); r.nextPrice = nextPriceLevel; _history.push_back(r); } } _previouslyValid = true; } catch (...) { // This is probably just paranoia, but I don't like some of the // real division then conversion back to integers. This scares // me. Some odd values could cause a numeric overflow. TclList msg; msg<<"TrailingStop"<<"NewTosData"<<"unexpected exception"; sendToLogFile(msg); } } void TrailingStop::findFirstLevel(double startingPoint, double &nextLevel, bool &success) { static const double BAD_STEP_SIZE = 0.0; const double step = getStepSize(startingPoint); if (step <= BAD_STEP_SIZE) success = false; else { nextLevel = startingPoint + moreReportableDirection() * step * 2; success = (nextLevel != startingPoint) && (nextLevel > 0); } } static int basicQuality(int startingQuality) { if (startingQuality <= 0) return 0; // Use an unsigned number to avoid an infinite loop! unsigned int toShift = startingQuality; int result = 1; while ((toShift & 1) == 0) { toShift >>= 1; result <<= 1; } return result; } void TrailingStop::findLevels(double startingPoint, double currentPoint, std::string &brokenLevelText, double &brokenLevelQuality, double &next) { const double step = getStepSize(startingPoint); const double distance = (currentPoint - startingPoint) * moreReportableDirection(); // Is it possible that, due to round off error, the caller thinks that // we just barely crossed a line, but this code thinks we just barely // missed it? const int count = (int)floor(distance / step); brokenLevelText = descriptionOfMove(currentPoint - startingPoint, count); brokenLevelQuality = qualityFromSteps(basicQuality(count)) * (2 / (2 - dRand())); next = startingPoint + moreReportableDirection() * (count + 1) * step; } //////////////////////////////////////////////////////////////////// // TrailingStopPercent //////////////////////////////////////////////////////////////////// class TrailingStopPercent : public TrailingStop { protected: virtual double getStepSize(double startingPoint); virtual std::string descriptionOfMove(double priceIncrease, int steps); virtual double qualityFromSteps(int steps); private: TrailingStopPercent(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; double TrailingStopPercent::getStepSize(double startingPoint) { return startingPoint * 0.0025; } std::string TrailingStopPercent::descriptionOfMove(double priceIncrease, int steps) { std::string result; if (moreReportableDirection() > 0) result = '+'; else result = '-'; result += ntoa(steps>>2); switch (steps % 4) { case 1: result += ".25"; break; case 2: result += ".5"; break; case 3: result += ".75"; break; } result += '%'; return result; } double TrailingStopPercent::qualityFromSteps(int steps) { return steps / 4.0; } TrailingStopPercent::TrailingStopPercent(DataNodeArgument const &args) : TrailingStop(args) { } //////////////////////////////////////////////////////////////////// // TrailingStopSigma //////////////////////////////////////////////////////////////////// class TrailingStopSigma : public TrailingStop { protected: virtual double getStepSize(double startingPoint); virtual std::string descriptionOfMove(double priceIncrease, int steps); virtual double qualityFromSteps(int steps); private: double _volatility; bool _volatilityHasBeenFixed; TrailingStopSigma(DataNodeArgument const &args); friend class GenericDataNodeFactory; }; double TrailingStopSigma::getStepSize(double startingPoint) { if (!_volatilityHasBeenFixed) { if (startingPoint > 0.10) { // A one penny minimum is right for most NASDAQ/NYSE/AMEX stocks. if (_volatility < 0.01) _volatility = 0; } else { if (_volatility < 0.0001) _volatility = 0; } _volatilityHasBeenFixed = true; } return _volatility / 2.0; } std::string TrailingStopSigma::descriptionOfMove(double priceIncrease, int steps) { return formatPrice(priceIncrease, true); } double TrailingStopSigma::qualityFromSteps(int steps) { return steps / 2.0; } TrailingStopSigma::TrailingStopSigma(DataNodeArgument const &args) : TrailingStop(args), _volatilityHasBeenFixed(false) { std::string const &symbol = args.getListValue()[0].getStringValue(); _volatility = getTickVolatility(symbol); } //////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////// void initializeTrailingStop() { GenericDataNodeFactory::sf< TrailingStopPercent > ("TrailingStopDownPercent", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< TrailingStopPercent > ("TrailingStopUpPercent", symbolPlaceholderObject, false); GenericDataNodeFactory::sf< TrailingStopSigma > ("TrailingStopDownSigma", symbolPlaceholderObject, true); GenericDataNodeFactory::sf< TrailingStopSigma > ("TrailingStopUpSigma", symbolPlaceholderObject, false); }