Trading with REST, part 1

Many brokers and exchanges can nowadays be accessed online with a REST API that communicates with plain-text HTTP requests. The days of awkward proprietary broker APIs are coming to an end. This article is a step by step instruction of implementating a REST API interface in C for connecting a trading system to the Bittrex cryptocurrency exchange. It’s for the Zorro platform, but the principles are also valid for other exchanges and platforms. The C code for a basic REST API implementation is relatively short and straightforward.

 For connecting the Zorro platform to a particular broker API, a DLL must be dropped in its Plugin folder. The DLL is then automatically recognized and appears in Zorro’s Broker selection box. For trading with the broker, the DLL exports functions for getting prices and sending orders. These functions are standardized and described in the Zorro manual. They are the same for all brokers. The following 4 functions are the most important:

BrokerOpen – identify the DLL.
BrokerLogin – initialize and store login credentials.
BrokerAsset – retrieve price and other parameters of an asset.
BrokerBuy2 – send a buy or sell order.

These four are sufficient for running most trading strategies. The DLL can export additional functions that are useful, but not mandatory, since Zorro works around them when they are not implemented:

BrokerAccount -get the account state.
BrokerTrade – get the trade or order state.
BrokerHistory2 -get price history.
BrokerCommand – set special modes, get order book, etc.

Zorro users have written DLLs for several broker APIs, but the code is often redundant and invents the wheel many times again. I’ll give here an example of a relatively lean broker DLL that can be used as template for individual REST API implementations. Bittrex is a US based digital currency exchange that supports several hundred crypto currencies and provides free API access. We’re first going to implement the 4 mandatory functions, following the API description on the Bittrex website:

https://bittrex.github.io/api/v3

We will use the free Visual C++ Community Edition from Microsoft. Aside from the exported functions, we need some internal functions to print messages, send HTTP requests, parse a JSON response, and generate a digital signature. For all this there’s ready code available on the Internet, but there are also ready functions in Zorro’s library. To make life easier, we simply import the Zorro function library to the DLL. The VC++ setup for generating such a DLL is described here:

https://zorro-project.com/manual/en/dlls.htm

The setup is for a C++ trading strategy, but works as well for a broker API DLL. First we’re going to implement the adminstrative stuff, the BrokerOpen and BrokerLogin functions:

#define PLUGIN_TYPE 2
#define PLUGIN_NAME "Bittrex V3"
#define DLLFUNC extern "C" __declspec(dllexport)

int (__cdecl *BrokerMessage)(const char *Text);
int (__cdecl *BrokerProgress)(const int Progress);
...

DLLFUNC int BrokerOpen(char* Name,FARPROC fpMessage,FARPROC fpProgress)
{
strcpy_s(Name,32,PLUGIN_NAME);
(FARPROC&)BrokerMessage = fpMessage;
(FARPROC&)BrokerProgress = fpProgress;
return PLUGIN_TYPE;
}

The BrokerOpen function is Zorro specific. It sets up two function pointers for printing message and sending heartbeats to Zorro. It’s needed for recognizing the DLL in the Plugin folder and letting the plugin name, “Bittrex V3”, appear in the broker scrollbox. 

Next we need the login function that’s called at the begin and the end of any algo trading session

struct GLOBAL {
int PriceType,VolType,OrderType;
double Unit; // smallest trade unit
char Path[256]; // send path buffer
char Key[256],Secret[256]; // credentials
char Symbol[64],Uuid[256]; // last trade symbol and UUID
char AccountId[16]; // account currency
} G; // parameter singleton
...

DLLFUNC int BrokerLogin(char* User,char* Pwd,char* Type,char* Accounts)
{
if(User) { // login
memset(&G,0,sizeof(G));
strcpy_s(G.Key,User);
strcpy_s(G.Secret,Pwd);
return 1;
} else // logout
return 0;
}

This function only sets up the G struct, a singleton that holds all global variables of the DLL. The User and Pwd arguments contain the Bittrex key and secret. They are taken from the Zorro login fields or from the account list. Type is the account type, ignored here because Bittrex has no demo accounts.  Account is the account identifier and also ignored since we’re not using sub-accounts.

Next comes the function to retrieve prices and other parameters of a particular cryptocurrency. It’s a bit more complex and requires a HTTP request to the API. Some requests require authentication, some not. For all this we first implement a send() function in the DLL that uses Zorro’s functions for sending http requests and generating a HMAC signature. The authentication method by the Bittrex REST API V3 is a bit complex:

To properly sign an authenticated request for the Bittrex v3 API, the following headers must be included: Api-Key, Api-Timestamp, Api-Content-Hash, Api-Signature. Api-Timestamp is the current time as a UNIX timestamp in epoch-millisecond format. Api-Content-Hash is a SHA512 hash of the request body, Hex-encoded (with no request body, a SHA512 hash of an empty string). For creating the Api-Signature, first generate a string by concatenating the following items: Contents of the Api-Timestamp header; the full URI used to make the request, including query string; the HTTP method of the request, in all caps (GET, POST, DELETE, etc.); Contents of the Api-Content-Hash header. Sign this string via HmacSHA512, using the API secret as the signing secret. Hex-encode the result and populate the Api-Signature header with it.

This is the implementation:

#define RHEADER "https://api.bittrex.com/v3/"
...

int sleep(int ms)
{
Sleep(ms);
return BrokerProgress(0);
}

char* send(const char* Url,
int Mode = 0,
const char* Method = NULL,
const char* Body = NULL) { static char Url[1024], Header[2048], Signature[1024], Buffer1[1024*1024], Buffer2[2048]; *Signature = *Header = 0; sprintf_s(Url,"%s%s",RHEADER,Url); if (Mode & 1) { // Authentication required strcpy_s(Header, "Content-Type:application/json"); strcat_s(Header, "\nAccept:application/json"); strcat_s(Header, "\nApi-Key: "); strcat_s(Header,G.Key); strcat_s(Header, "\nApi-Timestamp: "); __time64_t Time; _time64(&Time); static __time64_t Offset = 0; char* TimeStamp = i64toa(Time*1000 + Offset++); strcat_s(Header, TimeStamp); strcat_s(Header, "\nApi-Content-Hash: "); char* Hash = hmac(Body, 0, 0, 512); strcat_s(Header,Hash); strcpy_s(Signature, TimeStamp); strcat_s(Signature, Url); if(Method && *Method) strcat_s(Signature, Method); else if(!Body) strcat_s(Signature,"GET"); else strcat_s(Signature,"POST"); strcat_s(Signature, Hash); strcat_s(Header, "\nApi-Signature: "); strcat_s(Header, hmac(Signature, 0, G.Secret, 512)); } char* Response = Mode & 2? Buffer2 : Buffer1;
int MaxSize = Mode & 2? sizeof(Buffer2) : sizeof(Buffer1);
int Id = http_request(Url,Body,Header,Method); if(!Id) goto send_error; // wait 30 seconds for the server to reply int Size = 0, Wait = 3000; while (!(Size = http_status(Id)) && --Wait > 0) if(!sleep(10)) goto send_error; if (!Size) goto send_error; if(!http_result(Id,Response,MaxSize)) goto send_error; Response[MaxSize-1] = 0; // prevent buffer overrun http_free(Id); return Response;
// transfer unsuccessful? send_error: if(Id) http_free(Id); return NULL; }

The above code adds an authentication header with signature when Mode == 1. Mode == 2 selects a second, smaller response buffer and thus allows to evaluate two API responses at the same time. The sleep function generates a delay with a heartbeat to keep the Zorro window responsive during long requests. If it returns 0, someone has hit Zorro’s [Stop] key and all operations should be aborted. The Windows _time64 function has only 1-second resolution, so we add an incremented Offset for generating unique timestamps.

We’re now prepared to implement the BrokerAsset function:

double fixAmount(double Value)
{
int Exp = log10(Value);
if (Exp >= 0) Exp++;
return pow(10,Exp);
}

DLLFUNC int BrokerAsset(char* Symbol,double* pPrice,double* pSpread,
double *pVolume, double *pPip, double *pPipCost, double *pMinAmount,
double *pMarginCost, double *pRollLong, double *pRollShort, double *pCommission)
{
sprintf_s(G.Url,"markets/%s/ticker",fixSymbol(Symbol));
char* Response = send(G.Url,2);
if(!Response) return 0;
double Bid = strvar(Response, "bidRate", 0.),
Ask = strvar(Response,"askRate",0.);
if (Ask > 0. Bid > 0. && pSpread)
*pSpread = Ask - Bid;
double Last = strvar(Response, "lastTradeRate", 0.);
if(Ask == 0. || G.PriceType == 2)
Ask = Last;
if (Ask == 0.) return 0; // symbol does not exist
if(pPrice) *pPrice = Ask;
if(pVolume) {
sprintf_s(G.Url,"markets/%s/summary",fixSymbol(Symbol));
Response = send(G.Url,2);
if (Response) {
if (G.VolType == 4)
*pVolume = strvar(Response, "volume", 0);
else
*pVolume = strvar(Response, "quoteVolume", 0);
}
}
if (pMinAmount) { // get lot amount
sprintf_s(G.Url,"markets/%s",fixSymbol(Symbol));
Response = send(G.Url,2);
if (Response) {
*pMinAmount = fixAmount(strvar(Response,"minTradeSize",0.000001));
if (pPip) {
int Exp = strvar(Response,"precision",8);
*pPip = pow(10,-Exp);
while (*pPip * *pMinAmount < 0.000000001)
*pPip *= 10; // avoid too small pip cost
if (pPipCost)
*pPipCost = *pPip * *pMinAmount;
}
}
}
if (pMarginCost)
*pMarginCost = -100; // no leverage
return 1;
}

This function is supposed to return current price, current ask-bid spread, and current volume. It can also optionally request other asset specific parameters when available from the API. Otherwise Zorro will replace them with parameter values from the asset list. Broker APIs rarely provide all requested parameters. Make sure to only calculate and fill a parameter when its pointer passed to the function is nonzero. Most of the pointers are NULL most of the time.

The send function uses the second buffer because we’ll need price requests internally for calculating the account state. For getting lot amount, pip size, and pip cost, two more API requests are needed. The values from the response string are parsed with Zorro’s strvar function. The fixAmount function converts a value to its next-higher power of 10 – for instance, 7 is converted to 10 and 0.07 to 0.1. We’re doing this because we don’t want a strange number for the lot amount. The pip size is calculated from the precision, but we prevent it from going too small. Bittrex has no leverage, so the margin cost is always 100%.    

The next and final function sends an order to the API:

DLLFUNC int BrokerBuy2(char* Symbol,int Volume,double StopDist,double Limit,double *pPrice,int *pFill)
{
if(!isConnected() || !Volume) return 0;

// compose the body
char Body[256] = "{\n";
strcat_s(Body,"\"marketsymbol\": \"");
strcat_s(Body,fixSymbol(Symbol));
strcat_s(Body,"\",\n\"direction\": \"");
strcat_s(Body,Volume > 0? "BUY" : "SELL");
strcat_s(Body,"\",\n\"type\": \"");
strcat_s(Body,Limit > 0. ? "LIMIT" : "MARKET");
strcat_s(Body,"\",\n\"quantity\": \"");
double Size = labs(Volume);
if(G.Amount < 1.) Size *= G.Amount;
strcat_s(Body,ftoa(Size));
if (Limit > 0.) {
strcat_s(Body,"\",\n\"limit\": \"");
strcat_s(Body,ftoa(Limit));
}
strcat_s(Body,"\",\n\"timeInForce\": \"");
if ((G.OrderType&2) && Limit > 0.)
strcat_s(Body,"GOOD_TIL_CANCELLED"); // limit orders only
else if (G.OrderType&1)
strcat_s(Body,"FILL_OR_KILL"); // fill all or nothing
else
strcat_s(Body,"IMMEDIATE_OR_CANCEL");
strcat_s(Body,"\"\n}");

char* Response = send("orders",1,0,Body);
if(!Response) return 0;

char* Uuid = strtext(Response,"id","");
if(!*Uuid) return 0; // failed
strcpy(G.Uuid,Uuid);
double Filled = strvar(Response,"fillQuantity",0);
if(Filled == 0. && !(G.OrderType&2))
return 0; // unfilled FOK/IOC order
double Price = strvar(Response,"proceeds",0);
if (Filled > 0. && Price > 0. && pPrice)
*pPrice = Price/Filled;
if (G.Unit < 1.) Filled *= G.Unit;
if (*pFill) *pFill = Filled;
return -1;
}

The function first composes a message body in this JSON format (example):

{
"marketSymbol": "ETH/BTC",
"direction": "BUY",
"type": "LIMIT",
"quantity": "0.1",
"limit": "0.008",
"timeInForce": "IMMEDIATE_OR_CANCEL"
}

If the order is accepted, the response has this JSON format:

{
"id": "12345-6789-007-4711",
"marketSymbol": "ETH/BTC",
"direction": "BUY",
"type": "LIMIT",
"quantity": "0.1",
"limit": "0.008",
"timeInForce": "IMMEDIATE_OR_CANCEL",
"fillQuantity": "0.05",
"commission": "0.000002",
"proceeds": "0.00001",
"status": "CLOSED"
}

The relevant fields, especially the id, the fill amount, and the fill price, are parsed from the response and returned. The return value -1 indicates to Zorro that the order generated no identifer number, but an UUID. It was stored and can be retrieved with a subsequent command. G.Unit is the lot amount that was generated with the previous BrokerAsset call. 

In part 2 of this series we’ll implement the remaining functions BrokerTrade, BrokerAccount, BrokerHistory2, and BrokerCommand. We’ll then have everything together for a relatively complex and rich broker API implementation. The full source code of the Bittrex DLL is available in the Source folder of the latest Zorro beta version. 

10 thoughts on “Trading with REST, part 1”

  1. Very interesting article indeed. Do you really think that, for example, the IB-API’s days are close to an end and changing in this new way? I’ve been trying to rebuild that API in C but since I’m still a hobbyist programmer, my speed is slow.

    Anyway, thanks for your always interesting articles

  2. This plugin can be lean because 1) it only uses REST, and 2) the plugin now has access to all Zorro functions via pointer array, so it can use included JSON parsers and the new hmac function.
    Unfortunately, when complications arise, you have to throw more libraries at the solution. Usually, it’s websockets or HTTP chunk streaming over TLS. Once I had to integrate an HTTP localhost server into a plugin: a user logs in to a heavily authenticated web portal (verify it’s you, etc), and then the key gets forwarded to the localhost server.

  3. Yes. I found the Bittrex authentication already a bit exaggerated, but there are far worse solutions around where 90% of code is authentication and 10% is for trading.

  4. It will survive a null pointer and is not compiler dependent, with one exception. It won’t work with the lite-C compiler. In lite-C, it would be if(Method) if(*Method) …

  5. Hi jcl,

    thank you for the very informative article!

    You mention about JSON parsers – are there any examples how to use dataParseJSON / dataParseString to parse JSON files?
    As an example – say I would like to parse this simple string and save it into T2 file:
    {
    “lastUpdateId”:27447192609,
    “bids”:[[“16875.25000000″,”0.10063000”],[“16875.21000000″,”0.02025000”],[“16875.19000000″,”0.11852000”]],
    “asks”:[[“16875.69000000″,”0.68407000”],[“16875.91000000″,”0.01481000”],[“16876.34000000″,”0.00655000”]]
    }
    What would be the steps?

  6. dataParseJSON is for straight price data only. Your example looks like order book data. There is no high level function for order book data, so you’ll need to parse the values separately with strvar.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.