【AWS】bitbank Stop注文実装

1. 使用するサービス

(AWS)

  • Lambda
  • IAM
  • SSM
  • DynamoDB

 
(外部サービス)

  • Bitbank API 


2.概要

bitbank APIにはデフォルトで逆指値注文など特殊な注文方法がありあません。今回はDynamoDBを使用して事前に設定した価格を超えるまたは下がった場合にMarket注文を行う仕組みを実装します。

3. 実装

3-1. DynamoDBの中身

テーブルを事前に作成しておきます。PrimaryKeyをcoin_pair、SortKeyをPriceにします。bitflyer 自動注文などで作成したテーブルと基本内容は同じです。

buffalokusojima.hatenablog.com

DynamoDB_contents
テーブルの中身

3-2. Lambdaの実装

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

const momentTimezone = require('moment-timezone');

const lambda = new AWS.Lambda();

AWS.config.update({region: 'ap-northeast-1'});

const ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});

exports.handler = (event, context, callback) => {
    
    var XRP_JPY = process.env['XRP_JPY'];
    var dbData;
    getDatafromDynamoDB('stop_check_bitbank', callback)
    .then(function(data){
        
        if(data.Items.length == 0){
          console.log("stop check data not set");
          callback(null, {
              statusCode: 200,
              body: JSON.stringify({message: "price check data not set"}),
              headers: {"Content-type": "application/json"}
            });
          return;
        }
      
        dbData = data.Items;
        
        console.log(dbData);
        
        const method = "GET"
        
        const path = "/xrp_jpy/depth" 
        
        
        var option = {
          url: 'https://public.bitbank.cc' + path,
          method: method,
          headers: {
            'Content-Type': 'application/json'
            }
        }
        
    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;
        }
        
        
        var price_data = JSON.parse(data.body);
        
        if(price_data == null){
          console.log("price data not Found");
          callback(null, {
            statusCode: 403,
            body: JSON.stringify({message: 'No Data Found'}),
            headers: {"Content-type": "application/json"}
          });
          return;
        }
        
        price_data = price_data.data;
        console.log(price_data);
        
        dbData.forEach(function(item){
          
          if(item.side.S == "buy"){
            if(item.price.N < Number(price_data.asks[0][0])){
              executeOrder(item,callback);
            }
          }else if(item.side.S == "sell"){
            if(item.price.N > Number(price_data.bids[0][0])){
              executeOrder(item,callback);
            }
          }
        });
        
        callback(null, {
            statusCode: 200,
            body: JSON.stringify({message: 'No Order Executed'}),
            headers: {"Content-type": "application/json"}
        });
        return;
    });
    
    });
    function executeOrder(item,callback){
      var param = {
          "coin_pair": item.coin_pair.S,
          "price": item.price.N,
          "size": item.size.N,
          "side": item.side.S,
          "type": "market"
      };
      
      console.log("order will be executed:", param);
                
      var payload = param;
      
      payload = JSON.stringify({body:JSON.stringify(payload)});
      callLambda(payload, callback);
      deleteDataFromDynamoDB('stop_check_bitbank', param, callback);
    }
      
    function getDatafromDynamoDB(table_name, callback){
      
      return new Promise(function (resolve) {
          var params = {
        ExpressionAttributeValues: {
          ':c': {S: 'xrp_jpy'}
        },
        KeyConditionExpression: 'coin_pair = :c',
        ProjectionExpression: 'coin_pair, price, side, size',
        TableName: table_name
      };
    
      // Call DynamoDB to add the item to the table
      ddb.query(params, function(err, data) {
          if (err) {
            console.log("Error", err);
            callback(null, {
                statusCode: 401,
                body: JSON.stringify({message: err.toString()}),
                headers: {"Content-type": "application/json"}
              });
            resolve(null);
            return;
          }
          resolve(data);
        });
      });
    }
    
    function deleteDataFromDynamoDB(table_name, params, callback){
        var params = {
        TableName: table_name,
        Key: {
                'coin_pair' : {S: params.coin_pair},
                'price' : {N: params.price}
             }
      };
      ddb.deleteItem(params, function(err, data){
        if (err) {
          console.log("Error", err);
          callback(null, {
            statusCode: 400,
            body: JSON.stringify({message: err}),
            headers: {
              "Content-type": "application/json"
            }
          });
          return;
        }
        console.log("Success");
        
        params = params.Key;
        
        callback(null,{
          statusCode: 200,
          body: JSON.stringify({message: 'DeleteItem Success: {coin_pair: ' + params.coin_pair.S
                              + ' price: ' + params.price.N
           }),
          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 callLambda(payload, callback){
        return new Promise(function (resolve) {
            
            var params = {
                FunctionName: "bitbank-project-pipeline-stac-ControlOrderFunction-114U1VFTL0EE0",
                InvocationType: "RequestResponse",
                Payload: payload
            }
            console.log(params);
            lambda.invoke(params, function(error, res){
                if(error){
                        console.error(error);
                        callback(null,{
                            statusCode: 500,
                            body: JSON.stringify({message: error.toString()}),
                            headers: {"Content-type": "application/json"}
                        });
                        resolve(null);
                    }
                resolve(res);
            }); 
        });          
    }
    
    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);
            });
            });
    }
};

CloudWatchで定期的にキックすることでSTOP注文を実現します。