#include "../shared/MiscSupport.h" #include "../shared/MarketHours.h" #include "../shared/SimpleLogFile.h" #include "../shared/MiscSQL.h" #include "CandleTimer.h" ///////////////////////////////////////////////////////////////////// // CandleTimer ///////////////////////////////////////////////////////////////////// const std::string CandleTimer::DAILY = "Daily"; const std::string CandleTimer::INTRADAY = "Intraday"; std::string CandleTimer::debugDump() const { TclList result; const time_t now = time(NULL); time_t start, end; getTimes(0, start, end); result<<"getTotalRowCount(now)"< MINUTES_OF_TRADING) return MINUTES_OF_TRADING; return result; } int SimpleCandleTimer::getPreviewRowCount(time_t time) const { return getTotalRowCount(time + 59); } void SimpleCandleTimer::getTimes(int row, time_t &start,time_t &end) const { start = row * MARKET_HOURS_MINUTE + _offset; end = start + MARKET_HOURS_MINUTE; } SimpleCandleTimer::SimpleCandleTimer(time_t startDate) : _offset(midnight(startDate) + MARKET_HOURS_OPEN) { } CandleTimer::Ref SimpleCandleTimer::Tomorrow() { return new SimpleCandleTimer(midnight(time(NULL)) + 24 * MARKET_HOURS_HOUR); } CandleTimer::Ref SimpleCandleTimer::Today() { return new SimpleCandleTimer(midnight(time(NULL))); } CandleTimer::Ref SimpleCandleTimer::Round() { return new SimpleCandleTimer(roundToMidnight(time(NULL))); } std::string SimpleCandleTimer::debugDump() const { TclList result; result<<"SimpleCandleTimer"<<"_offset"< 0, tue -> 1, ... sun -> 6 // Go backwards from the initial time and stop the first time you get to // Monday at midnight. _baseDate = midnight(time - daysBack * 24 * MARKET_HOURS_HOUR); sendToLogFile(TclList()< 4) { // This is a Saturday or a Sunday. This is the same as end of market on // Friday. (Before the market next Monday would have also worked.) additionalCompleteDays = 4; candlesToday = _candlesPerDay; } else { // Week day. Figure out exactly what time on that day. const int timeToday = time - date; if (timeToday <= _startTime) // Note: At exactly the start time, we don't count the first candle // at all. One second later, we say the first candle is ready to be // previewed, but it is not complete. candlesToday = 0; else if (timeToday >= _endTime) candlesToday = _candlesPerDay; else { const int endAdjustment = completeOnly?0:(_secondsPerCandle - 1); candlesToday = (timeToday - _dailyOffset + endAdjustment) / _secondsPerCandle; } } return (weeks * 5 + additionalCompleteDays) * _candlesPerDay + candlesToday - _rowOffset; } int IntradayCandleTimer::getTotalRowCount(time_t time) const { return getRowCount(time, true); } int IntradayCandleTimer::getPreviewRowCount(time_t time) const { return getRowCount(time, false); } void IntradayCandleTimer::getTimes(int row, time_t &start,time_t &end) const { row += _rowOffset; const int candlesPerWeek = 5 * _candlesPerDay; const int weeks = row / candlesPerWeek; row -= weeks * candlesPerWeek; const int days = row / _candlesPerDay; row -= days * _candlesPerDay; const time_t date = midnight(_baseDate + 12 * MARKET_HOURS_HOUR + (weeks * 7 + days) * 24 * MARKET_HOURS_HOUR); const int idealStartTime = _dailyOffset + _secondsPerCandle * row; const int idealEndTime = idealStartTime + _secondsPerCandle; start = date + std::max(idealStartTime, _startTime); end = date + std::min(idealEndTime, _endTime); } CandleTimer::Ref IntradayCandleTimer::reanchor(int completeRows, time_t atDateTime) const { // The caller has given us two new parameters. The others should be copied // from the original call to IntradayCandleTimer(). We didn't save those // exact values. So we'll do some algebra here to recover the original // values from what we previously saved. const int minutesPerCandle = _secondsPerCandle / MARKET_HOURS_MINUTE; const int startOffset = (MARKET_HOURS_OPEN - _startTime) / MARKET_HOURS_MINUTE; const int endOffset = (_endTime - MARKET_HOURS_CLOSE) / MARKET_HOURS_MINUTE; const int firstCandleSize = (_secondsPerCandle + _dailyOffset - _startTime) / MARKET_HOURS_MINUTE; return new IntradayCandleTimer(completeRows, atDateTime, minutesPerCandle, startOffset, endOffset, firstCandleSize); } IntradayCandleTimer::IntradayCandleTimer(int completeRows, time_t atDateTime, int minutesPerCandle, int startOffset, int endOffset, int firstCandleSize) { initBaseDate(); if (atDateTime <= 0) atDateTime = time(NULL); else if (atDateTime < _baseDate) atDateTime = _baseDate; _startTime = MARKET_HOURS_OPEN - startOffset * MARKET_HOURS_MINUTE; _endTime = MARKET_HOURS_CLOSE + endOffset * MARKET_HOURS_MINUTE; _secondsPerCandle = minutesPerCandle * MARKET_HOURS_MINUTE; if ((_startTime < 1 * MARKET_HOURS_MINUTE) || (_endTime > 24 * MARKET_HOURS_HOUR - MARKET_HOURS_MINUTE) || (_startTime + _secondsPerCandle > _endTime) || (minutesPerCandle < 1)) { // A simple and easy to recognize error state. _startTime = MARKET_HOURS_OPEN; _endTime = MARKET_HOURS_OPEN + MARKET_HOURS_MINUTE; minutesPerCandle = 1; _secondsPerCandle = MARKET_HOURS_MINUTE; } const int minutesToWatch = (_endTime - _startTime) / MARKET_HOURS_MINUTE; const int extraTime = minutesToWatch % minutesPerCandle; if (firstCandleSize > minutesPerCandle) // First candle is normal. Last candle might be partial. firstCandleSize = minutesPerCandle; else if (firstCandleSize < extraTime) // Last candle is normal. First candle might be partial. (extraTime == 0 // is not a special case. If that happens, all candles are complete.) firstCandleSize = extraTime; _dailyOffset = _startTime - (minutesPerCandle - firstCandleSize) * 60; _candlesPerDay = minutesToWatch / minutesPerCandle; if (extraTime) _candlesPerDay++; _rowOffset = 0; const int initialOffset = getTotalRowCount(atDateTime); if (initialOffset > completeRows) _rowOffset = initialOffset - completeRows; } ///////////////////////////////////////////////////////////////////// // DailyCandleTimer ///////////////////////////////////////////////////////////////////// time_t DailyCandleTimer::_baseDate = 0; std::string DailyCandleTimer::debugDump() const { TclList result; result<<"DailyCandleTimer" <<"_rowOffset"<<_rowOffset <= cutoff; const time_t date = midnight(time); static const int lowestPossibleResult = std::numeric_limits< int >::lowest(); if (date < _baseDate) return lowestPossibleResult; const int days = (date - _baseDate + 12 * MARKET_HOURS_HOUR) / MARKET_HOURS_DAY; const int weeks = days / 7; int additionalDays = days % 7; // days = weeks * 7 + additionalDays. if (additionalDays > 4) { // The initial date was on a weekend. We translate that to the // preceeding Friday, after the cutoff. additionalDays = 5; } else if (afterTheCutoff) // Monday after the cutoff is the same as Tuesday before the cutoff. additionalDays++; int result = weeks * 5 + additionalDays - _rowOffset; if (result < lowestPossibleResult) return lowestPossibleResult; return result; } int DailyCandleTimer::getTotalRowCount(time_t time) const { return getRowCount(time, MARKET_HOURS_CLOSE); } int DailyCandleTimer::getPreviewRowCount(time_t time) const { return getRowCount(time, MARKET_HOURS_OPEN); } void DailyCandleTimer::getTimes(int row, time_t &start,time_t &end) const { row += _rowOffset; const int weeks = row / 5; const int additionalDays = row % 5; time_t time = _baseDate + (weeks * 7 + additionalDays) * MARKET_HOURS_DAY; time = roundToMidnight(time); start = time + MARKET_HOURS_OPEN; end = time + MARKET_HOURS_CLOSE; } void DailyCandleTimer::initBaseDate() { if (_baseDate) // Already initialized! return; // Noon, 2/1/1990 // That's currently the earliest time in our database. time_t time = mysqlToTimeT("1990-02-01 12:00:00"); struct tm brokenDown; localtime_r(&time, &brokenDown); const int weekDay = brokenDown.tm_wday; const int daysBack = (weekDay + 6) % 7; // mon -> 0, tue -> 1, ... sun -> 6 // Go backwards from the initial time and stop the first time you get to // Monday at midnight. _baseDate = midnight(time - daysBack * MARKET_HOURS_DAY); sendToLogFile(TclList()< rowCount) _rowOffset = initialOffset - rowCount; } CandleTimer::Ref DailyCandleTimer::reanchor(int completeRows, time_t atDateTime) const { return new DailyCandleTimer(completeRows, atDateTime); } ///////////////////////////////////////////////////////////////////// // WeeklyCandleTimer ///////////////////////////////////////////////////////////////////// time_t WeeklyCandleTimer::_baseDate = 0; std::string WeeklyCandleTimer::debugDump() const { TclList result; result<<"WeeklyCandleTimer" <<"_rowOffset"<<_rowOffset < cutoffDayOfWeek) afterTheCutoff = true; else afterTheCutoff = secondOfTheDay >= cutoffSecondOfDay; startDate = time - secondOfTheDay; // Midnight on the requested day. startDate -= (brokenDown.tm_wday - 1) * MARKET_HOURS_DAY; // startDate is now midnight on Monday, the same week as the original // request. It might be off by an hour because of daylight savings time. // The value will be rounded below. } static const int lowestPossibleResult = std::numeric_limits< int >::lowest(); if (startDate < _baseDate) return lowestPossibleResult; int weeks = (startDate - _baseDate + 12 * MARKET_HOURS_HOUR) / MARKET_HOURS_DAY / 7; if (afterTheCutoff) weeks++; // Include this week. int result = weeks - _rowOffset; if (result < lowestPossibleResult) return lowestPossibleResult; return result; } int WeeklyCandleTimer::getTotalRowCount(time_t time) const { return getRowCount(time, Cutoff::Total); } int WeeklyCandleTimer::getPreviewRowCount(time_t time) const { return getRowCount(time, Cutoff::Preview); } void WeeklyCandleTimer::getTimes(int row, time_t &start,time_t &end) const { time_t time = _baseDate + (row + _rowOffset) * 7 * MARKET_HOURS_DAY; time = roundToMidnight(time); // Midnight Monday morning. start = time + MARKET_HOURS_OPEN; // Monday morning. end = time + 4 * MARKET_HOURS_DAY + MARKET_HOURS_CLOSE; // Friday afternoon. } void WeeklyCandleTimer::initBaseDate() { if (_baseDate) // Already initialized! return; // Noon, 2/1/1990 // That's currently the earliest time in our database. time_t time = mysqlToTimeT("1990-02-01 12:00:00"); struct tm brokenDown; localtime_r(&time, &brokenDown); const int weekDay = brokenDown.tm_wday; const int daysBack = (weekDay + 6) % 7; // mon -> 0, tue -> 1, ... sun -> 6 // Go backwards from the initial time and stop the first time you get to // Monday at midnight. _baseDate = midnight(time - daysBack * MARKET_HOURS_DAY); sendToLogFile(TclList()< rowCount) _rowOffset = initialOffset - rowCount; } CandleTimer::Ref WeeklyCandleTimer::reanchor(int completeRows, time_t atDateTime) const { return new WeeklyCandleTimer(completeRows, atDateTime); } ///////////////////////////////////////////////////////////////////// // MonthlyCandleTimer ///////////////////////////////////////////////////////////////////// time_t MonthlyCandleTimer::getStartTime(int rowCount) const { const int totalMonths = std::max(0, _rowOffset + rowCount); const int year = totalMonths / 12; // Offset from 1900 const int month = totalMonths % 12; // Offset from January. struct tm brokenDown; brokenDown.tm_sec = MARKET_HOURS_OPEN; brokenDown.tm_min = 0; brokenDown.tm_hour = 0; brokenDown.tm_mday = 1; brokenDown.tm_mon = month; brokenDown.tm_year = year; brokenDown.tm_isdst = -1; time_t time = mktime(&brokenDown); if (brokenDown.tm_wday == 0) { // The first was a Sunday. Add one day to get to Monday. brokenDown.tm_sec = MARKET_HOURS_OPEN; brokenDown.tm_min = 0; brokenDown.tm_hour = 0; brokenDown.tm_mday = 2; brokenDown.tm_isdst = -1; time = mktime(&brokenDown); assert(brokenDown.tm_wday == 1); } else if (brokenDown.tm_wday == 6) { // The first was a Saturday. Add two days to get to Monday. brokenDown.tm_sec = MARKET_HOURS_OPEN; brokenDown.tm_min = 0; brokenDown.tm_hour = 0; brokenDown.tm_mday = 3; brokenDown.tm_isdst = -1; time = mktime(&brokenDown); assert(brokenDown.tm_wday == 1); } return time; } time_t MonthlyCandleTimer::getEndTime(int rowCount) const { const int totalMonths = std::max(0, _rowOffset + rowCount + 1); const int year = totalMonths / 12; // Offset from 1900 const int month = totalMonths % 12; // Offset from January. struct tm brokenDown; brokenDown.tm_sec = 0; brokenDown.tm_min = 0; brokenDown.tm_hour = 0; brokenDown.tm_mday = 1; brokenDown.tm_mon = month; brokenDown.tm_year = year; brokenDown.tm_isdst = -1; time_t time = mktime(&brokenDown); // time is now midnight the 1st of the following month. I.e. one second // earlier would put us into the requested month. int daysBack; if (brokenDown.tm_wday == 0) // The first day of next month is a Sunday. Go back 2 days to the Friday // near the end of the requested month. daysBack = 2; else if (brokenDown.tm_wday == 1) // The first day of next month is a Monday. Go back 3 days to the Friday // near the end of the requested month. daysBack = 3; else // Go back 1 day to the last day of the requested month. It will be a // weekday. daysBack = 1; time -= daysBack * MARKET_HOURS_DAY; time = roundToMidnight(time) + MARKET_HOURS_CLOSE; return time; } time_t MonthlyCandleTimer::getTime(int rowCount, Cutoff cutoff) const { if (cutoff == Cutoff::Start) return getStartTime(rowCount); else return getEndTime(rowCount); } int MonthlyCandleTimer::getRowCount(time_t time, Cutoff cutoff) const { struct tm brokenDown; localtime_r(&time, &brokenDown); int rowCount = brokenDown.tm_year * 12 + brokenDown.tm_mon - _rowOffset; if (time >= getTime(rowCount, cutoff)) // Include this month. rowCount++; return rowCount; } int MonthlyCandleTimer::getTotalRowCount(time_t time) const { return getRowCount(time, Cutoff::End); } int MonthlyCandleTimer::getPreviewRowCount(time_t time) const { return getRowCount(time, Cutoff::Start); } void MonthlyCandleTimer::getTimes(int row, time_t &start,time_t &end) const { start = getStartTime(row); end = getEndTime(row); } std::string MonthlyCandleTimer::debugDump() const { TclList result; result<<"MonthlyCandleTimer" <<"_rowOffset"<<_rowOffset < rowCount) _rowOffset = initialOffset - rowCount; } ///////////////////////////////////////////////////////////////////// // unit test ///////////////////////////////////////////////////////////////////// #ifdef __UNIT_TEST_CANDLE_TIMER_ #include #include "../shared/MiscSupport.h" #include "../shared/MiscSQL.h" // g++ -Wall -ggdb -o CandleTimerUnitTest -D __UNIT_TEST_CANDLE_TIMER_ CandleTimer.C ../shared/shared.a ../shared/SimpleLogFile.C class DailyCandleTimerTest { private: bool testOne(CandleTimer::Ref candleTimer, int rowCount, time_t endTime); bool testOne(int rowCount, time_t endTime); bool testOneDay(time_t endTime); public: void go(); void show(); }; bool DailyCandleTimerTest::testOne(CandleTimer::Ref candleTimer, int rowCount, time_t endTime) { bool success = true; const int found = candleTimer->getTotalRowCount(endTime); if (found != rowCount) { success = false; std::cerr<<"Invalid row count. Expected="<>initialCount; std::string clearToEndOfLine; std::getline(std::cin, clearToEndOfLine); if (!std::cin) return 0; //CandleTimer::Ref candleTimer = new DailyCandleTimer(initialCount, time); CandleTimer::Ref candleTimer = new MonthlyCandleTimer(initialCount, time); for (int i = -7; i < initialCount + 7; i++) { time_t start, end; candleTimer->getTimes(i, start, end); std::cout<getTotalRowCount(end-1)<<", " <getTotalRowCount(end)<<", " <getTotalRowCount(end+1)<<"; " <getPreviewRowCount(start-1)<<", " <getPreviewRowCount(start)<<", " <getPreviewRowCount(start+1)<getTotalRowCount(end-1) == i); // Standard STL rules apply. The end of the candle is the first // time after the candle is complete, not part of the candle. // So at 9:35:00 we have finished the 5 minute candles that // starts at 9:30:00 and ends at 9:35:00. assert(candleTimer->getTotalRowCount(end) == i+1); // And one second after the candle ended, it should still be // ended! assert(candleTimer->getTotalRowCount(end+1) == i+1); // Preview works the same way, but based on the start of the // candle. If it's 9:32:27 and you want to see the 9:30:00 to // 9:35:00 candle, then use getPreviewRowCount(). It will include // a row as soon as it's started, rather than waiting until it's // finished. assert(candleTimer->getPreviewRowCount(start-1) == i); assert(candleTimer->getPreviewRowCount(start) == i+1); assert(candleTimer->getPreviewRowCount(start+1) == i+1); } } } #endif