Giter VIP home page Giter VIP logo

huobi_cpp's Introduction

Huobi C++ SDK For Spot v3

This is Huobi C++ SDK v3, you can import to your project and use this SDK to query all market data, trading and manage your account. The SDK supports RESTful API invoking, and subscribing the market, account and order update from the WebSocket connection.

If you already use SDK v1 or v2, it is strongly suggested migrate to v3 as we refactor the implementation to make it simpler and easy to maintain. The SDK v3 is completely consistent with the API documentation of the new HTX open platform. Compared to SDK versions v1 and v2, due to changes in parameters of many interfaces, in order to match the latest interface parameter situation, v3 version has made adjustments to parameters of more than 80 interfaces to ensure that requests can be correctly initiated and accurate response data can be obtained. Meanwhile, the v3 version has added over 130 new interfaces available for use, greatly expanding the number of available interfaces. We will stop the maintenance of v2 in the near future. Please refer to the instruction on how to migrate v1 or v2 to v3 in section Migrate from v1 and v2.

Table of Contents

Quick Start

After you install this SDK properly, you can follow below steps in your C++ project

  • Create the client instance.
  • Call the method provided by client.
// Create ReferenceClient instance and get the timestamp
ReferenceClient client;
cout << client.getTimestamp() << endl;

// Create MarketClient instance and get btcusdt latest 1-min candlestick
MarketClient client;
char *symbol = "btcusdt";
CandlestickRequest candlestickRequest;
candlestickRequest.symbol = symbol;
candlestickRequest.period = "1min";
vector<Candlestick> klines = client.getCandlestick(candlestickRequest);
for (Candlestick candlestick:klines) {
  cout << "open " << candlestick.open << endl;
  cout << "close " << candlestick.close << endl;
}

For how to install the SDK, please read the Usage section below.

Usage

Folder structure

This is the folder structure of SDK source code and the description

  • demo: The demo has main function, it provides the examples how to use client instance to access API and read response.
  • include: The header file of the implementation
    • client: The client class declaration, it is responsible to access data
    • rapidjson: The rapid json library
    • request: The request struct implementation
    • response: The response struct implementation
  • src: The core of the SDK
    • client: The client class implementation.

Installation

The SDK is compiled by C++ 11.

The compiler can be gccor clang.

Currently, The SDK has the compatibility on linux system (centos 7 and ubuntu 18.04) only.

Later, macOS and windows.

Install CMake

Please make sure the CMake is installed on your OS.

If not, you can follow https://cmake.org/install/ to install it.

The minimum required version of CMake is 2.8, but we suggest to use the lastest CMake version.

Install 3rd party libraries

Please make sure the 3rd party libraries have been installed in your system. If not, please install them.

Build SDK

Refer to below steps to build the SDK library

$ git clone https://github.com/huobiapi/huobi_Cpp.git
$ cd huobi_Cpp
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_PREFIX_PATH=/opt/cpprest-2.10.16/lib64/cmake/
 // You need to replace it with your own path
$ make
$ sudo make install

if you see the following error when executing "make" command

ld: library not found for -lssl

It indicates your openssl library was not installed expectedly, you can try below command to fix:

$ brew link openssl --force

Run examples

After installing the SDK, you are able to run the examples under /demo folder. if you want to access private data, you need below additional two steps:

  1. Create an API Key first from Huobi official website
  2. Assign your API access key and secret key in file /include/define.h as below:
#define APIKEY "hrf5gdfghe-e74bebd8-2f4a33bc-e7963"
#define SECRETKEY "fecbaab2-35befe7e-2ea695e8-67e56"

If you don't need to access private data, you can ignore the API key. Regarding the difference between public data and private data you can find details in Client section below.

There are different types of demo, refer below steps to run the 'market' demo

<In huobi_Cpp folder>
$ cd demo
$ cd market
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_PREFIX_PATH=/opt/cpprest-2.10.16/lib64/cmake/
 // You need to replace it with your own path
$ make

Client

In this SDK, the client is the struct to access the Huobi API. In order to isolate the private data with public data, and isolated different kind of data, the client category is designated to match the API category.

All the client is listed in below table. Each client is very small and simple, it is only responsible to operate its related data, you can pick up multiple clients to create your own application based on your business.

Data Category Client Privacy API Protocol
Reference ReferenceClient Public Rest
Market MarketClient Public Rest
Account AccountClient Private Rest
Wallet WalletClient Private Rest
Trade TradeClient Private Rest
SubUser SubUserClient Private Rest
Algo AlgoClient Private Rest
IsolatedMargin IsolatedMarginClient Private Rest
CrossMargin CrossMarginClient Private Rest
WebSocketMarket WebSocketMarketClient Public WebSocket
WebSocketAsset WebSocketAssetClient Private WebSocket v2
WebSocketOrders WebSocketOrdersClient Private WebSocket v2
WebSocketTrade WebSocketTradeClient Private WebSocket v2

Public and Private

There are two types of privacy that is correspondent with privacy of API:

Public client: It invokes public API to get public data (Reference data and Market data), therefore you can create a new instance without applying an API Key.

// Create a ReferenceClient instance
ReferenceClient client;

// Create a MarketClient instance
MarketClient client;

Private client: It invokes private API to access private data, you need to follow the API document to apply an API Key first, and pass the API Key to the init function

// Create an AccountClient instance with APIKey
AccountClient accountClient{APIKEY, SECRETKEY};

// Create a TradeClient instance with API Key
TradeClient tradeClient{APIKEY, SECRETKEY};

The API key is used for authentication. If the authentication cannot pass, the invoking of private interface will fail.

Rest and WebSocket

There are two protocols of API, Rest and WebSocket

Rest: It invokes Rest API and get once-off response, it has two basic types of method: GET and POST

WebSocket: It establishes WebSocket connection with server and data will be pushed from server actively. There are two types of method for WebSocket client:

  • Request method: The method name starts with "req-", it will receive the once-off data after sending the request.
  • Subscription: The method name starts with "sub-", it will receive update after sending the subscription.

Migrate from v1 and v2

Why v3

The major difference between v1 and v2 is that the client category.

In SDK v1, the client is categorized as two protocol, request client and subscription client. For example, for Rest API, you can operate everything in request client. It is simple to choose which client you use, however, when you have a client instance, you will have dozens of method, and it is not easy to choose the proper method.

The thing is different in SDK v2, the client class is categorized as seven data categories, so that the responsibility for each client is clear. For example, if you only need to access market data, you can use MarketClient without applying API Key, and all the market data can be retrieved from MarketClient. If you want to operate your order, then you know you should use TradeClient and all the order related methods are there. Since the category is exactly same as the API document, so it is easy to find the relationship between API and SDK. In SDK v2, each client is smaller and simpler, which means it is easier to maintain and less bugs.

Compared to SDK versions v1 and v2, due to changes and updates in the out and in parameters of many interfaces, in order to match the latest interface in and out parameter situation, v3 version has made adjustments and updates to the out and in parameters of more than 80 interfaces to ensure that requests can be correctly initiated and accurate response data can be obtained. Meanwhile, the v3 version has added over 130 new interfaces available for use, greatly expanding the number of available interfaces.

How to migrate

You don't need to change your business logic, what you need is to find the v1 or v2 request client and subscription client, and replace with the proper v3 client. The additional cost is that you need to have additional initialization for each v3 client.

Request example

Reference data

Exchange timestamp

ReferenceClient client;
cout << client.getTimestamp() << endl;

Symbols

ReferenceClient client;
vector<Symbol> symbols = client.getSymbols();

Currencies

ReferenceClient client;
vector<std::string> currencies = client.getCurrencies();

Currency & Chains

ReferenceClient client;
ReferenceCurrenciesRequest referenceCurrenciesRequest;
vector<ReferenceCurrencies> vec = client.getReferenceCurrencies(referenceCurrenciesRequest);

Market data

Candlestick

MarketClient client;
char *symbol = "btcusdt";
CandlestickRequest candlestickRequest;
candlestickRequest.symbol = symbol;
candlestickRequest.period = "1min";
vector<Candlestick> klines = client.getCandlestick(candlestickRequest);

Depth

MarketClient client;
DepthRequest depthRequest;
depthRequest.symbol = symbol;
Depth depth = client.getDepth(depthRequest);
cout << "ask price: " << depth.asks[0].price << endl;
cout << "bid price: " << depth.bids[0].price << endl;

Latest trade

MarketClient client;
TradeRequest tradeRequest{symbol};
vector<Trade> trade = client.getTrade(tradeRequest);

Historical Trade

MarketClient client;
HistoryTradeRequest historyTradeRequest;
historyTradeRequest.symbol = symbol;
vector<Trade> tradeHistory = client.getHistoryTrade(historyTradeRequest);
cout << "trade price: " << tradeHistory[0].price << endl;

Account

Authentication is required.

Account balance

AccountClient accountClient{APIKEY, SECRETKEY};
vector<Balance> balanceVec = accountClient.getBalance(12345);

Get Account History

AccountClient accountClient{APIKEY, SECRETKEY};
AccountHistoryRequest accountHistoryRequest{12345, "usdt"};
accountHistoryRequest.transactTypes = "trade";
accountHistoryRequest.size = 10;
vector<AccountHistory> accountHistory = accountClient.getHistory(accountHistoryRequest);

Wallet

Authentication is required.

Withdraw

WalletClient walletClient{APIKEY, SECRETKEY};
WithdrawCreateRequest withdrawCreateRequest;
withdrawCreateRequest.amount = "5";
withdrawCreateRequest.currency = "usdt";
withdrawCreateRequest.address = "xxxxx";
withdrawCreateRequest.chain = "trc20usdt";
withdrawCreateRequest.fee = "0.0";
long withdrawId = walletClient.withdrawCreate(withdrawCreateRequest);

Cancel withdraw

WalletClient walletClient{APIKEY, SECRETKEY};
long cancelwithdraw = walletClient.withdrawCancel(withdrawId);

Withdraw and deposit history

WalletClient walletClient{APIKEY, SECRETKEY};
QueryDepositWithdrawRequest queryDepositWithdrawRequest;
queryDepositWithdrawRequest.type = "withdraw";
vector<DepositWithdraw> record = walletClient.queryDepositWithdraw(queryDepositWithdrawRequest);
queryDepositWithdrawRequest.type = "deposit";
vector<DepositWithdraw> record = walletClient.queryDepositWithdraw(queryDepositWithdrawRequest);

Trading

Authentication is required.

Create order

TradeClient tradeClient{APIKEY, SECRETKEY};
PlaceOrderRequest placeOrderRequest;
placeOrderRequest.accountId = 12345;
placeOrderRequest.symbol = "htusdt";
placeOrderRequest.type = "buy-market";
placeOrderRequest.amount = "5.0";
placeOrderRequest.clientOrderId = "client_order-id";
long orderId = tradeClient.placeOrder(placeOrderRequest);

Cancel order

TradeClient tradeClient{APIKEY, SECRETKEY};
tradeClient.submitCancelOrder("order-id");

Cancel open orders

TradeClient tradeClient{APIKEY, SECRETKEY};
BatchCancelOpenOrdersRequest batchCancelOpenOrdersRequest;
batchCancelOpenOrdersRequest.accountId = accountId;
BatchCancelOpenOrders batchCancelOpenOrders = tradeClient.batchCancelOpenOrders(batchCancelOpenOrdersRequest);

Get order info

TradeClient tradeClient{APIKEY, SECRETKEY};
Order order = tradeClient.getOrder(orderId);

Historical orders

TradeClient tradeClient{APIKEY, SECRETKEY};
OrdersHistoryRequest ordersHistoryRequest;
std::vector<Order> historicalOrders = tradeClient.getOrdersHistory(ordersHistoryRequest);

Margin Loan

Authentication is required.

These are examples for cross margin

####Apply loan

CrossMarginClient crossMarginClient{APIKEY, SECRETKEY};
CrossMarginTransferOrApplyRequest crossMarginTransferOrApplyRequest{currency, amount};
long marginId = crossMarginClient.marginOrders(crossMarginTransferOrApplyRequest);

Repay loan

CrossMarginClient crossMarginClient{APIKEY, SECRETKEY};
std::string amount = "100.0";
crossMarginClient.repay(marginId, amount.c_str());

Loan history

CrossMarginClient crossMarginClient{APIKEY, SECRETKEY};
CrossMarginLoanOrdersRequest crossMarginLoanOrdersRequest;
vector<CrossMarginLoanOrder> crossMarginLoanOrders = crossMarginClient.getLoanOrders(crossMarginLoanOrdersRequest);

Subscription example

Subscribe trade update

websocketMarketClient client;
client.subTrade("htusdt", [](Trade trade) {
  cout << trade.price << endl;
  cout << trade.tradeId << endl;
});

Subscribe candlestick update

websocketMarketClient client;
client.subKline("htusdt", "1min", [](Candlestick candlestick) {
  cout << candlestick.amount << endl;
});

Subscribe order update

Authentication is required.

websocketOrdersClient client{APIKEY, SECRETKEY};
client.subOrders("htusdt", [](OrdersUpdate ordersUpdate) {
  cout << ordersUpdate.symbol << endl;
});

Subscribe account change

Authentication is required.

websocketAssetClient client{APIKEY, SECRETKEY};
client.subAccounts(1, [](AccountsUpdate accountsUpdate) {
  cout << accountsUpdate.changeType << endl;
});

huobi_cpp's People

Contributors

clarkjia avatar devin-y avatar eynzhang avatar huobiapi avatar huobistarlab avatar stonejiang208 avatar yxq212526 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

huobi_cpp's Issues

The server CPU is FULL

I subscribed to two contracts using the subMBPrefresh interface, and the CPU exploded,please resolve it,thank you.

wss api: TLS Short Read

在香港阿里云上测试 wss接口 报错TLS Short Read,但是可以收到OrderUpdate Msg
在本地机器通过代理测试 wss接口 也是同样的错误,无法收到OrderUpdate Msg
请问这是什么原因?
以下是在阿里云测试输出日志
server response: {"action":"sub","code":200,"ch":"orders#eosusdt","data":{}}
server response: {"action":"ping","data":{"ts":1595578819682}}
{"action":"pong","data":{"ts":"1595578819682"}}
disconnection...
exception Msg: TLS Short Read
server response: {"action":"req","code":200,"ch":"auth","data":{}}
server response: {"action":"sub","code":200,"ch":"orders#eosusdt","data":{}}
disconnection...
exception Msg: TLS Short Read
server response: {"action":"req","code":200,"ch":"auth","data":{}}
server response: {"action":"sub","code":200,"ch":"orders#eosusdt","data":{}}
server response: {"action":"push","ch":"orders#eosusdt","data":{"lastActTime":1595578842727,"remainAmt":"5","orderId":62678599055412,"clientOrderId":"","eventType":"cancellation","orderStatus":"canceled","symbol":"eosusdt","type":"buy-limit"}}
eosusdt
server response: {"action":"push","ch":"orders#eosusdt","data":{"lastActTime":1595578843685,"remainAmt":"5","orderId":62678691348965,"clientOrderId":"","eventType":"cancellation","orderStatus":"canceled","symbol":"eosusdt","type":"buy-limit"}}
eosusdt
disconnection...
exception Msg: TLS Short Read

Huobi::SubscriptionClientImpl::subscribeOrderUpdateV2 未定义

多个例子链接都会遇到 Huobi::SubscriptionClientImpl::subscribeOrderUpdateV2 符号未定义错误.
例如 SubscribeMarketBBOEvent

[100%] Linking CXX executable SubscribeMarketBBOEvent
/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/ld: /usr/local/lib/libHuobiClient.a(SubscriptionClientImpl.cpp.o):(.rodata._ZTVN5Huobi22SubscriptionClientImplE[_ZTVN5Huobi22SubscriptionClientImplE]+0x78): undefined reference to `Huobi::SubscriptionClientImpl::subscribeOrderUpdateV2(char const*, std::function<void (Huobi::OrderUpdateV2Event const&)> const&, std::function<void (Huobi::HuobiApiException&)> const&)'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/SubscribeMarketBBOEvent.dir/build.make:112: SubscribeMarketBBOEvent] Error 1
make[1]: *** [CMakeFiles/Makefile2:96: CMakeFiles/SubscribeMarketBBOEvent.dir/all] Error 2

Demo请求报错,能帮忙看下吗?

request请求已经做好了,但是好像 rapidjson 这里好像出了点问题!

This is the request: 
https://api.huobi.pro/market/history/kline?symbol=btcusdt&period=1min&size=100
This is the original response from the server: 

marketdemo: /home/yyy/GitPro/huobi_Cpp/include/rapidjson/document.h:1154: rapidjson::GenericValue<Encoding, Allocator>::MemberIterator rapidjson::GenericValue<Encoding, Allocator>::FindMember(const rapidjson::GenericValue<Encoding, SourceAllocator>&) [with SourceAllocator = rapidjson::MemoryPoolAllocator<>; Encoding = rapidjson::UTF8<>; Allocator = rapidjson::MemoryPoolAllocator<>; rapidjson::GenericValue<Encoding, Allocator>::MemberIterator = rapidjson::GenericMemberIterator<false, rapidjson::UTF8<>, rapidjson::MemoryPoolAllocator<> >]: Assertion `IsObject()' failed.

throwing exception when invoking createRequestClient

When I give the Access key and secret key in the createRequestClient API, I got the following error code:

terminate called after throwing an instance of 'Huobi::HuobiApiException'

is anyone has the same error ?

during make, prompted error with the 3rdparity libraries.

I think you should re-comile the 3rdparity library with -fPIC option.

[100%] Linking CXX executable unittest
/usr/bin/ld: ../../3rdparty/libdecnumber/lib/libdecnumber.a(decContext.o): relocation R_X86_64_32 against .rodata.str1.1' can not be used when making a PIE object; recompile with -fPIC /usr/bin/ld: ../../3rdparty/libdecnumber/lib/libdecnumber.a(decDouble.o): relocation R_X86_64_32S against symbol DPD2BIN' can not be used when making a PIE object; recompile with -fPIC
/usr/bin/ld: ../../3rdparty/libdecnumber/lib/libdecnumber.a(decQuad.o): relocation R_X86_64_32S against symbol `DPD2BIN' can not be used when making a PIE object; recompile with -fPIC

client.connect().wait error

client.connect(WEBSOCKET_HOST).wait error,in "webcosketMarketClient.cpp" and "webcocketHelper.cpp"
in VS2015 enviroment,said"cannot convert const char[23] to web::uri error",
说是从const char转换不到web::uri,
Please look at this,S2015 is too low?
and how can I fix this?
thanks a lot ,to all hands

asynchronous REST

hi,

does this library provide the ability to execute REST requests asynchronously, like this ?

thanks.

websocketMarketdemo运行失败

我下载了master的代码,cmake&make之后,运行websocketMarketdemo 出现异常。

root@xxx:/home/xxxx/websocketMarket/build# ./websocketMarketdemo
market.htusdt.kline.1min
enter any key to quit
terminate called after throwing an instance of 'web::websockets::client::websocket_exception'
what(): set_fail_handler: 9: Timer Expired
terminate called recursively
Aborted (core dumped)

这个是因为什么原因造成的?是URL错误吗?

demo Aborted

An error is reported after the demo has been running for a short time,maybe 1min or something.

websocketMarketdemo: /huobi/huobi_Cpp/include/rapidjson/document.h:1053: rapidjson::GenericValue<Encoding, Allocator>& rapidjson::GenericValue<Encoding, Allocator>::operator[](const rapidjson::GenericValue<Encoding, SourceAllocator>&) [with SourceAllocator = rapidjson::MemoryPoolAllocator<>; Encoding = rapidjson::UTF8<>; Allocator = rapidjson::MemoryPoolAllocator<>]: Assertion `false' failed.
Aborted

src/Logger.cpp 句柄用尽bug

logger.cpp 23行
src/Logger.cpp
应该是
if(log_fp == NULL) log_fp = fopen(log_file_locate.c_str(), "a");
原代码会导致每记录一条logger 新开一个文件句柄. 最终导致句柄用尽,程序挂起.

find a bug in SDK

In your WriteLog method, you call the fopen function while you don't call fclose function to close the FP and release the fd. The fd resourse will be exhausted after few hours.

need help--during make in examples, need pthread lib

when i made the examples, it has errors, like this, please help me

thanks.

===log===============================
~/Downloads/huobi_Cpp-master/examples/GetCandlestickData/build$ make
[ 50%] Linking CXX executable GetCandlestickData
/usr/bin/ld: /usr/local/lib/libHuobiClient.a(WebSocketWatchDog.cpp.o): undefined reference to symbol 'pthread_create@@GLIBC_2.2.5'
//lib/x86_64-linux-gnu/libpthread.so.0: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status
CMakeFiles/GetCandlestickData.dir/build.make:101: recipe for target 'GetCandlestickData' failed
make[2]: *** [GetCandlestickData] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/GetCandlestickData.dir/all' failed
make[1]: *** [CMakeFiles/GetCandlestickData.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2

Fatal Error While Compling include.h

fatal error: request/crossmargin/crossMarginGeneralReplayLoanOptionalRequest.h: No such file or directory
#include <request/crossmargin/crossMarginGeneralReplayLoanOptionalRequest.h>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

subscribe price depth

In my test, I found subscribePriceDepthEvent will send me data about every one second. Does it meas that the price depth data is not real-time( I mean it will send me data as soon as there has a new trade)?

wss Market 接口报错:: basic_string::_M_construct null not valid

websocketMarketClient.cpp market()函数如下代码块触发异常
string msg = client.receive().then([](websocket_incoming_message in_msg) {
char buf[BUFF] = {0};
unsigned int l = BUFF;
in_msg.body().streambuf().getn((unsigned char *) buf, l);
char sbuf[BUFF] = {0};
gzDecompress(buf, in_msg.length(), sbuf, BUFF);
return sbuf;
}).get();
此处sbuf返回的是 正确的buff 但是,get()返回的对象转换成string报错 basic_string::_M_construct null not valid
测试机器 阿里云ubuntu16
编译器版本gcc7.5
第三方库版本 cpprest采用apt-get install安装

Any plan to support windows?

I would like to use SDK on windows. Please hint me about Any part of code rely specially on linux? Maybe I can try to make it compatible on windows.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.