#ifndef __CandleClient_h_ #define __CandleClient_h_ #include "../shared/PipeConditionVar.h" #include "../shared/ServerConnection.h" #include "CandleCache.h" #include "OneMinuteCandles.h" #include "DailyCandles.h" /* This class manages the connection to the candle server. * * The candle server is a historical server which will give you access to daily * and intraday candles. It does not do any streaming. * * The server serves multiple purposes. * o Some clients will not have access to our database. This will serve as * a conduit: checking credentials, and then copying the relevant data for * the client. * o This server can serve as a cache. We can decide up front what data * should be kept in memory. This server, rather than each client, can * store that data. The data can be preloaded in the middle of the night * and stored in an efficient format. * o Recent intraday candles are never stored in the database. Currently * the client has to decide first thing in the morning what it wants to * follow. If it tried to add more symbols in the middle of the day, some * data would always be missing. The candle server will watch our entire * universe, so the client can start in the middle of the day without * missing anything. * * This provides a thread to handle a single connection to the candle server. * (Perhaps in the future we'll find a good way for several clients to share * one thread. I.e. Candles, NASDAQ VF data, and StockTwits data.) This one * TCP/IP connection can send multiple requests at once, just like any client * connection, i.e. TI Pro talking to the server. * * Some data comes from this thread to the consumer as a message. That's the * ideal way of doing things. Some requests are synchronous. This thread * doesn't doesn't block, but whoever made the request from this thread does. * This design comes in large part from the way we do the database. The new * candle server is replacing the database server, and the bulk of the design * of the client software will not change. * * I've thought a lot about a better way to manage these requests. Ideally * we'd quickly look at every grid, make the request for data for it, then * put it aside until the data arrives. That's easy in principal, but there * are some issues. * o A grid cell can ask for almost anything. Of course, we could probably * make a good guess that would be right most of the time. Maybe even * tag a grid to say that we are or are not limiting ourselves to the * obvious data. That would certainly be the common case, so optimize for * that. * o We don't know up front which rows we are going to update. The first * thing we do when we get a request is to see what's out of date. * o If we ask what's out of date, and then pause, what's out of date is * likely to change. This would be especially true if we broke up the * request into small pieces, like doing one row at a time. In that case * we might never really make progress or catch up to the current time. * o We might be able to do slightly better. Step 1: ask whats out of date, * and ask for all data. Step 2: When the data comes in (a) check if * we're even further out of date (i.e. we need even older data) and (b) * if not, fill in all rows that are out of date in the same request. * o If we were only asking for one row's worth of data, then I'd have no * problem making a lot of requests at once. However, sometimes you ask * for a lot of data at once. Do I really want to store all of that for * a large number of stocks? If I grab data for one grid at a time, I don't * have to store as much. * o Maybe that last point isn't as bad as it sounds. If you have any * interesting grids, the data you will be saving is at least as big as * the data that you're temporarily grabbing for the cache. * * The previous paragraph does a good job describing the general case, but * it may be simpler than that. In reality you typically have two distinct * cases. When you process a historical request from the user, or when you * first initialize a grid for realtime use, you will need a lot of history. * As realtime work is constantly updated, it should never need to talk with * the candle server. * * One thought is to segregate the work, so that live things never have to * wait on historical things. Either you give more priority to the live * things, or you have separate threads dedicated to the historical things. * As long as something that doesn't need historical data isn't waiting on * something that does, there's a limited amount of payback for holding the * historical requests until all of the data is ready. You might get rid of * some latency issues, but you still have to wait for for the server to do * its work, such as reading from the database. * * The exact interface is very specific. This is aimed at OneMinuteCandles * and DailyCandles. Most classes should get data from one of those classes, * not directly from here. */ class CandleClient : ForeverServerConnection { public: class TodaysCandles : public Request { private: TodaysCandles(std::string const &symbol, RequestListener *finalListener, int finalCallbackId) : Request(NULL), _symbol(symbol), _finalListener(finalListener), _finalCallbackId(finalCallbackId) { } friend class CandleClient; const std::string _symbol; RequestListener *const _finalListener; const int _finalCallbackId; public: SingleCandle::ByStartTime data; std::string const &getSymbol() const { return _symbol; } }; private: static std::string makeStartList(AllRowTimes const &allRowTimes); static std::string makeEndList(AllRowTimes const &allRowTimes); static SingleCandle extractSingleCandle(std::string const &bytes); class GetIntraday { private: const std::string _symbol; const std::string _start; const std::string _end; std::string _result; PipeConditionVar _conditionVar; public: // For use in the CandleClient thread. std::string const &getSymbol() const { return _symbol; } std::string const &getStart() const { return _start; } std::string const &getEnd() const { return _end; } void sendResult(std::string const &result); // For use in the requester's thread. GetIntraday(std::string const &symbol, time_t start, time_t end) : _symbol(symbol), _start(ntoa(start)), _end(ntoa(end)) { } GetIntraday(std::string const &symbol, AllRowTimes const &allRowTimes) : _symbol(symbol), _start(makeStartList(allRowTimes)), _end(makeEndList(allRowTimes)) { } std::string const &getResult(); // Usage: This is created in the thread that makes the initial request. // It is sent to the CandleClient thread through the normal queuing // mechanism. The initial request thread calls getResult() to // synchronously wait for a result. The initial request thread will // keep the pointer to this object. The ClientCandle thread will store // a copy of this object until it is ready to respond. Once it has an // answer, the CandleClient thread will call sendResult(). sendResult // will store the result in this object, and then notify the original // requester. After calling sendResult() the CandleClient thread must // be hands off. After getResult() returns, the requester's thread // must delete this object. }; class GetDaily { private: const std::string _symbol; const std::string _date; std::string _result; PipeConditionVar _conditionVar; public: // For use in the CandleClient thread. std::string const &getSymbol() const { return _symbol; } std::string const &getDate() const { return _date; } void sendResult(std::string const &result); // For use in the requester's thread. GetDaily(std::string const &symbol, time_t date) : _symbol(symbol), _date(ntoa(date)) { } GetDaily(std::string const &symbol, AllRowTimes const &allRowTimes) : _symbol(symbol), _date(makeStartList(allRowTimes)) { } std::string const &getResult(); // Usage: This is created in the thread that makes the initial request. // It is sent to the CandleClient thread through the normal queuing // mechanism. The initial request thread calls getResult() to // synchronously wait for a result. The initial request thread will // keep the pointer to this object. The ClientCandle thread will store // a copy of this object until it is ready to respond. Once it has an // answer, the CandleClient thread will call sendResult(). sendResult // will store the result in this object, and then notify the original // requester. After calling sendResult() the CandleClient thread must // be hands off. After getResult() returns, the requester's thread // must delete this object. }; CandleClient(); ~CandleClient(); enum { ciLogin, ciTodaysCandles, ciIntradayCandle, ciDailyCandle }; std::map< TalkWithServer::CancelId, TodaysCandles * > _todaysCandlesPending; void sendRequestToServer(TalkWithServer *connection, TodaysCandles *request); void onTodaysCandlesMessage(std::string const &bytes, TalkWithServer::CancelId cancelId); std::map< TalkWithServer::CancelId, GetIntraday * > _getIntradayPending; void sendRequestToServer(TalkWithServer *connection, GetIntraday *request); void onIntradayMessage(std::string const &bytes, TalkWithServer::CancelId cancelId); std::map< TalkWithServer::CancelId, GetDaily * > _getDailyPending; void sendRequestToServer(TalkWithServer *connection, GetDaily *request); void onDailyMessage(std::string const &bytes, TalkWithServer::CancelId cancelId); protected: virtual void onMessage(std::string bytes, int clientId, TalkWithServer::CancelId cancelId); virtual void onNewConnection(TalkWithServer *talkWithServer); public: // This is thread safe. The result will be be shipped to the requesting // thread. The response is a TodaysCandles object. void requestTodaysCandles(std::string const &symbol, RequestListener *listener, int callbackId); // This is thread safe. Request exactly one candle. This is a blocking // call. SingleCandle getIntraday(std::string const &symbol, time_t start, time_t end); SingleCandle getDaily(std::string const &symbol, time_t date); // This is thread safe. Requests N candles. This is a blocking call. This // doesn't require the cache, but it uses the same data structures, so it // dovetails nicely. The return value is a marshalled set of candles, which // can be given to CandleCache::CandleCache() or to // OneMinuteCandles::SingleCandle::unmarshal(). std::string getIntraday(std::string const &symbol, AllRowTimes const &allRowTimes); std::string getDaily(std::string const &symbol, AllRowTimes const &allRowTimes); // This might return NULL. It will automatically create the instance, if // needed. But it will return NULL if the config file doesn't set // use_candle_server=1. static CandleClient *instance(); }; #endif