Progress on the Binance project, but help needed with internetpostrequest()

I'm trying to build out a trading system to auto trade on Binance. There have been loads of lessons and my coding skills have improved over the last 3 months. Recently, members of this forum have been so helpful in cracking the hmac signature for example.

Feel free to follow if it is useful to you, or skip

Summary

I'm at the stage now where I can manipulate returned strings to get the data out of binance that I need. Here's an example which works. The API's are only testnet APIs, so I have left there up here so that you can all see how the code is printed exactly, since having a quote mark out of place in a string knocks out the HMAC signature etc.

Also, you'll need OpenSSL 3.0 installed and added to PATH in the command prompt to make it work. Feel free to use the code below. The API documentation is here.

_SECTION_BEGIN("Test User Get Equity");
/*
Get the time (from , apikey, the BASE_URL, the url path (endpoint) and querystring.
Wallet endpoints for the query string are found at 
https://binance-docs.github.io/apidocs/spot/en/#wallet-endpoints. Note that time.txt is updated on and explore autorepeat every few seconds and writen to a file, time.txt, which is then refernced by the script when needed, as below. (Yes, I know I have taken out and then replace the same string,.... but it's working for now)

time =InternetOpenURL("https://api.binance.com/api/v3/time");
if( time )
{
     while( ( str = InternetReadString( time ) ) != "" )
     {
		extracted = StrMid(str,14,13);
         printf( "%s", extracted);
     }
     InternetClose( time );
}
fh = fopen( "C:\\Program Files\\AmiBroker\\Brokers\\Binance\\time.txt", "w");
if( fh )
{
   fputs("timestamp="+extracted, fh );
   fclose( fh );
} 

*/
time = fh = fopen("C:\\Program Files\\AmiBroker\\Brokers\\Binance\\time.txt","r");
		if( fh )
			{
			str = fgets(fh);
			extracted = StrMid(str,10);
			printf( "%s", extracted);
			fclose(fh);
			}

nameofuser = ParamStr("Name of User:","TestNet");
userpath = "C:\\Program Files\\AmiBroker\\Brokers\\Binance\\"+nameofuser;
fmkdir(userpath);

apikey = ParamStr("API Key : ","rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp");
apisecret = ParamStr("API Secret : ", "wnjAuvzTt8vMrLDulLOQdOeRTCKctNYlYf0YdrleDAv6MUcVrj8cDJEPtzZfKarF");
BASE_URL = ParamStr("BASE URL of API","https://testnet.binance.vision");
url_path = ParamStr("Endpoint (include '/' at beginning) ","/api/v3/account");
query_string = ParamStr("Query String/Parameter","&recvWindow=10000&timestamp=") + extracted;

/*
Write a file "instring.txt" which contains the query string. 
Note that for signed APIs, at least timestamp must always be written to file but it is good to specify a recvwindow
also, after which the request should no longer be valid (in case of a delay getting the order to the broker)
Use shellexecute to hash the input of that file with the secret key and outoput that to file "outsig.txt" 
*/
fh = fopen( userpath + "\\instring.txt", "w");
if( fh )
{
   fputs(query_string, fh );
   fclose( fh );
} 
ShellExecute("openssl", " dgst -sha256 -hmac "+ apisecret +" -out outsig.txt  instring.txt", userpath, 0);

/*
Pause to allow the script to complete writing "outsig.txt" 
*/
ThreadSleep(40);

/*
initialise signature variable. Define signature as the output from the file "outsig.txt".
Then remove "HMAC-SHA2-256(instring.txt)= " from that string using the StrReplace command
Define the URL by concatenating the base url, the url path (endpoint), "?", the query string, then 
"&signature=" and append it with your signature. Set header using the API key, then open the url, read
and print string to output.
*/
signature = "";
fh = fopen(userpath + "\\outsig.txt", "r");
if( fh )
{
   if( ! feof( fh ) )
   {
    signature = fgets( fh ); 
    signature = StrReplace(signature,"HMAC-SHA2-256(instring.txt)= ","");
    url = BASE_URL + url_path + "?" + query_string + "&signature=" + signature;
      
    InternetSetHeaders("X-MBX-APIKEY: " + apikey);
    io = InternetOpenURL(url);
    str = InternetReadString(io);
    printf( "%s", str );
	InternetClose( io );
	}
}
fclose(fh);
ThreadSleep(100);

fh = fopen( userpath +"\\fullstring.txt", "w");
if( fh )
{
   fputs(str, fh );
 }
   fclose( fh );

bnb = StrExtract(str,2,'{');
btc = StrExtract(str,3,'{');
busd = StrExtract(str,4,'{');
eth = StrExtract(str,5,'{');
ltc = StrExtract(str,6,'{');
trx = StrExtract(str,7,'{');
usdt = StrExtract(str,8,'{');
xrp = StrExtract(str,9,'{');

forequity = (
				bnb + "\n" +
				btc + "\n" +
				busd + "\n" +
				eth + "\n" +
				ltc + "\n" +
				trx + "\n" +
				usdt + "\n" +
				xrp
			);

fh = fopen( userpath +"\\equity.txt", "w");
if( fh )
{
   fputs(forequity, fh );
 }
   fclose( fh );

So, where are we now?
We're at the stage where we can pull data and maniplate strings to get balances of different assets in order to be able to make some kind of calcualtion of equity. The string manipulation functions in AFL are great for what I need. I just write the outputs to files and grab what I need form those files. ( and in any case, I can't use JSON parse functions because my virtual machine won't process jscripts ,...a rabbit hole I have spent 40 hours on,..... for another day perhaps).

Now, I am trying to post buy orders and sell orders. I can write a string to a .py file then execute that, provided I have the binance connector library installed. But I am looking to try an keep it all in AFL.

My attempt

Summary
_SECTION_BEGIN("Post Buy Order");
/*
Get the time (from , apikey, the BASE_URL, the url path (endpoint) and querystring.
Wallet endpoints for the query string are found at 
https://binance-docs.github.io/apidocs/spot/en/#wallet-endpoints

*/
time = fh = fopen("C:\\Program Files\\AmiBroker\\Brokers\\Binance\\time.txt","r");
		if( fh )
			{
			str = fgets(fh);
			extracted = StrMid(str,10);
			printf( "%s", extracted);
			fclose(fh);
			}

nameofuser = ParamStr("Name of User:","TestNet");
userpath = "C:\\Program Files\\AmiBroker\\Brokers\\Binance\\"+nameofuser;
fmkdir(userpath);

apikey = ParamStr("API Key : ","rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp");
apisecret = ParamStr("API Secret : ", "wnjAuvzTt8vMrLDulLOQdOeRTCKctNYlYf0YdrleDAv6MUcVrj8cDJEPtzZfKarF");
BASE_URL = ParamStr("BASE URL of API","https://testnet.binance.vision");
url_path = ParamStr("Endpoint (include '/' at beginning) ","/api/v3/order");
query_string = ParamStr("Query String/Parameter","symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1&timestamp=") + extracted;

/*
Write a file "instring.txt" which contains the query string. 
Note that for signed APIs, at least timestamp must always be written to file
Use shellexecute to hash the input of that file with the secret key and outoput that to file "outsig.txt" 
*/
fh = fopen( userpath + "\\instring.txt", "w");
if( fh )
{
   fputs(query_string, fh );
   fclose( fh );
} 
ShellExecute("openssl", " dgst -sha256 -hmac "+ apisecret +" -out outsig.txt  instring.txt", userpath, 0);

/*
Pause to allow the script to complete writing "outsig.txt" 
*/
ThreadSleep(40);

/*
initialise signature variable. Define signature as the output from the file "outsig.txt".
Then remove "HMAC-SHA2-256(instring.txt)= " from that string using the StrReplace command
Define the URL by concatenating the base url, the url path (endpoint), "?", the query string, then 
"&signature=" and append it with your signature. Set header using the API key, then open the url and post parameters with internetpostrequest, read
and print returned string to output.
*/
signature = "";
fh = fopen(userpath + "\\outsig.txt", "r");
if( fh )
{
   if( ! feof( fh ) )
   {
    signature = fgets( fh ); 
    signature = StrReplace(signature,"HMAC-SHA2-256(instring.txt)= ","");
    url = BASE_URL + url_path + "?" + query_string + "&signature=" + signature;
    printf("%s", url);  
    InternetSetHeaders("X-MBX-APIKEY: " + apikey);
    io = InternetPostRequest(url, query_string);
    str = InternetReadString(io);
    printf( "%s", str );
	InternetClose( io );
	}
}
fclose(fh);

ThreadSleep(100);

fh = fopen( userpath +"\\fullstring.txt", "w");
if( fh )
{
   fputs(str, fh );
 }
   fclose( fh );

This is what I get returned

Screen Shot 2022-09-26 at 12.51.36 pm

and my output window...

1664168032919TestNet
C:\Program Files\AmiBroker\Brokers\Binance\TestNet
rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp
wnjAuvzTt8vMrLDulLOQdOeRTCKctNYlYf0YdrleDAv6MUcVrj8cDJEPtzZfKarF
https://testnet.binance.vision
/api/v3/order
symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1&timestamp=1664168032919

https://testnet.binance.vision/api/v3/order?symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1&timestamp=1664168032919&signature=869994ca83835c4621fb09f24034c917ef0e2be3b351f91f23fdc7e0901671e9

The contents of instring.txt are

symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1&timestamp=1664168032919

and of outsig.txt are

HMAC-SHA2-256(instring.txt)= 869994ca83835c4621fb09f24034c917ef0e2be3b351f91f23fdc7e0901671e9

I have contacted Binance and the url and the query are allegedly constructed correctly from what they can see, but they have no experience of Amibroker so don't know how to format that into a post request from internetpostrequest(). I did think I had followed the instructions, but obviously there is something I have missed.

Any advice and lessons offered are greatly appreciated as always.

Once I have it all up and working, I'll post the whole thing here so that others can make use of it.

Thanks once again.

Your code is incorrect. And it has nothing to do with AmiBroker, but with basic understanding how GET request differs from POST. Consult basic HTTP documentation to find the difference.
With POST request you send the parameters NOT as part of URL, but as part of "POST DATA", which is second argument of the function call, documented in the manual (with examples). URL in POST request should NOT have parameters.

http://www.amibroker.com/guide/afl/internetpostrequest.html

1 Like

Ok, I have changed my code, so that the parameters are in the post data arguments but I am still getting thrown an error.

time =InternetOpenURL("https://api.binance.com/api/v3/time");
if( time )
{
     while( ( str = InternetReadString( time ) ) != "" )
     {
		extracted = StrMid(str,14,13);
         printf( "%s", extracted);
     }
     InternetClose( time );
}
apikey = ParamStr("API Key : ","rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp");
apisecret = ParamStr("API Secret : ", "wnjAuvzTt8vMrLDulLOQdOeRTCKctNYlYf0YdrleDAv6MUcVrj8cDJEPtzZfKarF");

parameters = "symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1&timestamp="+extracted;

fh = fopen( "C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\instring.txt", "w");
if( fh )
{
   fputs(parameters, fh );
   fclose( fh );
} 
ShellExecute("openssl", " dgst -sha256 -hmac "+ apisecret +" -out outsig.txt  instring.txt","C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\" , 0);
ThreadSleep(40);
signature = "";
fh2 = fopen("C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\outsig.txt","r");
if( fh2 )
{
   signature = fgets(fh2);
   fclose( fh2 );
} 
signature = StrReplace(signature,"HMAC-SHA2-256(instring.txt)= ","");

InternetSetHeaders("X-MBX-APIKEY: rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp");
ih = InternetPostRequest( "https://testnet.binance.vision/api/v3/order?", parameters +"&signature="+signature);
if( ih )
{
    while( ( str = InternetReadString( ih ) ) != "" )
    {
    
       printf( "%s", str );
    }
    
    InternetClose( ih );
} 

I get Warning 507 Internet error: remote server returned HTTP status code 400

The output is

1664207224445rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp
wnjAuvzTt8vMrLDulLOQdOeRTCKctNYlYf0YdrleDAv6MUcVrj8cDJEPtzZfKarF
symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1&timestamp=1664207224445

09005e5bdb2760367aa45d0da815c2a0ce91d0ae0c96e7ea13ccc95aa41fe582

Where could I be going wrong? (The API key and secret are just a testnet, so can be used)

There isn't a ? in the api endpoint.

Thanks.

With the ? in place, I get status code 400 from Binance. Also, leaving hte ? in place on postman renders a succesful order.

Screen Shot 2022-09-27 at 10.41.14 am

So I have updated my code, so that parameters are sent as arguments and the URL and enpont are sent in the URL



time =InternetOpenURL("https://api.binance.com/api/v3/time");
if( time )
{
     while( ( str = InternetReadString( time ) ) != "" )
     {
		extracted = StrMid(str,14,13);
         printf( "%s", extracted);
     }
     InternetClose( time );
}
apikey = ParamStr("API Key : ","rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp");
apisecret = ParamStr("API Secret : ", "wnjAuvzTt8vMrLDulLOQdOeRTCKctNYlYf0YdrleDAv6MUcVrj8cDJEPtzZfKarF");

parameters = "symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1&timestamp="+extracted;

fh = fopen( "C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\instring.txt", "w");
if( fh )
{
   fputs(parameters, fh );
   fclose( fh );
} 
ShellExecute("openssl", " dgst -sha256 -hmac "+ apisecret +" -out outsig.txt  instring.txt","C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\" , 0);
ThreadSleep(40);
signature = "";
fh2 = fopen("C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\outsig.txt","r");
if( fh2 )
{
   signature = fgets(fh2);
   fclose( fh2 );
} 
signature = StrReplace(signature,"HMAC-SHA2-256(instring.txt)= ","");
[/details]


InternetSetHeaders("X-MBX-APIKEY: rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp");
//{{url}}/api/v3/order?symbol=BTCUSDT&side=SELL&type=MARKET&quantity=0.1&timestamp={{timestamp}}&signature={{signature}}
ih = InternetPostRequest( "https://testnet.binance.vision/api/v3/order", "?symbol=BTCUSDT&side=SELL&type=MARKET&quantity=0.1&timestamp="+extracted+"&signature="+signature);
if( ih )
{
    while( ( str = InternetReadString( ih ) ) != "" )
    {
    
       printf( "%s", str );
    }
    
    InternetClose( ih );
} 

Screen Shot 2022-09-27 at 10.44.29 am

It'c clearly something to do with the way I have constructed the code, but I don't understand what. I ahve read about the differenced between http POST vs GET, I have set my header, I have placed the parameters as arguments, and I have kept the url, in the URL argument of the function.
Furthermore, I have tried dropping the ? from the parameters and appending it to the URL, but still not dice.

I don't understand what I am doing wrong.

Because you are not reading properly.
In POST, specific to this API there is no ? either in url or parameters.

2 Likes

As @nsm51 noted, you have to be very precise with HTTP APIs. They would give you generic Error 400 (bad request), even if single character is not what they expect you to do.

Thanks.

It is not working with or without the ?.

I have spoken with their API customer support this evening. The chap there thinks it should be constructed like...

ih = InternetPostRequest( "https://testnet.binance.vision/api/v3/order"  , "symbol=BTCUSDT&side=SELL&type=MARKET&quantity=0.3&timestamp={{timestamp}}&signature={{signature}}" );

That, as far as I can see, is how I have constructed it. (Note, 'extracted' is a variable I defined in BinanceTime.afl and have placed in the Include folder in AFL, and works with my GET script. The key and secret are testnet and don't represent real funds)

_SECTION_BEGIN("TestUserBuy");
/////////////////////////////////////////////////////////////////////////////
#include <BinanceTime.afl>
/////////////////////////////////////////////////////////////////////////////
apikey = "rTVOlY4UilAoQyTJxllRYliPfrdIdK80jzFxV7IqxkvUevG6XBie8ZqKtw9f4mPp";
apisecret = "wnjAuvzTt8vMrLDulLOQdOeRTCKctNYlYf0YdrleDAv6MUcVrj8cDJEPtzZfKarF";
querystring = "symbol=BTCUSDT&side=SELL&type=MARKET&quantity=0.3&timestamp="+extracted;
////////////////////////////////////////////////////////////////////////////
fh = fopen( "C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\instring.txt", "w");
if( fh )
{
   fputs(querystring, fh );
   fclose( fh );
}
ShellExecute("openssl", " dgst -sha256 -hmac "+ apisecret +" -out outsig.txt  instring.txt","C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\" , 0);
/////////////////////////////////////////////////////////////////////////////
ThreadSleep(20);
////////////////////////////////////////////////////////////////////////////
signature ="";
fh = fopen("C:\\Program Files\\AmiBroker\\Brokers\\Binance\\TestNet\\outsig.txt", "r");
if (fh)
{
signature = fgets(fh);
signature = StrReplace(signature,"HMAC-SHA2-256(instring.txt)= ","");
fclose(fh); 
}
//////////////////////////////////////////////////////////////////////////

InternetSetHeaders("X-MBX-APIKEY: " + apikey);
ih = InternetPostRequest("https://testnet.binance.vision/api/v3/order","symbol=BTCUSDT&side=SELL&type=MARKET&quantity=0.3&timestamp="+extracted+"&signature="+signature);
//ih = InternetPostRequest( "https://testnet.binance.vision/api/v3/order/", "symbol=BTCUSDT&side=SELL&type=MARKET&quantity=0.3&timestamp="+extracted+"&signature="+signature);
if( ih )
{
    while( ( str = InternetReadString( ih ) ) != "" )
    {
       printf( "%s", str );
    }
    
    InternetClose( ih );
} 
1 Like

and just for reference
binancetime.afl is

Summary
time =InternetOpenURL("https://api.binance.com/api/v3/time");
if( time )
{
     while( ( str = InternetReadString( time ) ) != "" )
     {
		extracted = StrMid(str,14,13);
     }
     InternetClose( time );
}
1 Like

One advice:

Add _TRACE calls to your formulas. Use _TRACE to display both URL and Post parameters to find out what you are really sending.

To get better understanding of what is happening in your code and how functions work, use advice given here: How do I debug my formula?

So instead:

ih = InternetPostRequest( "https://testnet.binance.vision/api/v3/order?", parameters +"&signature="+signature);

Use

url = "https://testnet.binance.vision/api/v3/order?";
postparams = parameters +"&signature="+signature;
_TRACE("I am really using this URL: %s\nWith PostData:\n%s", url, postparams );

ih = InternetPostRequest( url, postparams );

1 Like

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.