えんじん

えんじん

バイクとお金とITと

なんだか使いづらそうなpoloniexのAPIを呼び出してみた

f:id:corbo93:20180106130601p:plain

多くの仮想通貨取引所が取引のレートや、実際に注文をすることができる機能を持たせたAPIを公開しています。

その中でもpoloniexの取引所は単にURLを公開していると言うよりは、プログラムを公開して、これを使ってね。

みたいなスタンス。

そのpoloniexのAPIを呼び出すプログラムを実装してみました!

スポンサーリンク

まずはAPIキーを発行する

f:id:corbo93:20180106131018j:plain

他の取引所では必要なかったのですが、poloniexではpublic APIを呼び出すのにもAPIキーが必要みたいです。

ちなみにここでは

public API : ログインの必要がない情報を取得するAPI(各通貨の価格など)

認証API : 本来ログイン後にのみ使用することができる機能(通貨の売買など)

てな感じで表現していきます(-∀-)!

APIキーの発行はそんなに難しくありません。

poloniexにアクセスし、右上のスパナマークをクリックすると、

ペロンとメニューがでてきます。

f:id:corbo93:20180106131418p:plain

ここの「API KEYS」を選択します。

送信されたメールのURLにアクセスすればAPIキーの発行が完了します!

f:id:corbo93:20180106132557p:plain

ここで注意なのはとりあえずAPI叩いてみたいよってレベルの人は必ず

「Enable Withdraws」にチェックをしないでください!

シークレットキーを発行

f:id:corbo93:20180106133142j:plain

先ほどのキー発行画面のAPIキーの下にシークレットキーを表示するボタンがあるのでそこから確認することができます。

このキーの扱いには十分注意してくださいね!

普段お金を送金させたりするときは、

poloniexにログインして、

多くの人が2段階認証の認証を行い、

画面を操作し、確認を行いながら

送金を行うかと思いますが、APIというのはURLリクエスト1つでサクッと送金ができてしまう道具です。

え?ログインもなしに?

と思った方もいるかと思いますが、そのログイン作業の代わりがシークレットキーというわけです。

逆に言えばシークレットキーが人にバレてしまうと、ログインして好きな操作していいよ!

と言っているようなものなのです。

前述ではありますが、それを防止するためにも「Enable Withdraws」にチェックをつけなければAPIから引き出しは行えないはずです。

ラッパークラスを引っ張ってくる

f:id:corbo93:20180106134224j:plain

下記のページからそれぞれ

PHP wrapper

Python wrapper

をコピーしてきます。

Poloniex - Bitcoin/Digital Asset Exchange - API

今回はサクッと作りたかったのでいつも使っているPHPを使いました。

ディレクトリ構成はこんな感じ。

├── coin
│   ├── poloniex
│   │   ├── getPoloniexData.php
│   │   └── poloniex.php
│   ├── shell
│   │   └── getPoloniexData.sh
│   └── util
│       └── util.php
└── mail
    └── mail.php

環境は

OS : macOS High Sierra

PHP : 7.1.7

とりあえず呼び出してみた

f:id:corbo93:20180106135107j:plain

各ファイルの中身はこんな感じです。

ラッパークラスは多少クラス名を変えたりしたので一応載っけときます。

・poloniex.php(ラッパークラス)

<?php
    // FINAL TESTED CODE - Created by Compcentral

    // NOTE: currency pairs are reverse of what most exchanges use...
    //       For instance, instead of XPM_BTC, use BTC_XPM

    class Poloniex {
        protected $api_key;
        protected $api_secret;
        protected $trading_url = "https://poloniex.com/tradingApi";
        protected $public_url = "https://poloniex.com/public";

        public function __construct($api_key, $api_secret) {
            $this->api_key = $api_key;
            $this->api_secret = $api_secret;
        }

        private function query(array $req = array()) {
            // API settings
            $key = $this->api_key;
            $secret = $this->api_secret;

            // generate a nonce to avoid problems with 32bit systems
            $mt = explode(' ', microtime());
            $req['nonce'] = $mt[1].substr($mt[0], 2, 6);

            // generate the POST data string
            $post_data = http_build_query($req, '', '&');
            $sign = hash_hmac('sha512', $post_data, $secret);

            // generate the extra headers
            $headers = array(
                'Key: '.$key,
                'Sign: '.$sign,
            );

            // curl handle (initialize if required)
            static $ch = null;
            if (is_null($ch)) {
                $ch = curl_init();
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_USERAGENT,
                    'Mozilla/4.0 (compatible; Poloniex PHP bot; '.php_uname('a').'; PHP/'.phpversion().')'
                );
            }
            curl_setopt($ch, CURLOPT_URL, $this->trading_url);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);

            // run the query
            $res = curl_exec($ch);

            if ($res === false) throw new Exception('Curl error: '.curl_error($ch));
            //echo $res;
            $dec = json_decode($res, true);
            if (!$dec){
                //throw new Exception('Invalid data: '.$res);
                return false;
            }else{
                return $dec;
            }
        }

        protected function retrieveJSON($URL) {
            $opts = array('http' =>
                array(
                    'method'  => 'GET',
                    'timeout' => 10
                )
            );
            $context = stream_context_create($opts);
            $feed = file_get_contents($URL, false, $context);
            $json = json_decode($feed, true);
            return $json;
        }

        public function get_balances() {
            return $this->query(
                array(
                    'command' => 'returnBalances'
                )
            );
        }

        public function get_open_orders($pair) {
            return $this->query(
                array(
                    'command' => 'returnOpenOrders',
                    'currencyPair' => strtoupper($pair)
                )
            );
        }

        public function get_my_trade_history($pair) {
            return $this->query(
                array(
                    'command' => 'returnTradeHistory',
                    'currencyPair' => strtoupper($pair)
                )
            );
        }

        public function buy($pair, $rate, $amount) {
            return $this->query(
                array(
                    'command' => 'buy',
                    'currencyPair' => strtoupper($pair),
                    'rate' => $rate,
                    'amount' => $amount
                )
            );
        }

        public function sell($pair, $rate, $amount) {
            return $this->query(
                array(
                    'command' => 'sell',
                    'currencyPair' => strtoupper($pair),
                    'rate' => $rate,
                    'amount' => $amount
                )
            );
        }

        public function cancel_order($pair, $order_number) {
            return $this->query(
                array(
                    'command' => 'cancelOrder',
                    'currencyPair' => strtoupper($pair),
                    'orderNumber' => $order_number
                )
            );
        }

        public function withdraw($currency, $amount, $address) {
            return $this->query(
                array(
                    'command' => 'withdraw',
                    'currency' => strtoupper($currency),
                    'amount' => $amount,
                    'address' => $address
                )
            );
        }

        public function get_trade_history($pair) {
            $trades = $this->retrieveJSON($this->public_url.'?command=returnTradeHistory&currencyPair='.strtoupper($pair));
            return $trades;
        }

        public function get_order_book($pair) {
            $orders = $this->retrieveJSON($this->public_url.'?command=returnOrderBook&currencyPair='.strtoupper($pair));
            return $orders;
        }

        public function get_volume() {
            $volume = $this->retrieveJSON($this->public_url.'?command=return24hVolume');
            return $volume;
        }

        public function get_ticker($pair = "ALL") {
            $pair = strtoupper($pair);
            $prices = $this->retrieveJSON($this->public_url.'?command=returnTicker');
            if($pair == "ALL"){
                return $prices;
            }else{
                $pair = strtoupper($pair);
                if(isset($prices[$pair])){
                    return $prices[$pair];
                }else{
                    return array();
                }
            }
        }

        public function get_trading_pairs() {
            $tickers = $this->retrieveJSON($this->public_url.'?command=returnTicker');
            return array_keys($tickers);
        }

        public function get_total_btc_balance() {
            $balances = $this->get_balances();
            $prices = $this->get_ticker();

            $tot_btc = 0;

            foreach($balances as $coin => $amount){
                $pair = "BTC_".strtoupper($coin);

                // convert coin balances to btc value
                if($amount > 0){
                    if($coin != "BTC"){
                        $tot_btc += $amount * $prices[$pair];
                    }else{
                        $tot_btc += $amount;
                    }
                }

                // process open orders as well
                if($coin != "BTC"){
                    $open_orders = $this->get_open_orders($pair);
                    foreach($open_orders as $order){
                        if($order['type'] == 'buy'){
                            $tot_btc += $order['total'];
                        }elseif($order['type'] == 'sell'){
                            $tot_btc += $order['amount'] * $prices[$pair];
                        }
                    }
                }
            }

            return $tot_btc;
        }
    }
?>

・getPoloniexData.php

<?php
require_once realpath(dirname(__FILE__)) . '/poloniex.php';
require_once realpath(dirname(__FILE__)) . '/../util/util.php';
require_once realpath(dirname(__FILE__)) . '/../../mail/mail.php';

class GetPoloniexData extends Poloniex {

    private $mailFlag;

    /**
     * コンストラクタ
     */
    public function __construct($argv) {
        $keys = Util::pickUpKeys($argv);
        $this->mailFlag = Util::isSendMail($argv);
        parent::__construct($keys['api'], $keys['sec']);
    }


    /**
     * メインロジック
     */
    public function execute(){
        try{

            // ユーザ情報を含むAPIの利用
            if(is_null($this->api_secret) === false){
                // 使いたくなったら使う
            }

            // publicAPIの利用
            if(is_null($this->api_key) === false){
                $ticker = $this->get_ticker();
            }

            if(is_null($ticker)){
                throw new Exception('APIからのデータが取得なーい!!');
            }

            if($this->mailFlag){
                $this->_sendMail($ticker);
            }

        }catch(Exception $e){
            echo "<エラーが発生しました!!!>\n\n";
            echo $e->getMessage() . "\n\n";
            exit(0);
        }
    }

    /**
     * メインロジック
     */
    private function _sendMail($retJson){
        $sendInfo = array();
        $sendInfo['to']      = ''; // 実行前にメールアドレスを入力する。
        $sendInfo['subject'] = 'poloniex info';
        $sendInfo['message'] = $this->_getMailText($retJson);

        $mail = new Mail();
        $mail->sendMail($sendInfo);
    }


    private function _getMailtext($retJson){
        $t = '';
        foreach($retJson as $key => $coinData){
            $t .= "・" . $key . "\n";
            $t .= "  quotVolume : " . $coinData['quoteVolume']. "\n";
            $t .= "  vaseVolume : " . $coinData['baseVolume']. "\n";
            $t .= "\n";
        }
        return $t;
    }
}

$poloniexApi = new getPoloniexData($argv);
$ret = $poloniexApi->execute();

exit(1);

・util.php

<?php
class Util{

    /**
     * APIkeyとsecret keyをオプションから取り出す処理
     * 今の所、poloniexのリクエスト用に使用
     * @return $key
     */
    public static function pickUpKeys($argv){
        $key = array();
        foreach($argv as $option){
            $tmp = explode('=', $option);
            $key[$tmp[0]] = $tmp[1];
        }
        return $key;
    }

    public static function isSendMail($argv){
        return in_array('--sendMail', $argv) ? true : false;
    }
}

・mail.php

<?php
class Mail{

    public function sendMail($sendInfo){

        $to         = isset($sendInfo['to'])        ? $sendInfo['to']         : null;
        $subject    = isset($sendInfo['subject'])   ? $sendInfo['subject']    : null;
        $message    = isset($sendInfo['message'])   ? $sendInfo['message']    : 'テスト送信です。';
        $addHeaders = isset($sendInfo['addHeader']) ? $sendInfo['addHeader']  : null;
        $addParam   = isset($sendInfo['addParam'])  ? $sendInfo['addParam']   : null;

        echo $message;

        // メール送信が可能かチェックする
        $this->_canSendMail($to, $subject);

        if(mb_send_mail($to, $subject, $message, $addHeaders, $addParam) == false){
            throw new Exception('メール送信失敗したー!!');
        }
    }

    private function _canSendMail($to, $subject){
        if($to == null){
            throw new Exception('宛先がないよ');
        }
        if($subject == null){
            throw new Exception('題名がないよ');
        }
        if(strpos(gethostname(), 'local')){
            throw new Exception('ローカルは送らないでおくね。');
        }
    }
}

・getPoloniexData.sh

#!/bin/sh
echo "<--call poloniex API-->\n"
/usr/bin/php ../poloniex/getPoloniexData.php  "$@"
echo "<--end poloniex API-->\n"

.shファイルを実行することでphpでレートを取得する流れになっているので、

やることはgetPoloniexData.shを実行するだけです。

sh getPoloniexData.sh api=ここにAPIキーを入力

オプションで取得結果をメール送信させる処理も入れましたが、

ローカル環境では多分動かないので今回は使わないでいきます。

細かいところがまだ未完成ではありますが、とりあえずmacのどこかにおけば動くとは思います。

まとめ

正直Javaをしっかりとやったことがないのでラッパークラスといってもいまいちピンときませんでした。(´・д・`)

アクセス修飾子を見る限りpublicがあったりprotectedがあったり、

ライブラリ的に使えばいいのか、はたまた継承すればいいのか。。。

とりあえず後者で実装してみました。

このバッチ自体は雑に作っちゃいましたが、他の取引所のデータも取得したりして少しまともなwebページを公開できたらなと思っていますのでお待ちくださいませー!

ではでは( *`ω´)ノ

スポンサーリンク