【AWS】bitflyer注文詳細取得

【きっかけ】

過去の記事参照
 
buffalokusojima.hatenablog.com

1. 使用するサービス

(AWS)

  • Lambda
  • IAM

(外部サービス)

bitflyer Lightning API


2. 概要
Lambdaを使用し、パラメータとして受け取った注文受付番号からbitflyerの注文詳細を取得します。

3. 実装

基本的にこれまでのコピペみたいなもんです。開発時間も数分もかからない程度になりました。
前回紹介したcloudformationを通してのデプロイをしているので、デプロイ自体もすぐ簡単に出来ます。

const ssm = new (require('aws-sdk/clients/ssm'))();
const request = require('request');
const crypto = require('crypto');

exports.handler = (event, context, callback) => {
     
    var body = JSON.parse(event.body);
    
    if(!body.id){
        console.log("body empty");
          callback(null, {
              statusCode: 400,
              body: JSON.stringify({message: "body empty"}),
              headers: {"Content-type": "application/json"}
            });
        return;
    }
    
    getParameterFromSystemManager('bitflyer-keys',callback)
    .then(function(data){
       
        const apikey = data.split(",")[0];
        const sercretKey = data.split(",")[1];
        
        var timestamp = Date.now().toString();
        var method = 'GET';
        var path = '/v1/me/getparentorder?&parent_order_acceptance_id='+body.id;
        
        var text = timestamp + method + path;
        var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex');
        
        var option = {
          url: 'https://api.bitflyer.jp' + path,
          method: method,
          headers: {
            'ACCESS-KEY': apikey,
            'ACCESS-TIMESTAMP': timestamp,
            'ACCESS-SIGN': sign,
            'Content-Type': 'application/json'
            }
        }
        
        return sendRequest(option, callback);
    }).then(function(data){
        
        if(data.response.statusCode != 200){
          console.error("Error:",data.response);
          callback(null, {
            statusCode: data.response.statusCode,
            body: JSON.stringify({message: data.response}),
            headers: {"Content-type": "application/json"}
          });
          return;
        }
        
        data = JSON.parse(data.body);
        
        if(data.length == 0){
            console.log('No data Found');
            callback(null,{
                statusCode: 200,
                body: JSON.stringify({message: 'No data Found'}),
                headers: {"Content-type": "application/json"}
            });
            return;
        }
        
        console.log(data)
        callback(null, {
            statusCode: 200,
            body: JSON.stringify({data: data}),
            headers: {"Content-type": "application/json"}
        });
        return;
    });
   
    function getParameterFromSystemManager(apikey_name, callback) {
    
        return new Promise(function (resolve) {
            var apikey = process.env[apikey_name];
            
            if(!apikey || typeof apikey == undefined){
            
                // Fetches a parameter called REPO_NAME from SSM parameter store.
                // Requires a policy for SSM:GetParameter on the parameter being read.
                var params = {
                    Name: apikey_name,
                    /* required */
                    WithDecryption:true
                };
                
                ssm.getParameter(params, function(err, apikey) {
                    if (err){
                        console.error(err.stack);
                        callback(null,{
                            statusCode: 500,
                            body: JSON.stringify({message: err.toString()}),
                            headers: {"Content-type": "application/json"}
                        });
                        resolve(null);
                        return;
                    }
                    process.env[apikey_name] = apikey.Parameter.Value;
                    resolve(apikey.Parameter.Value);
                });
            }else resolve(apikey);
        });
    }
    
    
    function sendRequest(option, callback){
        
        return new Promise(function (resolve) {
            request(option, function(error, response, body){
                    
                    if(error){
                        console.error(error);
                        callback(null,{
                            statusCode: 500,
                            body: JSON.stringify({message: error.toString()}),
                            headers: {"Content-type": "application/json"}
                        });
                        resolve(null);
                    }
                    var data = {response, body}
                    resolve(data);
            });
            });
    }
};

4. おわりに
簡単過ぎて、手抜き感ありますが、次回はこれのフロント実装を説明します。

【AWS】bitflyer注文一覧表示

きっかけ】

過去の記事参照
 
buffalokusojima.hatenablog.com


1. 使用するサービス

HTML, Javascript, CSS

2. 概要

前回バックエンド側で注文一覧を取得したところまでやりました。今回はそれらのHTML上での表示をやります。
前回の記事は以下になります。
buffalokusojima.hatenablog.com


3.実装

①見た目の部分

読み込み時に注文取得関数を呼び出し、データを受け取った後に描画する仕組みです。

<head>
        
    </head>
    <title>タイトル</title>
    <style>
       table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}

tr:nth-child(even) {
  background-color: #dddddd;
}

#orderDetail{
    display: none;
}

.orderDetailContent{
    height: 100px;
}

span{
    padding: 5px;
}
    </style>
    <body onload="getStatus()">
        <div id="positionArea"></div>
        <div id="openOrderArea"></div>

②読み込み時の呼び出し関数

getStatus内にデータ取得関数や後々作成される関数が入ります。
データ取得関数などAPIを叩く系は共通してsendRequestを呼び出す仕組みになっており、
それぞれの関数で与える引数と、データ取得後に呼び出される関数を渡します。
今回説明するデータ取得関数であるgetOrdersはデータ取得後にコールバック関数である
makeOrdersによって内容が描画される仕組みです。

  const URL = "API URL"

        function getStatus(){

            getOrders();
        }

        function getOrders(){

            var data = {
                "method": "GET",
                "url": URL + "GetOrders"
            }
   
            //引数はそれぞれ、API呼び出しの引数、コールバック関数、コールバック関数への引数
            sendRequest(data, makeOrderList, null);
        }

  function sendRequest(data, callback, calbackValue){

            var request = new XMLHttpRequest();
            request.open(data.method, data.url);
            request.setRequestHeader("Content-Type", "application/json");

            request.onload = function(err){
                console.log(err)
                if(request.readyState === 4){
                    if(request.status === 200){
                        console.log("success")
                        var res = JSON.parse(request.responseText)
                        console.log(res);
                        callback(res.body, calbackValue);
                    }
                    else{
                        console.error(request.status, request.response);
                    }
                }
            };

            request.onerror = function(err){
                console.log("err" + request.statusText);
            };

            console.log("sending")

            var body = null;

            if(data.body){
                body = {
                    "body": JSON.stringify(data.body)
                }
                body = JSON.stringify(body);
            }

            console.log(body);
            request.send(body);
        }

③取得したデータの描画

取得したデータの描画を動的に要素を作成することで実現します。
データが無かった場合はないことを伝える描画をします。

function makeOrderList(data){
            
            //取得したデータ
            data = JSON.parse(data);

   //描画対象となるdiv
            var targetDiv = document.getElementById("openOrderArea");
            if(targetDiv.firstElementChild){
                targetDiv.removeChild(targetDiv.childNodes[0]);
            }
            
            //データがなければメッセージを表示
            if(!data.data){
                var div = document.createElement('div');
                div.innerText="No Open Order";
                targetDiv.appendChild(div);
                return;
            }
            
    //テーブルのヘッダ作成 
            var table = document.createElement('table');
            table.id = 'orderListTable';
            var th = document.createElement('th');
            th.innerText = 'date';
            table.appendChild(th);
            th = document.createElement('th');            
            th.innerText = 'executed'
            table.appendChild(th);
            th = document.createElement('th');
            th.innerText = 'type';
            table.appendChild(th);
            th = document.createElement('th');
            table.appendChild(th);

            //データの数分、テーブルの中身を作成
            for(d of data.data){
                var tr = document.createElement('tr');
                var td = document.createElement('td');

                //注文日付のフォーマット整え
                var format_str = 'YYYY-MM-DD hh:mm:ss';
                var date;
                if(d.parent_order_date){
                    date = new Date(d.parent_order_date);
                }else if(d.child_order_date){
                    date = new Date(d.child_order_date);
                }
                format_str = format_str.replace(/YYYY/g, date.getFullYear());
                format_str = format_str.replace(/MM/g, date.getMonth()+1);
                format_str = format_str.replace(/DD/g, date.getDate());
                format_str = format_str.replace(/hh/g, date.getHours());
                format_str = format_str.replace(/mm/g, date.getMinutes());
                format_str = format_str.replace(/ss/g, date.getSeconds());
                
                td.innerText=format_str;

     //クリック時の動作関数を宣言(今回は未実装)
                td.onclick=openOrderDetail;

                //各種カラムにidを振っておく、後々の関数実装時に役に立つ
                if(d.child_order_id){
                    td.id = "child_id"+d.child_order_id;
                }else{
                    td.id = "parent_id"+d.parent_order_acceptance_id;
                }
                tr.appendChild(td);

     //サイズの描画
                var td = document.createElement('td');
                if(d.executed_size && d.executed_size > 0){
                    td.innerText = d.executed_size;
                }
                td.onclick=openOrderDetail;
                if(d.child_order_id){
                    td.id = "child_id"+d.child_order_id;
                }else{
                    td.id = "parent_id"+d.parent_order_acceptance_id;
                }
                tr.appendChild(td);

                //注文タイプの描画
                var td = document.createElement('td');
                if(d.parent_order_type){
                    td.innerText=d.parent_order_type;
                }else if(d.child_order_type){
                    td.innerText=d.child_order_type;
                }
                
                td.onclick=openOrderDetail;
                if(d.child_order_id){
                    td.id = "child_id"+d.child_order_id;
                }else{
                    td.id = "parent_id"+d.parent_order_acceptance_id;
                }
                
                tr.appendChild(td);
   
                //キャンセルボタンの実装
                var td = document.createElement('td');
                var input = document.createElement('input');
                input.type="button";
                input.value="cancel";
                if(d.child_order_id){
                    input.id = "child_id"+d.child_order_id;
                }else{
                    input.id = "parent_id"+d.parent_order_acceptance_id;
                }

     //キャンセル関数の呼び出し(今回は未実装)
                input.addEventListener('click', function(){
                    console.log("cancel:", this);
                    cancelOrder(d.product_code, this.id);
                })
                td.appendChild(input);
                tr.appendChild(td)
                if(d.child_order_id){
                    tr.id="child_id"+d.child_order_id;
                }else if(d.parent_order_acceptance_id){
                    tr.id = "parent_id"+d.parent_order_acceptance_id;
                }
                
                table.appendChild(tr);
            }
            targetDiv.appendChild(table)
        }


4. おわりに

以上で、注文一覧が表示されるようになりました。各種未実装の関数はまた次回以降実装していきます。
画面に表示してみるとこんな感じになります。

order_list
注文一覧

【AWS】bitflyer注文一覧取得

【きっかけ】

過去の記事参照
 
buffalokusojima.hatenablog.com


1. 使用するサービス

(AWS)

  • Lambda
  • IAM

(外部サービス)

bitflyer Lightning API


2. 概要

Lambdaを使用して、bitflyerから注文一覧を取得します。通常注文と特殊注文で叩くAPIのパスが異なるので、2回叩き、両方とも取得します。
API Gatewayに紐付けてブラウザ形式でAPIを叩くことが出来ます。詳細は前回記事参照
buffalokusojima.hatenablog.com


3. 実装
以下、Lambda実装

const ssm = new (require('aws-sdk/clients/ssm'))();
const request = require('request');
const crypto = require('crypto');

exports.handler = (event, context, callback) => {
    
    var apikey;
    var sercretKey;
    var returnData;
    
    // bitflyerのAPIKeyを取得(パラメータストアに事前登録必要)
    getParameterFromSystemManager('bitflyer-keys',callback)
    .then(function(data){
       
        apikey = data.split(",")[0];
        sercretKey = data.split(",")[1];
        
        var timestamp = Date.now().toString();
        var method = 'GET';
        var path = '/v1/me/getparentorders?product_code=FX_BTC_JPY&parent_order_state=ACTIVE';
        
        var text = timestamp + method + path;
        var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex');
        
        var option = {
          url: 'https://api.bitflyer.jp' + path,
          method: method,
          headers: {
            'ACCESS-KEY': apikey,
            'ACCESS-TIMESTAMP': timestamp,
            'ACCESS-SIGN': sign,
            'Content-Type': 'application/json'
            }
        }
        // 特殊注文取得
        return sendRequest(option, callback);
    }).then(function(data){
        
        if(data.response.statusCode != 200){
          console.error("Error:",data.response);
          callback(null, {
            statusCode: data.response.statusCode,
            body: JSON.stringify({message: data.response}),
            headers: {"Content-type": "application/json"}
          });
          return;
        }
        
        data = JSON.parse(data.body);
       
        console.log(data)
        
        // global変数に特殊注文を保持
        returnData = data;
        
        var timestamp = Date.now().toString();
        var method = 'GET';
        var path = '/v1/me/getchildorders?product_code=FX_BTC_JPY&child_order_state=ACTIVE';
        
        var text = timestamp + method + path;
        var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex');
        
        var option = {
          url: 'https://api.bitflyer.jp' + path,
          method: method,
          headers: {
            'ACCESS-KEY': apikey,
            'ACCESS-TIMESTAMP': timestamp,
            'ACCESS-SIGN': sign,
            'Content-Type': 'application/json'
            }
        }
        
        // 通常注文取得
        return sendRequest(option, callback);
    }).then(function(data){
        
        if(data.response.statusCode != 200){
          console.error("Error:",data.response);
          callback(null, {
            statusCode: data.response.statusCode,
            body: JSON.stringify({message: data.response}),
            headers: {"Content-type": "application/json"}
          });
          return;
        }
        
        data = JSON.parse(data.body);
        
        console.log(data)
       
        // 特殊注文と通常注文を結合
        returnData = returnData.concat(data)

        if(returnData.length == 0){
            console.log('No data Found');
            callback(null,{
                statusCode: 200,
                body: JSON.stringify({message: 'No data Found'}),
                headers: {"Content-type": "application/json"}
            });
            return;
        }
        callback(null, {
            statusCode: 200,
            body: JSON.stringify({data: returnData}),
            headers: {"Content-type": "application/json"}
        });
        return;
    });
   
    function getParameterFromSystemManager(apikey_name, callback) {
    
        return new Promise(function (resolve) {
            var apikey = process.env[apikey_name];
            
            if(!apikey || typeof apikey == undefined){
            
                // Fetches a parameter called REPO_NAME from SSM parameter store.
                // Requires a policy for SSM:GetParameter on the parameter being read.
                var params = {
                    Name: apikey_name,
                    /* required */
                    WithDecryption:true
                };
                
                ssm.getParameter(params, function(err, apikey) {
                    if (err){
                        console.error(err.stack);
                        callback(null,{
                            statusCode: 500,
                            body: JSON.stringify({message: err.toString()}),
                            headers: {"Content-type": "application/json"}
                        });
                        resolve(null);
                        return;
                    }
                    process.env[apikey_name] = apikey.Parameter.Value;
                    resolve(apikey.Parameter.Value);
                });
            }else resolve(apikey);
        });
    }
    
    
    function sendRequest(option, callback){
        
        return new Promise(function (resolve) {
            request(option, function(error, response, body){
                    
                    if(error){
                        console.error(error);
                        callback(null,{
                            statusCode: 500,
                            body: JSON.stringify({message: error.toString()}),
                            headers: {"Content-type": "application/json"}
                        });
                        resolve(null);
                    }
                    var data = {response, body}
                    resolve(data);
            });
            });
    }
};

SSMのパラメータストアへのAPIキー登録方法やその他実装方法の説明については過去記事参照
buffalokusojima.hatenablog.com

4. 以上で簡単にbitflyerから注文一覧を取得出来ます。次回はフロントエンド側の注文表示をやっていきたいと思います。

【AWS】bitflyer注文受付API Gateway実装(フロントエンド編)

【前回の記事】


1. 使用するサービス

前回作成したサービス(過去記事参照)
使用言語はnodeに合わせてjavascriptです。


2. 概要

前回作成したAPI Gatewayに対してHTTP通信を行うクライアント作成をします。ブラウザはChromeで、通信の実装はjavascriptで行います。


3. 実装

通常注文と特殊注文に分けて説明します。

3-1. 通常注文

①見た目の部分

見た目は簡単な部分だけ実装します。

<style>
        
        .orderArea{
            display: inline-block;
            position: relative;
            height: 80px;
        }

        #nomalOrder{
            display: flex;
        }

        .orderArea > .decideOrder{
            position: absolute;
            bottom: 0;
        }
        
    </style>
    <body>
        <div class="displayOrder">
            <div id="nomalOrder">
                <div id="Market" class="orderArea">
                        <div class="orderTitle">Market</div>
                        <div class="detailArea">
                            <span>
                                <label for="MarketOrderNumber">Num: </label>
                                <input type="text" id="MarketOrderNumber"/>
                            </span>
                        </div>
                        
                        <div class="decideOrder">
                            <input type="button" id="marketBuy" onClick="marketBuy()" value="BUY"/>
                            <input type="button" id="marketSell" onClick="marketSell()" value="SELL"/>
                        </div>
                </div>

                <div id="Limit" class="orderArea">
    
                        <div class="orderTitle">Limit</div>
                        <div class="detailArea">
                            <span>
                                <label for="LimitOrderNumber">Num: </label>
                                <input type="text" id="LimitOrderNumber"/>
                            </span>
                            <br>
                            <span>
                                <label for="LimitOrderPrice">Price: </label>
                                <input type="text" id="LimitOrderPrice"/>
                            </span>
                        </div>
                        <div class="decideOrder">
                            <input type="button" id="limitBuy" onClick="limitBuy()" value="BUY"/>
                            <input type="button" id="limitSell" onClick="limitSell()" value="SELL"/>
                        </div>
                    </div>
                </div>
            </div>
        </div>

②リクエスト送信メソッド

  const URL = "API GatewayのURL" // ステージのURLを貼り付け

        // 共通で使用するリクエストメソッド
        function sendRequest(data){

            var request = new XMLHttpRequest();

    // 引数にメソッドとURLを持してあるので、それらを入力
            request.open(data.method, data.url);
            request.setRequestHeader("Content-Type", "application/json");

            // 送信後の処理。特に画面表示等はない
            request.onload = function(err){
                console.log(err)
                if(request.readyState === 4){
                    if(request.status === 200){
                        console.log("success")
                        var res = JSON.parse(request.responseText)   
                        console.log(res);  
                    }
                    else{
                        console.error(request.status, request.response);
                    }
                }
            };

    // エラーの処理
            request.onerror = function(err){
                console.log("err" + request.statusText);
            };

            console.log("sending")

            var body = null;

            // リクエストに付随するbodyを入力
            if(data.body){
                body = {
                    "body": JSON.stringify(data.body)
                }
                body = JSON.stringify(body);
            }

            console.log(body);
            request.send(body);
        }

③Market注文メソッド
購入と売却のメソッドを実装します。どちらもタグから枚数を参照し、リクエスト送信メソッドに渡すbodyを

function marketBuy(){

            // 枚数参照
            var num = document.getElementById("MarketOrderNumber").value;
            if(!num){
                console.log("element empty");
                return;
            }
    
            // body作成
            var data = {
                "coin_pair": "FX_BTC_JPY",
                "type": "MARKET",
                "size": num,
                "side": "BUY"
            }

            // リクエスト送信メソッド呼び出し
            sendRequest(data);
        }

        // marketBuyと同様
        function marketSell(){
            var num = document.getElementById("MarketOrderNumber").value;
            if(!num){
                console.log("element empty");
                return;
            }

            var data = {
                "coin_pair": "FX_BTC_JPY",
                "type": "MARKET",
                "size": num,
                "side": "SELL"
            }
            sendRequest(data);
        }

④Limit注文

Market注文とほど同様で、違いは参照する値に価格が追加されただけです。

function limitBuy(){
            var num = document.getElementById("LimitOrderNumber").value;
            var price = document.getElementById("LimitOrderPrice").value;
            if(!num || !price){
                console.log("element empty");
                return;
            }

            var body = {
                "coin_pair": "FX_BTC_JPY",
                "type": "LIMIT",
                "size": num,
                "side": "BUY",
                "price": price
            }

            var data = {
                url: URL,
                method: 'POST',
                body: body
            }

            sendRequest(data);
        }

        function limitSell(){
            var num = document.getElementById("LimitOrderNumber").value;
            var price = document.getElementById("LimitOrderPrice").value;
            if(!num || !price){
                console.log("element empty");
                return;
            }

            var body = {
                "coin_pair": "FX_BTC_JPY",
                "type": "LIMIT",
                "size": num,
                "side": "SELL",
                "price": price
            }

            var data = {
                url: URL,
                method: 'POST',
                body: body
            }

            sendRequest(data);
        }

3-3. 特殊注文実装

通常注文とほとんど同様です。

①見た目の部分

<style>
        
        .orderArea{
            display: inline-block;
            position: relative;
            height: 200px;
        }

        #specialOrder{
            display: flex;
        }

        .orderArea > .decideOrder{
            position: absolute;
            bottom: 0;
        }
        
    </style>
    <body>
        <div class="displayOrder">
            <div id="specialOrder">
                <div id="ifd-buy" class="orderArea">
                    <div class="orderTitle">IFD BUY</div>
                    <br>
                    <div class="firstArea">
                        <div class="orderTitle">STOP BUY</div>
                        <div class="detailArea">
                            <span>
                                <label for="ifdFbuyNumber">Num: </label>
                                <input type="text" id="ifdFbuyNumber"/>
                            </span>
                            <br>
                            <span>
                                <label for="ifdFbuyPrice">Price: </label>
                                <input type="text" id="ifdFbuyPrice"/>
                            </span>
                        </div>
                    </div>
                    <br>
                    <div class="secondArea">
                        <div class="orderTitle">STOP SELL</div>
                        <div class="detailArea">
                            <span>
                                <label for="ifdSsellNumber">Num: </label>
                                <input type="text" id="ifdSsellNumber"/>
                            </span>
                            <br>
                            <span>
                                <label for="ifdSsellPrice">Price: </label>
                                <input type="text" id="ifdSsellPrice"/>
                            </span>
                        </div>
                    </div>
                        <div class="decideOrder">
                            <input type="button" id="ifdBuy" onClick="ifdBuy()" value="SUBMIT"/>
                        </div>
                </div>

                <div id="ifd-sell" class="orderArea">
                    <div class="orderTitle">IFD SELL</div>
                    <br>
                    <div class="firstArea">
                        <div class="orderTitle">STOP SELL</div>
                        <div class="detailArea">
                            <span>
                                <label for="ifdFsellNumber">Num: </label>
                                <input type="text" id="ifdFsellNumber"/>
                            </span>
                            <br>
                            <span>
                                <label for="ifdFsellPrice">Price: </label>
                                <input type="text" id="ifdFsellPrice"/>
                            </span>
                        </div>
                    </div>
                    <br>
                    <div class="secondArea">
                        <div class="orderTitle">STOP BUY</div>
                        <div class="detailArea">
                            <span>
                                <label for="ifdSbuyNumber">Num: </label>
                                <input type="text" id="ifdSbuyNumber"/>
                            </span>
                            <br>
                            <span>
                                <label for="ifdSbuyPrice">Price: </label>
                                <input type="text" id="ifdSbuyPrice"/>
                            </span>
                        </div>
                    </div>
                        <div class="decideOrder">
                            <input type="button" id="ifdSell" onClick="ifdSell()" value="SUBMIT"/>
                        </div>
                </div>

                <div id="stop" class="orderArea">
    
                    <div class="orderTitle">STOP</div>
                    <br>
                    <div class="detailArea">
                        <span>
                            <label for="StopOrderNumber">Num: </label>
                            <input type="text" id="StopOrderNumber"/>
                        </span>
                        <br>
                        <span>
                            <label for="StopOrderPrice">Price: </label>
                            <input type="text" id="StopOrderPrice"/>
                        </span>
                    </div>
                    <div class="decideOrder">
                        <input type="button" id="StopBuy" onClick="stopBuy()" value="BUY"/>
                        <input type="button" id="StopSell" onClick="stopSell()" value="SELL"/>
                    </div>
                </div>
            </div>
        </div>

②リクエスト送信メソッド

通常注文と同様です。

③ifdメソッド

BUYSELLとSELLBUYを記載します。これらもLimit注文とほど同様で、リクエスト送信メソッドに渡すbodyが異なるだけです。

function ifdSell(){
            var sell_num = document.getElementById("ifdFsellNumber").value;
            var sell_price = document.getElementById("ifdFsellPrice").value;

            if(!sell_num || !sell_price){
                console.log("element empty");
                return;
            }
            
            var buy_num = document.getElementById("ifdSbuyNumber").value;
            var buy_price = document.getElementById("ifdSbuyPrice").value;

            if(!buy_num || !buy_price){
                console.log("element empty");
                return;
            }

            var parameters = [
                {
                    'type': 'STOP',
                    'side': 'SELL',
                    'price': sell_price,
                    'size': sell_num
                },
                {
                    'type': 'STOP',
                    'side': 'BUY',
                    'price': buy_price,
                    'size': buy_num
                }
            ]

            var body = {
                'coin_pair': 'FX_BTC_JPY',
                'parameters': parameters,
                'order_method': 'IFD'
            };

            var data = {
                url: URL,
                method: 'POST',
                body: body
            }

            sendRequest(data);
        }

        function ifdBuy(){
            var buy_num = document.getElementById("ifdFbuyNumber").value;
            var buy_price = document.getElementById("ifdFbuyPrice").value;

            if(!buy_num || !buy_price){
                console.log("element empty");
                return;
            }
            
            var sell_num = document.getElementById("ifdSsellNumber").value;
            var sell_price = document.getElementById("ifdSsellPrice").value;

            if(!sell_num || !sell_price){
                console.log("element empty");
                return;
            }

            var parameters = [
                {
                    'type': 'STOP',
                    'side': 'BUY',
                    'price': buy_price,
                    'size': buy_num
                },
                {
                    'type': 'STOP',
                    'side': 'SELL',
                    'price': sell_price,
                    'size': sell_num
                }
            ]

            
            var body ={
                'coin_pair': 'FX_BTC_JPY',
                'parameters': parameters,
                'order_method': 'IFD'
            };

            var data = {
                url: URL,
                method: 'POST',
                body: body
            }
            
           sendRequest(data);
        }

④STOP注文

説明は割愛します。

function stopBuy(){
            var num = document.getElementById("StopOrderNumber").value;
            var price = document.getElementById("StopOrderPrice").value;
            if(!num || !price){
                console.log("element empty");
                return;
            }

            var parameters = [{
                "type": "STOP",
                "size": num,
                "price": price,
                "side": "BUY"
            }];

            var body ={
                'coin_pair': 'FX_BTC_JPY',
                'parameters': parameters,
                'order_method': 'SIMPLE'
            };

            var data = {
                url: URL,
                method: 'POST',
                body: body
            }

            sendRequest(data);
        }

        function stopSell(){
            var num = document.getElementById("StopOrderNumber").value;
            var price = document.getElementById("StopOrderPrice").value;
            if(!num || !price){
                console.log("element empty");
                return;
            }

            var parameters = [{
                "type": "STOP",
                "size": num,
                "price": price,
                "side": "SELL"
            }];

            var body ={
                'coin_pair': 'FX_BTC_JPY',
                'parameters': parameters,
                'order_method': 'SIMPLE'
            };

            var data = {
                url: URL,
                method: 'POST',
                body: body
            }

            sendRequest(data);
        }

4. おわりに

これで自作のbitflyer注文サイトが完成しました。見た目としては簡素ですが以下の様になります。

normalOrder
通常注文
specialOrder
特殊注文

実際に注文が通っていれば、bitflyerのご自身のアカウントの注文欄に追加されているはずです。
これでbitflyerのwebサーバに負荷がかかっている時でもAPIを叩くことで注文が比較的通りやすくなるかと思います。
※筆者は次の暴落時に検証してみようと思います。9月あたりに115万超えなければ60万あたりまで落ちると予想してます。暴落時にナンピンとか勝負する時はIFDでしっかり損切りできる注文を入れましょう。IFDOCOや自作注文も実装してみては面白いでしょう。

次回、注文は入れたけどキャンセル機能がまだなので、そこら辺を説明していきたいと思います。

【AWS】API Gateway cors template

【きっかけ】

過去の記事参照
 
buffalokusojima.hatenablog.com


1. 使用するサービス

(AWS)

  • CloudFormation


2. 概要

前回、作成したAPI GatewayをCloudFormationでテンプレート化するところをやります。
豆知識的な感じなんで一瞬で終わります。API Gatewayの機能でSwaggerファイルをエクスポートするだけです。
AWS公式とかでCORSのtemplate設定は書いてありますが、面倒な時に一発でCORS設定したPAI Gatewayをデプロイするのに便利な技です。
前回の記事は以下参照
buffalokusojima.hatenablog.com


3. 実践
AWSコンソールからAPI Gatewayを選択し、作成したAPI Gatewayのメニューに移ります。メニューから【Stage】を選択します。

apigateway_stage
ステージ選択

タブから【Export】を選択、真ん中の【Export as Swagger】から【YAML】を選択します。(JSONでもいいですが、YAMLで説明します。)

apigateway_export
Swaggerをエクスポート

エクスポートされたYAML形式のファイルが表示されるのでpathから以下をコピーします。

paths:
  /TestMethod:
    post:
      responses:
        200:
          description: "200 response"
          headers:
            Access-Control-Allow-Origin:
              type: "string"
      x-amazon-apigateway-integration:
        uri: "url"
        responses:
          default:
            statusCode: "200"
            responseParameters:
              method.response.header.Access-Control-Allow-Origin: "'*'"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        type: "aws"
    options:
      consumes:
      - "application/json"
      produces:
      - "application/json"
      responses:
        200:
          description: "200 response"
          headers:
            Access-Control-Allow-Origin:
              type: "string"
            Access-Control-Allow-Methods:
              type: "string"
            Access-Control-Allow-Headers:
              type: "string"
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: "200"
            responseParameters:
              method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            responseTemplates:
              application/json: "{}\n"
        requestTemplates:
          application/json: "{\n  \"statusCode\" : 200\n}\n"
        passthroughBehavior: "when_no_match"
        type: "mock"


ここからはtemplateを記述していきます。上で切り出した部分を後で貼りけます。

単純にAPI Gatewayを作成して、デプロイするには Type: AWS::ApiGateway::RestApiType: AWS::ApiGateway::Deploymentが必要になります。
さらに、Lambda呼び出しにType: AWS::Lambda::Permissionが必要になります。

実際にコードで例にします。

Resources:

  // Lambdaの呼び出しパーミッション
  TestPermissiondev:
      Type: AWS::Lambda::Permission
      Properties:
        Action: lambda:InvokeFunction
        Principal: apigateway.amazonaws.com
        FunctionName: !Ref TestFunction // 呼び出すLambdaの名前
        SourceArn: !Sub
          - >-
            arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/Test // メソッドPOSTにしてます。GETはここをGETに。
          - __Stage__: '*'
            __ApiId__: !Ref RestApi

  // API Gatewayのデプロイ
  ApiGatewayDeployment:
      Type: AWS::ApiGateway::Deployment
      Properties:
        RestApiId:
          Ref: RestApi // デプロイするAPIを設定
        Description: "apigateway deployment"
        StageName: "prod" // デプロイ名設定

  // API Gatewayの設定
  RestApi:
      Type: AWS::ApiGateway::RestApi
      Properties:
        Body:
          info:
            version: '1.0'
            title: !Ref 'AWS::StackName'
          paths:
                // さっきコピーした部分貼り付け
    

これでYAMLの形式エラー等がなければCloud Formationでデプロイ出来るはずです。
私がデプロイした時はcloud9からcodecommitにコミットしてcodepipeline上でのcloudformationでの
デプロイですが、上手くデプロイまでいけました。

【AWS】bitflyer注文受付API Gateway実装

【きっかけ】

過去の記事参照
 
buffalokusojima.hatenablog.com


1. 使用するサービス

(AWS)

  • Lambda

 
(外部サービス)


2. 概要

前回、作成したLambdaをAPI Gatewayに実装するところをやります。前回の記事は以下参照
buffalokusojima.hatenablog.com


3. 実装

3-1. API Gateway作成
AWSのコンソールメニューから【API Gateway】を選択し、【Create API】を選択します。

apigateway_create
API Gateway作成

3-2. RestAPI作成
メニューからRestAPIを選択します。今回はVPCを使用していないので、通常のRestAPIを選択します。【Build】で次へいきます。

RestAPI_create
RestAPI作成

3-3. RestAPI設定
デフォルトの設定で大丈夫です。【API name】だけ入力必須です。

RestAPI_edit
RestAPI設定

3-4. API Gatewayメニュー
これでAPI Gatewayのメニューが開くので、右のメニューから【Resources】を選択し、隣のフィールドの【Action】ボタンを押し、
アクションを選択します。アクションの中から【Create Method】を選択し、メソッドを作成します。

apigateway_menu
API Gateway メニュー

3-5. メソッド選択
【POST】を選択します。これは以前のLambdaでデータ受け取りをPOSTと想定している為です。

apigateway_post
【POST】選択

3-6. メソッド設定
メソッドの設定を行います。まずは主に呼び出しLambdaの指定です。【Integration Type】が【Lambda】になっていることを確認することと、【Use Lambda Proxy Integration】にチェックが入っていないことを確認します。今回の実装では統合をLambdaではなく、OPTIONSメソッドのMockで行うのでLambda Proxyは不要になります。これは後で記述します。
呼び出したいLambdaのリージョンを選択してからLambdaの名前を入力してもらえれば自動で補完されます。

apigateway_post_lambda
メソッド設定

3-7. CORS設定
次に【アクション】から【Enable CORS】を選択し、このメソッドのCORS設定を有効にします。

apigateway_cors_create
CORS設定

画面移動したらデフォルトのまま【Enable CORS and replace existing CORS headers】をクリックします。

apigateway_cors_create
CORS設定

次に出るポップアップも【Yes】を押すと自動で色々設定されます。【OPTIONS】メソッドが新たに作成されているのも確認できます。

apigateway_option_create
CORS設定

3-8. API Gatewayのデプロイ
Resourceのデプロイを行います。アクションから【Deploy】を選択してください。

apigateway_deploy
APIデプロイ

【Deploy Stage】は初回であれば【New Stage】とし、【Stage Name】を入力して【Deploy】を押してください。

apigateway_deploy_edit
APIデプロイ設定


すると、ステージメニューに遷移し、【Invoke URL】が表示されるので、curlなどで叩いてレスポンスが返ってくれば成功です。


3-9. 終わりに
簡単にAPI Gatewayの実装を説明しました。CORSはエラーがよく起こる設定なので、CORSエラーが出た時は正しいURLとパスなのか、レスポンスの設定は合っているかを確認してください。手動設定がめんどくさい人様にcloudformationのtemplate作成もいつかやります。
また、今回まともに疎通テストの部分をやっていないので、curlだけ出なく、クライアントも作成した記事を作ろうかと思います。

【AWS】bitflyer注文受付Lambda実装

【きっかけ】

過去の記事参照
 
buffalokusojima.hatenablog.com


1. 使用するサービス

(AWS)

  • Lambda
  • IAM
  • SSM
  • SQS   

 
(外部サービス)


2.概要

HTTP通信で受けた注文をbitflyerAPIへ注文を送信するLambdaを実装します。注文までの流れとしては、クライアントから来たPOSTに対して、
中身を確認し、注文として有効であればSQSにメッセージとして載せて、別Lambdaに飛ばします。SQSをトリガーとして、メッセージを受け取ったLambdaからbitflyerAPIに対して注文を送信します。
API Gatewayとの接続はまた別でやります。

architecture
構成図

3. 実装

3-1. 注文受付Lambda作成
以下コード入力

var AWS = require('aws-sdk');
var sqs = new AWS.SQS();

exports.handler = function(event, context, callback) {
    
    // 通常注文の場合のSQSURL
    const NORMAL_QUEUE_URL = 'normal queue url';

    // 特殊注文の場合のSQSURL
    const SPECIAL_QUEUE_URL = 'special queue url';

    const body = JSON.parse(event.body);
    
    if(!body){
        console.log("body empty");
          callback(null, {
              statusCode: 400,
              body: JSON.stringify({message: "body empty"}),
              headers: {"Content-type": "application/json"}
            });
        return;
    }
    
    const COIN_PAIR = body.coin_pair;
    
    const PRICE = body.price;
    
    const SIDE = body.side;
    
    const SIZE = body.size;
    
    const TYPE = body.type;
    
    const PARAMETERS = body.parameters;

    const ORDER_METHOD = body.order_method;
    
    if(!TYPE && !PARAMETERS){
        console.log("invalid order:",body);
          callback(null, {
              statusCode: 400,
              body: JSON.stringify({message: "invalid order"}),
              headers: {"Content-type": "application/json"}
            });
        return;
    }
    
    if(COIN_PAIR != 'FX_BTC_JPY'){
        console.log("invalid coin pair");
          callback(null, {
              statusCode: 400,
              body: JSON.stringify({message: "invalid order"}),
              headers: {"Content-type": "application/json"}
            });
        return;
    }
    
    var queueBody;
    var queueUrl;
    var id=new Date().getTime().toString();
    var groupId;
    
    // 注文内容によって構成する注文を変更
    if(!PARAMETERS){
        
        if(!checkElement(body)){
            console.log("Bad body:", body);
            callback(null, {
              statusCode: 400,
              body: JSON.stringify({message: "invalid order"}),
              headers: {"Content-type": "application/json"}
            });
            return;
        }
        queueBody = {
            product_code: COIN_PAIR,
            child_order_type: TYPE,
            side: SIDE,
            price: PRICE,
            size: SIZE,
        }
        if(TYPE == 'STOP'){
            queueBody.trigger_price = queueBody.price;
            queueBody.price = 0;
        } 
        queueUrl = NORMAL_QUEUE_URL;
        
        groupId = "NORMAL_ORDER";
    }else{
        if(ORDER_METHOD != "SIMPLE" && ORDER_METHOD != "IFD"){
            console.log("bad order method:");
            callback(null, {
                statusCode: 400,
                body: JSON.stringify({message: "invalid order"}),
                headers: {"Content-type": "application/json"}
            });
            return;
        }

        for(const parameter of PARAMETERS){
            if(!checkElement(parameter)){
                console.log("Bad body:", parameter);
                callback(null, {
                  statusCode: 400,
                  body: JSON.stringify({message: "invalid order"}),
                  headers: {"Content-type": "application/json"}
                });
                return;
            }
            parameter.product_code = COIN_PAIR
            parameter.condition_type = parameter.type;
            if(parameter.condition_type == 'STOP'){
                parameter.trigger_price = parameter.price;
                parameter.price = 0;
            } 
        }
        queueBody = {
            order_method: ORDER_METHOD,
            parameters: PARAMETERS
        }
        queueUrl = SPECIAL_QUEUE_URL;
        groupId = "SPECIAL_ORDER";
    }
        

    // SQS message parameters
    var params = {
        
        MessageBody: JSON.stringify(queueBody),
        MessageGroupId: groupId,
        MessageDeduplicationId: id,
        QueueUrl: queueUrl
    };
    
    console.log(params)

    // SQS送信
    sqs.sendMessage(params, function(err, data) {
        var responseCode = 200;
        
         // response and status of HTTP endpoint
        var responseBody = {
            message: ''
        };
    
        if (err) {
            console.log('error:', "failed to send message " + err);
            responseCode = 500;
        } else {
            console.log('data:', data.MessageId);
            responseBody.message = 'Sent to ' + queueUrl;
            responseBody.messageId = data.MessageId;
        }

        callback(null, {
            statusCode: responseCode,
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(responseBody)
        });
    });
    
    
    function checkElement(element){
        if(element.type == 'MARKET' || element.type == 'LIMIT'
        || element.type == 'STOP'){
                
            if(element.side != 'BUY' && element.side != 'SELL'){
                console.log("invalid side");
                return false;
            }
            
            
            element.price = Number(element.price);
            element.size = Number(element.size);
            if(isNaN(element.price) && element.type != 'MARKET'){
                console.log("number invalid:", element.price);
                return false;
            }
            if(isNaN(element.size)){
                console.log("number invalid:", element.size);
                return false;
            }
            
            return true;
        }
        return false;
    }
}

3-2. Lambdaにssm取得ロール付与、加えてSQSFullAccessも加えます。
role_attached
ロール付与

3-3. SQS受付Lambda実装

以下コード入力

const ssm = new (require('aws-sdk/clients/ssm'))();
const request = require('request');
const crypto = require('crypto');

exports.handler = (event, context, callback) => {
    
    // SQSからメッセージ取得
    var body = JSON.parse(event.Records[0].body);
    
    if(!body){
        console.log("Queue Empty")
        callback(null, {
                statusCode: 400,
                body: JSON.stringify({message: "Queue Empty"}),
                headers: {"Content-type": "application/json"}
            });
            return;
    }
    
    
    console.log(body);
    
    if(!body.order_method || !body.parameters){
        console.log("element invalid");
        callback(null, {
            statusCode: 400,
            body: JSON.stringify({message: "element invalid"}),
            headers: {"Content-type": "application/json"}
        });
        return;
    }
    console.log(body.parameters[0].price)
    
  // メッセージの内容をbitflyerAPIへ送信します
    getParameterFromSystemManager('bitflyer-keys', callback)
    .then(function(data){
        
        
        const apikey = data.split(",")[0];
        const sercretKey = data.split(",")[1];
    
        var timestamp = Date.now().toString();
        var method = 'POST';
        var path = '/v1/me/sendparentorder';
        
        
        
        body = JSON.stringify(body);
        console.log(body)
        var text = timestamp + method + path + body;
        var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex');
        
        var option = {
          url: 'https://api.bitflyer.com' + path,
          method: method,
          headers: {
            'ACCESS-KEY': apikey,
            'ACCESS-TIMESTAMP': timestamp,
            'ACCESS-SIGN': sign,
            'Content-Type': 'application/json'
            },
          body: body
        }
        
        return sendRequest(option, callback);
    }
    )
    .then(function(data){
        var message;
        if(data.response.statusCode == 200){
            message = data.body;
        }else{
            message = data.response;
        }
        console.log(message);
        callback(null, {
            statusCode: data.response.statusCode,
            body: JSON.stringify({message: message}),
            headers: {"Content-type": "application/json"}
        });
        return;
    });
        

    function getParameterFromSystemManager(apikey_name, callback) {
    
        return new Promise(function (resolve) {
            var apikey = process.env[apikey_name];
            
            if(!apikey || typeof apikey == undefined){
            
                // Fetches a parameter called REPO_NAME from SSM parameter store.
                // Requires a policy for SSM:GetParameter on the parameter being read.
                var params = {
                    Name: apikey_name,
                    /* required */
                    WithDecryption:true
                };
                
                ssm.getParameter(params, function(err, apikey) {
                    if (err){
                        console.error(err.stack);
                        callback(null,{
                            statusCode: 500,
                            body: JSON.stringify({message: err.toString()}),
                            headers: {"Content-type": "application/json"}
                        });
                        resolve(null);
                        return;
                    }
                    process.env[apikey_name] = apikey.Parameter.Value;
                    resolve(apikey.Parameter.Value);
                });
            }else resolve(apikey);
        });
    }
    
    function sendLine(message, callback){
        
        getParameterFromSystemManager('line-access-key', callback)
        .then(function(lineKey){
            
            var option = {
            url: 'https://notify-api.line.me/api/notify',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + lineKey
                },
            method: 'POST',
            form :{
              message: message
            }
            };
        
            return sendRequest(option,callback);
        })
        .then(function(data){
          callback(null, {
            statusCode: 200,
            body: data,
            headers: {"Content-type": "application/json"}
          });
        });
    }
    
    function sendRequest(option, callback){
        
        return new Promise(function (resolve) {
            request(option, function(error, response, body){
                    
                    if(error){
                        console.error(error);
                        callback(null,{
                            statusCode: 500,
                            body: JSON.stringify({message: error.toString()}),
                            headers: {"Content-type": "application/json"}
                        });
                        resolve(null);
                    }
                    var data = {response, body}
                    resolve(data);
            });
            });
    }
}

3-4. Lambdaにロール付与

3-2と内容は同じで、このLambdaにもロールを付与します。

3-5. SQSの作成

今回は順序と2重注文を防ぐために、FIFOキューを使用します。コンソールからSQSを検索して移動します。
【Create New Queue】を押して作成画面にいきます。

sqs_create
キュー作成

キューの拡張子は.fifoにする必要があります。
作成されたキューを選択するとURLが表示されているので、3-1のコードのURL部分の入力してください。

3-6. 終わりに

以上で、簡単なSQSを使用した注文機能は完成です。といってもこれだけじゃ使えないので、APIゲートウェイの設定もいつか書きます。