BINANCEのAPIをphpで動かす

前回のCoingeckoの価格表示の延長作業で、Binanceの取引スクリプトを作れないか(ボタン1回で売買できる簡易なもの)という依頼がございましたので、同様に簡易なPHPスクリプトを作りましたので、ご紹介します。
パブリックなAPIに比べて、キーやタイムスタンプなど多少追加のパラメータが必要になってきますが、それぞれ関数にまとめておき、その都度コールするという仕組みになります。

BinanceAPIを使えるようにする

まずはAPIを使えるようにBinanceで準備が必要になりますので、ログイン後に許可をしておきます。公式ページの説明を参考にしてください。
また、API作成時の注意点として下記のように、APIkeyとSecretKeyはメモを取っておきます(ApiKeyは後から編集画面で確認できますが、SecretKeyの文字列は確認できないようになってます)
Enable Spot & Margin Tradingにチェックを入れると、APIで取引を操作できるようになります。(作成時にはチェックが入ってません)また、これはAPIを作成して90日間だけ有効になりますので、90日後はまたチェックを入れることで引き続き使えます。

キーをサーバーに置きたくない(cookieから呼び出す)

上記のAPIkeyとSecretKeyはBinanceへアクションを取るごとに必要になりますが、共有サーバー内にこれらのコードが記載されたファイルを置いておくのは不安なので、下記のようにキーだけローカル上でブラウザのcookieに記録させておきます。ブラウザにAPIキーが無い場合は初回だけ入力フォームが表示されますので、上記で取得したAPIkeyとSecretKeyをcookieへ登録します。

<?php

//phpのURL
$bnc_url = "bnc.php";

//API & SEC設定(フォームからのデータをブラウザのクッキーへ登録)
if ($_POST["set"]){
setcookie("BINAPI",$_POST["api"],time()+60*60*24*365);
setcookie("BINSEC",$_POST["sec"],time()+60*60*24*365);
}

//API & SEC設定(クッキーから取得)
$apiKey = $_COOKIE["BINAPI"];
$secretKey = $_COOKIE["BINSEC"];

//API & SECが無い場合は登録フォーム表示
if (!$apiKey || !$secretKey){
print <<<EOF
<form action={$bnc_url} method=post>
<input type=hidden name=set value="set">
API:<input type=text name=api> <br>
SEC:<input type=text name=sec> <br>
<input type=submit value=SET>
</form>
EOF;
exit;
}
?>

タイムスタンプを取得する

APIを呼び出す時に必要なタイムスタンプは、サーバー同士の時間が正しいものでなければ弾かれてしまいますが、設置サーバーの時計に合わせて送信したとしても、ズレが生じます。
ここはBinanceのサーバーから直接取得し、それをタイムスタンプにします。

//タイムスタンプ取得・設定
$api_bin_file = file_get_contents("https://fapi.binance.com/fapi/v1/time");
$api_bin_object = json_decode($api_bin_file,TRUE);
$time_stamp = $api_bin_object["serverTime"];

各種オーダーの関数を作る

それではまず、売買に最低限必要な関数(新規発注、発注状況、ステータス、キャンセル)の4つの関数を作っておきます。発注は成り行きと指値で、発注状況は発注しているコインの詳細、ステータスは所持しているコインと発注しているコインの数、キャンセルは発注しているコインのキャンセル。ということになります。
なお、発注状況については発注していない場合は戻り値はありませんので、発注数と未発注数を両方取り出す場合、ステータスで取得したものをループで取り出すほうが1回で済みます。

//新規オーダー
function Order($symbol,$side,$type,$quantity,$price){
global $time_stamp;

$path = '/api/v3/order';
$query = 
'symbol='. $symbol .
'&side='. $side .
'&type='. $type .
'&timeInForce=' . "GTC".
'&quantity='. $quantity .
'&price='. $price .
'&timestamp=' . $time_stamp;
send_hash($path,$query,'POST');

}

//発注状況
function openOrders(){
global $time_stamp;

$path = '/api/v3/openOrders';
$query = 'timestamp=' . $time_stamp;
send_hash($path,$query,'GET');

}

//ステータス状況
function getall(){
global $time_stamp;

$path = '/sapi/v1/capital/config/getall';
$query = 'timestamp=' . $time_stamp;
send_hash($path,$query,'GET');

}

//オーダー全キャンセル
function cancelOrders($symbol){
global $time_stamp;

$path = '/api/v3/openOrders';
$query = 'symbol='. $symbol .'&timestamp=' . $time_stamp;
send_hash($path,$query,'DELETE');

}

APIを送信する共通の関数を作る

上記4つのパラメータを共通の関数を使って送信します。これはパラメータ受付(下処理分岐)→送信(POST送信かGET送信か)という流れで、2つの関数に分けておきます。
なお、送信後の返り値は全て$json_resに格納されていますので、これを処理することで所持数や発注数を確認できます。

//送信時の受付関数

function send_hash($path,$query,$method){
global $json_res;

if ($method == ""){
$url = 'https://api1.binance.com' . $path . '?' . $query;
}
else{
$sign = hash_hmac('SHA256', $query,$GLOBALS['secretKey']);
$url = 'https://api1.binance.com' . $path . '?' . $query ."&signature=" . $sign;
}
send($url,$method);
}


//送信関数(POST or GET or DELETE)

function send($url,$method){
global $json_res;

$headers = array(
"X-MBX-APIKEY:" . $GLOBALS['apiKey']
);

$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

if ($method == 'POST'){
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl,CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
}
elseif ($method == 'DELETE'){
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_URL, $url);
}
else{
curl_setopt($curl, CURLOPT_URL, $url);
}

$response = curl_exec($curl);
$json_res = json_decode($response , true);
curl_close($curl);

return $json_res;

}

トレード関数の呼び出し方

phpスクリプト内からクエリーを付けて、関数を呼び出す場合は以下のようになります。

BTCの成り行き買い注文(BTC_BUSDのペアで0.0001BTCを成り行き買い)
Order(‘BTCBUSD’,’BUY’,’MARKET’,0.0001,);

BTCの指値買い注文(BTC_BUSDのペアで40000ドルで0.0001BTCを指値買い)
Order(‘BTCBUSD’,’BUY’,’LIMIT’,0.0001,40000);

BTCの成り行き売り注文(BTC_BUSDのペアで0.0001BTCを成り行き売り)
Order(‘BTCBUSD’,’SELL’,’MARKET’,0.0001,);

BTCの指値売り注文(BTC_BUSDのペアで40000ドルで0.0001BTCを指値売り)
Order(‘BTCBUSD’,’SELL’,’LIMIT’,0.0001,40000);

BTCの指値買い・指値売り注文のキャンセル
cancelOrders(‘BTCBUSD’);

コイン所持数と発注数を表示させ、成り行き注文してみる

ここまででPHPでクエリを送信・トレードができますので、botなどで自動売買させる場合は良いのですが、ブラウザから表示させて確認する、呼び出し・表示・注文までのサンプルHTMLを作ってみます。
なお、発注可能数(対BUSDに対して何枚のコインを発注できるか)を行うため、各コインのティッカーを取得しますが、これは下記のようにコイン名のシンボルを指定するだけで取得できます。

//BTCの場合
$bin_api = file_get_contents("https://api1.binance.com/api/v3/ticker/price?symbol=BTCBUSD");
$bin_object = json_decode($bin_api,TRUE);

//$bin_object["price"]で価格取得し、小数点以下の余計な0を丸める
$bin_object["price"] = preg_replace("/\.?0+$/","",$bin_object["price"]);

//freeにあるBUSDでの発注可能数
$buy_quantity = floor($json_res[$i]["free"] / $bin_object["price"]);

【スクリプト使用のご注意】
上記までをまとめたサンプルのスクリプトはBUSDとBTCペア固定の取引となります。
lockedやfreeの所持数をクリックすると、発注・キャンセルの確認画面が出ますが、OKをクリックすると実際に発注されてしまいますので、テストは慎重に!

free = 所持しているコイン数
locked = 指値注文中のコイン数

新規買い注文:BUSDのfreeのコイン数クリックで全てBTCへ成り行きで注文します。
新規売り注文:BUSD以外のfreeのコイン数クリックで全てBUSDへ成り行きで注文します。
注文キャンセル:指値注文中のコインのlockedをクリックでfreeに戻り、注文はキャンセルされます。

<?php

//phpのURL
$bnc_url = "bnc.php";

//API & SEC設定(フォームからのデータをブラウザのクッキーへ登録)
if ($_POST["set"]){
setcookie("BINAPI",$_POST["api"],time()+60*60*24*365);
setcookie("BINSEC",$_POST["sec"],time()+60*60*24*365);
}

//API & SEC設定(クッキーから取得)
$apiKey = $_COOKIE["BINAPI"];
$secretKey = $_COOKIE["BINSEC"];

//API & SECが無い場合は登録フォーム表示
if (!$apiKey || !$secretKey){
print <<<EOF
<form action={$bnc_url} method=post>
<input type=hidden name=set value="set">
API:<input type=text name=api> <br>
SEC:<input type=text name=sec> <br>
<input type=submit value=SET>
</form>
EOF;
exit;
}

//タイムスタンプ取得・設定
$api_bin_file = file_get_contents("https://fapi.binance.com/fapi/v1/time");
$api_bin_object = json_decode($api_bin_file,TRUE);
$time_stamp = $api_bin_object["serverTime"];

/////////////////////

//新規オーダー
function Order($symbol,$side,$type,$quantity,$price){
global $time_stamp;

$path = '/api/v3/order';
$query = 
'symbol='. $symbol .
'&side='. $side .
'&type='. $type .
'&timeInForce=' . "GTC".
'&quantity='. $quantity .
'&price='. $price .
'&timestamp=' . $time_stamp;
send_hash($path,$query,'POST');
//パラメータでURLへ戻る
print <<<EOF
<script>
location.href= {$bnc_url}?t={$time_stamp};
</script>
EOF;
}

//オープンオーダー状況
function openOrders(){
global $time_stamp;

$path = '/api/v3/openOrders';
$query = 'timestamp=' . $time_stamp;
send_hash($path,$query,'GET');

}

//ステータス状況
function getall(){
global $time_stamp;

$path = '/sapi/v1/capital/config/getall';
$query = 'timestamp=' . $time_stamp;
send_hash($path,$query,'GET');

}

//オーダー全キャンセル
function cancelOrders($symbol){
global $time_stamp;

$path = '/api/v3/openOrders';
$query = 'symbol='. $symbol .'&timestamp=' . $time_stamp;
send_hash($path,$query,'DELETE');
//パラメータでURLへ戻る
print <<<EOF
<script>
location.href= {$bnc_url}?t={$time_stamp};
</script>
EOF;
}

//////////////////////////////////////////////////////////////
//送信時の受付関数

function send_hash($path,$query,$method){
global $json_res;

if ($method == ""){
$url = 'https://api1.binance.com' . $path . '?' . $query;
}
else{
$sign = hash_hmac('SHA256', $query,$GLOBALS['secretKey']);
$url = 'https://api1.binance.com' . $path . '?' . $query ."&signature=" . $sign;
}
send($url,$method);
}

//送信関数(POST or GET or DELETE)

function send($url,$method){
global $json_res;

$headers = array(
"X-MBX-APIKEY:" . $GLOBALS['apiKey']
);

$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

if ($method == 'POST'){
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl,CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
}
elseif ($method == 'DELETE'){
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_URL, $url);
}
else{
curl_setopt($curl, CURLOPT_URL, $url);
}

$response = curl_exec($curl);
$json_res = json_decode($response , true);
curl_close($curl);

return $json_res;

}

//////////////////////////////////////////////////////////////

//パラメータで関数を呼び出す(注文する)
if ($_GET['m'] == "order"){
Order($_GET['symbol'],$_GET['side'],$_GET['type'],$_GET['quantity'],$_GET['price']);
}

//パラメータで関数を呼び出す(キャンセルする)
if ($_GET['m'] == "cancel"){
cancelOrders($_GET['symbol']);
}

//BUSDとBTCペアで指定
$stable = "BUSD";
$pair = "BTC";

//ステータスの関数を呼び出して$json_resへ格納
getall();

//$json_resのステータスをチェック
for ($i=0;$i<count($json_res);$i++){

//未注文コイン数(free)
if ($json_res[$i]["free"] > 0){

if ($json_res[$i]["coin"] != $stable)
{
//コインの価格取得
$bin_api = file_get_contents("https://api1.binance.com/api/v3/ticker/price?symbol={$json_res[$i]["coin"]}{$stable}");
$bin_object = json_decode($bin_api,TRUE);
$bin_object["price"] = preg_replace("/\.?0+$/","",$bin_object["price"]);

//表示
print $json_res[$i]["coin"] . ' | locked : ' . $json_res[$i]["locked"] .
 ' / free : '. '<a href=' . $bnc_url . '?m=order&side=SELL&type=MARKET&quantity=' .
 $json_res[$i]["free"] . '&symbol=' . $json_res[$i]["coin"] .$stable .
 ' onclick="return confirm( '. "'" . $json_res[$i]["coin"] . ' -> BUSD OK?\')">' .
 $json_res[$i]["free"] . '</a>' .
 '<br>'; 
}
else
{
//コインの価格取得
$bin_api = file_get_contents("https://api1.binance.com/api/v3/ticker/price?symbol={$pair}{$stable}");
$bin_object = json_decode($bin_api,TRUE);

//$bin_object["price"]で価格取得し、小数点以下の余計な0を丸める
$bin_object["price"] = preg_replace("/\.?0+$/","",$bin_object["price"]);


//freeにあるBUSDでの発注可能数
$buy_quantity = floor($json_res[$i]["free"] / $bin_object["price"]);

//表示
print $json_res[$i]["coin"] . ' | locked : ' . $json_res[$i]["locked"] .
 ' / free : '. '<a href=' . $bnc_url . '?m=order&side=BUY&type=MARKET&quantity=' .
 $buy_quantity . '&symbol='. $pair .$stable .
 ' onclick="return confirm( '. "'" . $json_res[$i]["coin"] . ' -> BTC OK?\')">' .
 $json_res[$i]["free"] . '</a>' .
 '<br>'; 
}

}

//注文中コイン数(locked)
if ($json_res[$i]["locked"] > 0)
{
//表示

print $json_res[$i]["coin"] . ' | locked : ' . '<a href=' . $bnc_url . '?m=cancel&symbol=' . $json_res[$i]["coin"] .
 ' onclick="return confirm( '. "'" . $json_res[$i]["coin"] . ' CANCEL OK?\')">' .
 $json_res[$i]["locked"] . '</a>' .
 ' / free : '. $json_res[$i]["free"] .
 '<br>'; 
}


}

?>