Skip to content

Commit

Permalink
feat: add draft for rest query for trades, candlesticks, and market d…
Browse files Browse the repository at this point in the history
…epth - binance
  • Loading branch information
cryptochassis committed Sep 29, 2023
1 parent 89b5e22 commit 61a0c73
Show file tree
Hide file tree
Showing 19 changed files with 342 additions and 28 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ Received an event:
]
Bye
```
* Request operation types: `GET_INSTRUMENT`, `GET_INSTRUMENTS`, `GET_RECENT_TRADES`, `GET_RECENT_AGG_TRADES`(only applicable to binance family: https://binance-docs.github.io/apidocs/spot/en/#compressed-aggregate-trades-list).
* Request parameter names: `LIMIT`, `INSTRUMENT_TYPE`. Instead of these convenient names you can also choose to use arbitrary parameter names and they will be passed to the exchange's native API. See [this example](example/src/market_data_advanced_request/main.cpp).
* Request operation types: `GET_INSTRUMENT`, `GET_INSTRUMENTS`, `GET_RECENT_TRADES`, `GET_HISTORICAL_TRADES`, `GET_RECENT_CANDLESTICKS`, `GET_HISTORICAL_CANDLESTICKS`, `GET_RECENT_AGG_TRADES`, `GET_HISTORICAL_AGG_TRADES`(only applicable to binance family: https://binance-docs.github.io/apidocs/spot/en/#compressed-aggregate-trades-list), ``.
* Request parameter names: `LIMIT`, `INSTRUMENT_TYPE`, `CANDLESTICK_INTERVAL_SECONDS`, `START_TIME_SECONDS`, `END_TIME_SECONDS`, `START_TRADE_ID`, `END_TRADE_ID`, `START_AGG_TRADE_ID`, `END_AGG_TRADE_ID`. Instead of these convenient names you can also choose to use arbitrary parameter names and they will be passed to the exchange's native API. See [this example](example/src/market_data_advanced_request/main.cpp).
* Message's `time` represents the exchange's reported timestamp. Its `timeReceived` represents the library's receiving timestamp. `time` can be retrieved by `getTime` method and `timeReceived` can be retrieved by `getTimeReceived` method. (For non-C++, please use `getTimeUnix` and `getTimeReceivedUnix` methods or `getTimeISO` and `getTimeReceivedISO` methods).

**Objective 2:**
Expand Down Expand Up @@ -338,7 +338,7 @@ Best bid and ask at 2020-07-27T23:56:51.884855000Z are:
Best bid and ask at 2020-07-27T23:56:51.935993000Z are:
...
```
* Subscription fields: `MARKET_DEPTH`, `TRADE`, `AGG_TRADE`(only applicable to binance family: https://binance-docs.github.io/apidocs/spot/en/#aggregate-trade-streams).
* Subscription fields: `MARKET_DEPTH`, `TRADE`, `CANDLESTICK`, `AGG_TRADE`(only applicable to binance family: https://binance-docs.github.io/apidocs/spot/en/#aggregate-trade-streams).

### Advanced Market Data

Expand Down
12 changes: 12 additions & 0 deletions include/ccapi_cpp/ccapi_macro.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,18 @@
#ifndef CCAPI_LIMIT
#define CCAPI_LIMIT "LIMIT"
#endif
#ifndef CCAPI_START_TRADE_ID
#define CCAPI_START_TRADE_ID "START_TRADE_ID"
#endif
#ifndef CCAPI_START_AGG_TRADE_ID
#define CCAPI_START_AGG_TRADE_ID "START_AGG_TRADE_ID"
#endif
#ifndef CCAPI_START_TIME_SECONDS
#define CCAPI_START_TIME_SECONDS "START_TIME_SECONDS"
#endif
#ifndef CCAPI_END_TIME_SECONDS
#define CCAPI_END_TIME_SECONDS "END_TIME_SECONDS"
#endif
#ifndef CCAPI_BASE_ASSET
#define CCAPI_BASE_ASSET "BASE_ASSET"
#endif
Expand Down
20 changes: 20 additions & 0 deletions include/ccapi_cpp/ccapi_message.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ class Message CCAPI_FINAL {
GET_ACCOUNT_BALANCES,
GET_ACCOUNT_POSITIONS,
GET_RECENT_TRADES,
GET_HISTORICAL_TRADES,
GET_RECENT_AGG_TRADES,
GET_HISTORICAL_AGG_TRADES,
GET_RECENT_CANDLESTICKS,
GET_HISTORICAL_CANDLESTICKS,
GET_MARKET_DEPTH,
GET_INSTRUMENT,
GET_INSTRUMENTS,
RESPONSE_ERROR,
Expand Down Expand Up @@ -147,9 +152,24 @@ class Message CCAPI_FINAL {
case Type::GET_RECENT_TRADES:
output = "GET_RECENT_TRADES";
break;
case Type::GET_HISTORICAL_TRADES:
output = "GET_HISTORICAL_TRADES";
break;
case Type::GET_RECENT_AGG_TRADES:
output = "GET_RECENT_AGG_TRADES";
break;
case Type::GET_HISTORICAL_AGG_TRADES:
output = "GET_HISTORICAL_AGG_TRADES";
break;
case Type::GET_RECENT_CANDLESTICKS:
output = "GET_RECENT_CANDLESTICKS";
break;
case Type::GET_HISTORICAL_CANDLESTICKS:
output = "GET_HISTORICAL_CANDLESTICKS";
break;
case Type::GET_MARKET_DEPTH:
output = "GET_MARKET_DEPTH";
break;
case Type::GET_INSTRUMENT:
output = "GET_INSTRUMENT";
break;
Expand Down
20 changes: 20 additions & 0 deletions include/ccapi_cpp/ccapi_request.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ class Request CCAPI_FINAL {
GENERIC_PRIVATE_REQUEST = CCAPI_REQUEST_OPERATION_TYPE_GENERIC_PRIVATE_REQUEST,
FIX = CCAPI_REQUEST_OPERATION_TYPE_FIX,
GET_RECENT_TRADES = CCAPI_REQUEST_OPERATION_TYPE_MARKET_DATA,
GET_HISTORICAL_TRADES,
GET_RECENT_AGG_TRADES,
GET_HISTORICAL_AGG_TRADES,
GET_RECENT_CANDLESTICKS,
GET_HISTORICAL_CANDLESTICKS,
GET_MARKET_DEPTH,
GET_INSTRUMENT,
GET_INSTRUMENTS,
CREATE_ORDER = CCAPI_REQUEST_OPERATION_TYPE_EXECUTION_MANAGEMENT_ORDER,
Expand Down Expand Up @@ -61,9 +66,24 @@ class Request CCAPI_FINAL {
case Operation::GET_RECENT_TRADES:
output = "GET_RECENT_TRADES";
break;
case Operation::GET_HISTORICAL_TRADES:
output = "GET_HISTORICAL_TRADES";
break;
case Operation::GET_RECENT_AGG_TRADES:
output = "GET_RECENT_AGG_TRADES";
break;
case Operation::GET_HISTORICAL_AGG_TRADES:
output = "GET_HISTORICAL_AGG_TRADES";
break;
case Operation::GET_RECENT_CANDLESTICKS:
output = "GET_RECENT_CANDLESTICKS";
break;
case Operation::GET_HISTORICAL_CANDLESTICKS:
output = "GET_HISTORICAL_CANDLESTICKS";
break;
case Operation::GET_MARKET_DEPTH:
output = "GET_MARKET_DEPTH";
break;
case Operation::GET_INSTRUMENT:
output = "GET_INSTRUMENT";
break;
Expand Down
131 changes: 122 additions & 9 deletions include/ccapi_cpp/service/ccapi_market_data_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ class MarketDataService : public Service {
CCAPI_LOGGER_FUNCTION_ENTER;
this->requestOperationToMessageTypeMap = {
{Request::Operation::GET_RECENT_TRADES, Message::Type::GET_RECENT_TRADES},
{Request::Operation::GET_HISTORICAL_TRADES, Message::Type::GET_HISTORICAL_TRADES},
{Request::Operation::GET_RECENT_AGG_TRADES, Message::Type::GET_RECENT_AGG_TRADES},
{Request::Operation::GET_HISTORICAL_AGG_TRADES, Message::Type::GET_HISTORICAL_AGG_TRADES},
{Request::Operation::GET_RECENT_CANDLESTICKS, Message::Type::GET_RECENT_CANDLESTICKS},
{Request::Operation::GET_HISTORICAL_CANDLESTICKS, Message::Type::GET_HISTORICAL_CANDLESTICKS},
{Request::Operation::GET_MARKET_DEPTH, Message::Type::GET_MARKET_DEPTH},
{Request::Operation::GET_INSTRUMENT, Message::Type::GET_INSTRUMENT},
{Request::Operation::GET_INSTRUMENTS, Message::Type::GET_INSTRUMENTS},
};
Expand Down Expand Up @@ -778,6 +783,45 @@ class MarketDataService : public Service {
}
CCAPI_LOGGER_FUNCTION_EXIT;
}
void updateElementListWithOrderBookSnapshot(const std::string& field, int maxMarketDepth, const std::map<Decimal, std::string>& snapshotBid,
const std::map<Decimal, std::string>& snapshotAsk, std::vector<Element>& elementList) {
CCAPI_LOGGER_FUNCTION_ENTER;
if (field == CCAPI_MARKET_DEPTH) {
int bidIndex = 0;
for (auto iter = snapshotBid.rbegin(); iter != snapshotBid.rend(); iter++) {
if (bidIndex < maxMarketDepth) {
Element element;
element.insert(CCAPI_BEST_BID_N_PRICE, iter->first.toString());
element.insert(CCAPI_BEST_BID_N_SIZE, iter->second);
elementList.emplace_back(std::move(element));
}
++bidIndex;
}
if (snapshotBid.empty()) {
Element element;
element.insert(CCAPI_BEST_BID_N_PRICE, CCAPI_BEST_BID_N_PRICE_EMPTY);
element.insert(CCAPI_BEST_BID_N_SIZE, CCAPI_BEST_BID_N_SIZE_EMPTY);
elementList.emplace_back(std::move(element));
}
int askIndex = 0;
for (auto iter = snapshotAsk.begin(); iter != snapshotAsk.end(); iter++) {
if (askIndex < maxMarketDepth) {
Element element;
element.insert(CCAPI_BEST_ASK_N_PRICE, iter->first.toString());
element.insert(CCAPI_BEST_ASK_N_SIZE, iter->second);
elementList.emplace_back(std::move(element));
}
++askIndex;
}
if (snapshotAsk.empty()) {
Element element;
element.insert(CCAPI_BEST_ASK_N_PRICE, CCAPI_BEST_ASK_N_PRICE_EMPTY);
element.insert(CCAPI_BEST_ASK_N_SIZE, CCAPI_BEST_ASK_N_SIZE_EMPTY);
elementList.emplace_back(std::move(element));
}
}
CCAPI_LOGGER_FUNCTION_EXIT;
}
std::map<Decimal, std::string> calculateMarketDepthUpdate(bool isBid, const std::map<Decimal, std::string>& c1, const std::map<Decimal, std::string>& c2,
int maxMarketDepth) {
if (c1.empty()) {
Expand Down Expand Up @@ -948,8 +992,8 @@ class MarketDataService : public Service {
Element element;
std::string k1(CCAPI_LAST_PRICE);
std::string k2(CCAPI_LAST_SIZE);
element.emplace(k1, y.at(MarketDataMessage::DataFieldType::PRICE));
element.emplace(k2, y.at(MarketDataMessage::DataFieldType::SIZE));
element.emplace(k1, price);
element.emplace(k2, size);
{
auto it = y.find(MarketDataMessage::DataFieldType::TRADE_ID);
if (it != y.end()) {
Expand Down Expand Up @@ -1307,12 +1351,9 @@ class MarketDataService : public Service {
const TimePoint& tp, const TimePoint& timeReceived, MarketDataMessage::TypeForData& input, const std::string& field,
const std::map<std::string, std::string>& optionMap, const std::vector<std::string>& correlationIdList,
bool isSolicited) {
CCAPI_LOGGER_TRACE("input = " + MarketDataMessage::dataToString(input));
CCAPI_LOGGER_TRACE("optionMap = " + toString(optionMap));
std::vector<Message> messageList;
std::vector<Element> elementList;
this->updateElementListWithExchangeProvidedCandlestick(field, input, elementList);
CCAPI_LOGGER_TRACE("elementList = " + toString(elementList));
if (!elementList.empty()) {
Message message;
message.setTimeReceived(timeReceived);
Expand All @@ -1327,6 +1368,24 @@ class MarketDataService : public Service {
event.addMessages(messageList);
}
}
void processExchangeProvidedCandlestick(Event& event, const TimePoint& tp, const TimePoint& timeReceived, MarketDataMessage::TypeForData& input,
const std::vector<std::string>& correlationIdList, Message::Type messageType) {
std::vector<Message> messageList;
std::vector<Element> elementList;
this->updateElementListWithExchangeProvidedCandlestick(CCAPI_CANDLESTICK, input, elementList);
if (!elementList.empty()) {
Message message;
message.setTimeReceived(timeReceived);
message.setType(Message::Type::MARKET_DATA_EVENTS_CANDLESTICK);
message.setTime(tp);
message.setElementList(elementList);
message.setCorrelationIdList(correlationIdList);
messageList.emplace_back(std::move(message));
}
if (!messageList.empty()) {
event.addMessages(messageList);
}
}
void updateCalculatedCandlestick(const WsConnection& wsConnection, const std::string& channelId, const std::string& symbolId, const std::string& field,
const MarketDataMessage::TypeForData& input) {
if (field == CCAPI_TRADE || field == CCAPI_AGG_TRADE) {
Expand Down Expand Up @@ -1405,7 +1464,7 @@ class MarketDataService : public Service {
}
return true;
}
int calculateMarketDepthSubscribedToExchange(int depthWanted, std::vector<int> availableMarketDepth) {
int calculateMarketDepthAllowedByExchange(int depthWanted, std::vector<int> availableMarketDepth) {
int i = ceilSearch(availableMarketDepth, 0, availableMarketDepth.size(), depthWanted);
if (i < 0) {
i = availableMarketDepth.size() - 1;
Expand Down Expand Up @@ -1552,13 +1611,24 @@ class MarketDataService : public Service {
for (auto& marketDataMessage : marketDataMessageList) {
if (marketDataMessage.type == MarketDataMessage::Type::MARKET_DATA_EVENTS_MARKET_DEPTH ||
marketDataMessage.type == MarketDataMessage::Type::MARKET_DATA_EVENTS_TRADE ||
marketDataMessage.type == MarketDataMessage::Type::MARKET_DATA_EVENTS_AGG_TRADE) {
marketDataMessage.type == MarketDataMessage::Type::MARKET_DATA_EVENTS_AGG_TRADE ||
marketDataMessage.type == MarketDataMessage::Type::MARKET_DATA_EVENTS_CANDLESTICK) {
const std::vector<std::string>& correlationIdList = {request.getCorrelationId()};
CCAPI_LOGGER_TRACE("correlationIdList = " + toString(correlationIdList));
if (marketDataMessage.data.find(MarketDataMessage::DataType::TRADE) != marketDataMessage.data.end() ||
marketDataMessage.data.find(MarketDataMessage::DataType::AGG_TRADE) != marketDataMessage.data.end()) {
if (marketDataMessage.data.find(MarketDataMessage::DataType::BID) != marketDataMessage.data.end() ||
marketDataMessage.data.find(MarketDataMessage::DataType::ASK) != marketDataMessage.data.end()) {
auto messageType = this->requestOperationToMessageTypeMap.at(request.getOperation());
const auto& param = request.getFirstParamWithDefault();
const auto& maxMarketDepthStr = mapGetWithDefault(param, std::string(CCAPI_MARKET_DEPTH_MAX));
int maxMarketDepth = maxMarketDepthStr.empty() ? INT_MAX : std::stoi(maxMarketDepthStr);
this->processOrderBookSnapshot(event, marketDataMessage.tp, timeReceived, marketDataMessage.data, correlationIdList, messageType, maxMarketDepth);
} else if (marketDataMessage.data.find(MarketDataMessage::DataType::TRADE) != marketDataMessage.data.end() ||
marketDataMessage.data.find(MarketDataMessage::DataType::AGG_TRADE) != marketDataMessage.data.end()) {
auto messageType = this->requestOperationToMessageTypeMap.at(request.getOperation());
this->processTrade(event, marketDataMessage.tp, timeReceived, marketDataMessage.data, correlationIdList, messageType);
} else if (marketDataMessage.data.find(MarketDataMessage::DataType::CANDLESTICK) != marketDataMessage.data.end()) {
auto messageType = this->requestOperationToMessageTypeMap.at(request.getOperation());
this->processExchangeProvidedCandlestick(event, marketDataMessage.tp, timeReceived, marketDataMessage.data, correlationIdList, messageType);
}
} else {
CCAPI_LOGGER_WARN("market data event type is unknown!");
Expand All @@ -1581,6 +1651,45 @@ class MarketDataService : public Service {
messageList.emplace_back(std::move(message));
event.addMessages(messageList);
}
void processOrderBookSnapshot(Event& event, const TimePoint& tp, const TimePoint& timeReceived, MarketDataMessage::TypeForData& input,
const std::vector<std::string>& correlationIdList, Message::Type messageType, int maxMarketDepth) {
std::vector<Message> messageList;
std::vector<Element> elementList;
std::map<Decimal, std::string> snapshotBid, snapshotAsk;
for (auto& x : input) {
auto& type = x.first;
auto& detail = x.second;
if (type == MarketDataMessage::DataType::BID) {
for (auto& y : detail) {
auto& price = y.at(MarketDataMessage::DataFieldType::PRICE);
auto& size = y.at(MarketDataMessage::DataFieldType::SIZE);
Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum);
snapshotBid.emplace(std::move(decimalPrice), std::move(size));
}
CCAPI_LOGGER_TRACE("lastNToString(snapshotBid, " + toString(maxMarketDepth) + ") = " + lastNToString(snapshotBid, maxMarketDepth));
} else if (type == MarketDataMessage::DataType::ASK) {
for (auto& y : detail) {
auto& price = y.at(MarketDataMessage::DataFieldType::PRICE);
auto& size = y.at(MarketDataMessage::DataFieldType::SIZE);
Decimal decimalPrice(price, this->sessionOptions.enableCheckOrderBookChecksum);
snapshotAsk.emplace(std::move(decimalPrice), std::move(size));
}
CCAPI_LOGGER_TRACE("firstNToString(snapshotAsk, " + toString(maxMarketDepth) + ") = " + firstNToString(snapshotAsk, maxMarketDepth));
} else {
CCAPI_LOGGER_WARN("extra type " + MarketDataMessage::dataTypeToString(type));
}
}
this->updateElementListWithOrderBookSnapshot(CCAPI_MARKET_DEPTH, maxMarketDepth, snapshotBid, snapshotAsk, elementList);
CCAPI_LOGGER_TRACE("elementList = " + toString(elementList));
Message message;
message.setTimeReceived(timeReceived);
message.setType(messageType);
message.setTime(tp);
message.setElementList(elementList);
message.setCorrelationIdList(correlationIdList);
messageList.emplace_back(std::move(message));
event.addMessages(messageList);
}
void convertRequestForRestGenericPublicRequest(http::request<http::string_body>& req, const Request& request, const TimePoint& now,
const std::string& symbolId, const std::map<std::string, std::string>& credential) {
const std::map<std::string, std::string> param = request.getFirstParamWithDefault();
Expand Down Expand Up @@ -1879,6 +1988,10 @@ class MarketDataService : public Service {
std::map<std::string, std::map<std::string, std::map<std::string, Decimal>>> lowByConnectionIdChannelIdSymbolIdMap;
std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> closeByConnectionIdChannelIdSymbolIdMap;
std::string getRecentTradesTarget;
std::string getHistoricalTradesTarget;
std::string getRecentCandlesticksTarget;
std::string getHistoricalCandlesticksTarget;
std::string getMarketDepthTarget;
std::string getInstrumentTarget;
std::string getInstrumentsTarget;
std::map<std::string, int> exchangeJsonPayloadIdByConnectionIdMap;
Expand Down
5 changes: 5 additions & 0 deletions include/ccapi_cpp/service/ccapi_market_data_service_binance.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ class MarketDataServiceBinance : public MarketDataServiceBinanceBase {
this->apiKeyName = CCAPI_BINANCE_API_KEY;
this->setupCredential({this->apiKeyName});
this->getRecentTradesTarget = "/api/v3/trades";
this->getHistoricalTradesTarget = "/api/v3/historicalTrades";
this->getRecentAggTradesTarget = "/api/v3/aggTrades";
this->getHistoricalAggTradesTarget = "/api/v3/aggTrades";
this->getRecentCandlesticksTarget = "/api/v3/klines";
this->getHistoricalCandlesticksTarget = "/api/v3/klines";
this->getMarketDepthTarget = "/api/v3/depth";
this->getInstrumentTarget = "/api/v3/exchangeInfo";
this->getInstrumentsTarget = "/api/v3/exchangeInfo";
}
Expand Down
Loading

0 comments on commit 61a0c73

Please sign in to comment.