PHP + MySQL で Zaif Xem用の自動取引botを作ってみた

過去にニコニコ動画で途中まで紹介していたもののbot本体完成品(4号機)です。
これ以外に以下のプログラムが必要となります。

〇Tickerを取得してDBに蓄積
〇資産情報を取得してDBに登録
〇データベース接続用の資格情報
〇ツイッター自動投稿用の資格情報（使わない場合はコメントアウトで）

別プログラムにしている理由はZaifのAPIは遅延が酷い為です。
1つのバッチ内で回すと「価格取得」「資産情報取得」「注文」「約定確認」「未約定ならキャンセル」…のどこかで高確率でエラーを吐きます。

よってエラーを回避しつつ安全に取引させる為に3種類の処理を別建てにして動かしました。

https://www.nicovideo.jp/watch/sm33873109

※ハッキング発生に伴いZaifから全資産を引き上げたため、Zaif環境のbot開発は終了しています。
公開予定のシミュレータ等も同様、白紙化とさせて頂きました。

ソースコードを公開させて頂きますが、Zaif移行後の動作確認は行っていませんのでご了承ください。

bid_switchで購入フラグを管理し、購入条件を満たしたまま進められたときだけ購入。
ask_switchは逆に売却フラグを管理し、こちらが成立している場合は在庫を処理します。

信用取引が出来ず売りから入ることができない為、フラグ管理はこの2つだけで済んでいます。

<?php

//必要ファイルの読み込み
  require 'Zaif.php';
  require 'api.php';
  require 'dbconnect.php';
  require 'tweet_api.php';

//インスタンスの生成
  $zaif = new Zaif($key, $secret);

//時間切れを3600秒に設定
  set_time_limit(3600);

//tickerを取得
  $ticker_xem = Zaif::pub("ticker","xem_jpy");

//データを取り出し
  $last = $ticker_xem -> last;
  $high = $ticker_xem -> high;
  $low = $ticker_xem -> low;
  $vwap = $ticker_xem -> vwap;
  $bid = $ticker_xem -> bid;
  $ask = $ticker_xem -> ask;

//処理用の変数をセット
  $position = 0;
  $trade_success = 0;
  $cancel_success = 0;
  $date = date("Y-m-d H:i:s")."\n";
  $exit = "off";
  $ask_switch = "off";
  $bid_switch = "on";

//データベースから資産情報を取得

  $sql = "SELECT * FROM deposit_checker ORDER BY id DESC LIMIT 1";
  $stm = $pdo->prepare($sql);
  $stm->execute();
  $result1 = $stm->fetchall(PDO::FETCH_ASSOC);

//日本円の残高と現金比率
  $deposit_jpy = $result1[0]["deposit_jpy"];
  $deposit_xem = $result1[0]["deposit_xem"];
  $total = $result1[0]["total"];
  $capital = $result1[0]["capital"];

  $balance = ($deposit_jpy - $capital) / ($total - $capital);


  //データベースからデータを読み出し
    $sql = "SELECT * FROM xem_ticker ORDER BY id DESC LIMIT 120";
    $stm = $pdo->prepare($sql);
    $stm->execute();
    $result2 = $stm->fetchall(PDO::FETCH_ASSOC);

    //データの取り出し
    for($i = 0 ; $i < 120 ; $i++){
      $avg_5min[$i] = $result2[$i]["avg_5min"];
      $avg_15min[$i] = $result2[$i]["avg_15min"];
      $avg_60min[$i] = $result2[$i]["avg_60min"];
      $avg_300min[$i] = $result2[$i]["avg_300min"];
      $avg_1500min[$i] = $result2[$i]["avg_1500min"];
      $avg_4500min[$i] = $result2[$i]["avg_4500min"];
    }

echo "--------------------------------------------------\n";

echo "-XEM/JPY- ".$date."\n";
echo "LAST        -> ".$last." \n";

echo "AVG_15min   -> ".$avg_15min[0]." \n";
echo "AVG_60min -> ".$avg_60min[0]." \n";
echo "AVG_300min  -> ".$avg_300min[0]." \n";

//判定1
//5時間移動平均が上昇傾向の場合購入
//5時間移動平均が下降傾向の場合売却
echo $avg_300min[0] / $avg_300min[29]."\n";
echo $avg_300min[0] / $avg_300min[19]."\n";
echo $avg_300min[0] / $avg_300min[9]."\n";

if($avg_300min[0] > $avg_300min[29] * 1.0002 || $avg_300min[0] > $avg_300min[19] * 1.0001 || $avg_300min[0] > $avg_300min[9] * 1.00005){
    echo "[avg_300min UP]\n";
}else{
    $bid_switch = "off";
    echo "[avg_300min STAY]\n";
}

if($avg_300min[0] < $avg_300min[9] * 1.000025){
if(
  (
  (
   ($avg_300min[0] < $avg_300min[29] * 1.0001 && $avg_300min[0] < $avg_300min[19] * 1.00005)
|| ($avg_300min[0] < $avg_300min[29] * 1.0001 && $avg_300min[0] < $avg_300min[9] * 1.000025)
|| ($avg_300min[0] < $avg_300min[19] * 1.00005 && $avg_300min[0] < $avg_300min[14] * 1.000025)
)
|| ($avg_60min[0] < $avg_60min[9] && $avg_60min[0] < $avg_60min[4])
)
&&($avg_300min[0] < $avg_300min[29] * 1.0002 && $avg_300min[0] < $avg_300min[19] * 1.0001 && $avg_300min[0] < $avg_300min[9] * 1.00005)){
    echo "[avg_300min STAY]\n";
    $ask_switch = "on";
    $bid_switch = "off";
}
}
//判定2
//5時間移動平均が25時間平均より安い場合購入見送り//一時停止中
  if($avg_300min[0] < $avg_1500min[0]){
    echo "[avg_300min < avg_1500min]\n";
    // $bid_switch = "off";
  }else{
    echo "[avg_300min > avg_1500min]\n";
  }

  //条件3
  //買い板の最高値が15分平均以上の場合購入見送り
  if($bid < $avg_15min[0]){
    echo "[bid < avg_15min] OK.\n";
  }else{
    $bid_switch = "off";
    echo "[bid < avg_15min] NG.\n";
  }

  //条件4
  //現在価格が60分移動平均より下の場合購入見送り
  if($last < $avg_60min[0]){
    echo "[last < avg_60min] .\n";
    $bid_switch = "off";
  }

//売却判定がONかつ在庫がある場合売却処理へ
  if($ask_switch === "on" && $deposit_xem > 1){
      $price = $ask - 0.0001;
      $amount = floor($deposit_xem);
      echo "ASK ORDER START. \n";
      echo "PRICE       -> ".$price." \n";
      echo "AMOUNT      -> ".$amount." \n";


  $trade_ask = $zaif->trade("trade",
    array(
      'currency_pair' => 'xem_jpy',
      'action' => 'ask',
      'price' => $price,
      'amount' => $amount
    )
  );
  $trade_success = $trade_ask -> success;
  if($trade_success === 1){
    $order_id = $trade_ask -> return -> order_id;
    echo "ORDER NOW.WAIT 60 SEC.\n";
    sleep (60);
  }else{
    $error_code = $trade_ask -> error;
    if($error_code === "trade temporarily unavailable."){
      echo "SERVER BUSY...ORDER MISSING\n";
      sleep (60);
      exit;
    }
    echo $error_code."\n";
    echo "ORDER MISSING\n";
    exit;
  }
  $cancel_order = $zaif->trade("cancel_order",
    array(
      'order_id' => $order_id,
    )
  );
    $cancel_success = $cancel_order -> success;
    if($cancel_success === 1){
      echo "->PASSED 60 SEC. CANCEL ORDER.\n";
    }else{
      $cancel_order = $zaif->trade("cancel_order",
        array(
          'order_id' => $order_id,
        )
      );
      echo "SUCCESS ASK.\n";
      // //OAuthオブジェクトを生成する
      // $twObj = new TwitterOAuth($sConsumerKey,$sConsumerSecret,$sAccessToken,$sAccessTokenSecret);
      //
      // //呟きをPOSTするAPI
      // $sTweet = "自動投稿テスト【XEM売却】".$price."円 × ".$amount."枚(".date('Y-m-d H:i:s').")";
      // $vRequest = $twObj->OAuthRequest("https://api.twitter.com/1.1/statuses/update.json","POST",array("status" => $sTweet));
    }
  echo "--------------------------------------------------\n";
    exit;
  }

  //購入判定がONの場合購入処理へ
  if($bid_switch === "on"){
  $price = $bid + 0.0001;
  $amount = floor($deposit_jpy / $price);
  // $amount = 1;

        echo "ORDER START. \n";
        echo "PRICE       -> ".$price." \n";
        echo "AMOUNT      -> ".$amount." \n";

        if($amount < 1){
            echo "NO AMOUNT...REBOOT...\n";
            exit;
        }

  //購入処理
    $trade_ask = $zaif->trade("trade",
    	array(
    		'currency_pair' => 'xem_jpy',
    		'action' => 'bid',
    		'price' => $price,
    		'amount' => $amount
    	)
    );

    $trade_success = $trade_ask -> success;

    if($trade_success === 1){
      $order_id = $trade_ask -> return -> order_id;
      echo "ORDER NOW.WAIT 60 SEC.\n";
      sleep (60);
    }else{
      $error_code = $trade_ask -> error;
      if($error_code === "trade temporarily unavailable."){
        echo "SERVER BUSY...ORDER MISSING\n";
        sleep (60);
        exit;
      }
      echo $error_code."\n";
      echo "ORDER MISSING\n";
      exit;
    }

    $cancel_order = $zaif->trade("cancel_order",
    	array(
    		'order_id' => $order_id,
    	)
    );
      $cancel_success = $cancel_order -> success;
      if($cancel_success === 1){
        echo "->PASSED 60 SEC. CANCEL ORDER.\n";
      }else{
        $cancel_order = $zaif->trade("cancel_order",
          array(
            'order_id' => $order_id,
          )
        );
        echo "SUCCESS BUY.\n";
        // //OAuthオブジェクトを生成する
        // $twObj = new TwitterOAuth($sConsumerKey,$sConsumerSecret,$sAccessToken,$sAccessTokenSecret);
        //
        // //呟きをPOSTするAPI
        // $sTweet = "自動投稿テスト【XEM購入】".$price."円 × ".$amount."枚(".date('Y-m-d H:i:s').")";
        // $vRequest = $twObj->OAuthRequest("https://api.twitter.com/1.1/statuses/update.json","POST",array("status" => $sTweet));
      }

    echo "--------------------------------------------------\n";
  }
   ?>