Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

仮想通貨の完全自動売買をやってみよう その1

こんにちは。 エンジニアの横山です。

これまでMFクラウド会計のサーバーサイド開発を担当していましたが、最近、金融機関連携を行うための仕組みであるアカウントアグリゲーションの開発チームに異動しました。

早速、coincheckの連携を担当しまして、「FinTechっぽいことやってるー!!」と非常に楽しく仕事をしています。

仮想通貨といえば、1ビットコインの価値が金1オンスを超えたとかハードフォーク関連のごたごたとか、面白い感じに動いているようです。

ただ、個人的に面白かったのが、やはりボラティリティの高さ(値動きが激しさ)でしょう。

ここではブロックチェーンの仕組みや安全性、信頼性については言及せず、楽しいおもちゃとして扱います。

私は以前、ExcelのVBAを使った株の自動売買やMetaTradeを使ったFXの自動売買をやっておりました。 そこで仮想通貨でも自動売買にチャレンジしたいと思います。  

何が必要か

自動売買というと、事前が設定した逆指値注文などのトリガ注文を意味する場合もありますが、ここでご案内する自動売買とはシステムが自動で売買指値を決め、注文から決済まですべてシステムが行い、人間がやることはシステムのON/OFFのみという完全自動売買です。

※今回の作り方はすべてcoincheck社が提供しているAPI及びライブラリ(gem)で説明させていただきます。

自動売買の仕組みを作るにあたり、、、

1.情報取得、売買注文を機械的に行える仕組み 2.過去データを使い、売買ロジックの検証(バックテスト)が行える仕組み

は欲しいところです。

「1」については主要な仮想通貨取引所であればAPIを用意してくれています。  例:coincheckの取引所API

「2」についてはFXや株では結構あるのですが、(私が知る限り)仮想通貨では用意しているところはなさそうです。 しかし情報取得系のAPIが提供されている以上、自前で作ることは可能です。

私も、GWを利用してcoincheckに対応した検証用rails-apiアプリケーションを作りました。

https://github.com/xiao1203/coincheck_dummy_api (使い方はreadmeにまとめます)

そんなに小難しいことはやっておらず、情報取得系APIを一定期間、インターバルを置いて連続して叩き、そのレスポンスをseedファイルという形で保存します。

その後、rails db:seedを実行し、初期データとして格納しておきます。 こちらについてはgithubでのreadmeを参照してください。

controllerのところが少々イケてないですが、まぁいいや。。。 (データの取得はできていますが、約定系がまだ甘いです)  

どのように作るか

rubyの場合、coincheck側がgemを用意しているため、さらに簡単にできます。

自動売買システムを一から作るにあたり、まず考えなくてはいけないことは、どのようにイベントを発火させるかです。

取引情報を取得するにせよ、注文を実行するにせよ、何らかのトリガーが必要です。 ぱっと思いつくのは

  • cronやタスクスケジューラを使い、定期的に実行処理を行う。
  • エンドレスのループ処理で繰り返し処理を実施する。

などなど。今回はループ処理の方で検討。

先ずは雛形として、指値注文を行うスクリプトを組んでみます。

require 'ruby_coincheck_client'

USER_KEY = "your user key"
USER_SECRET_KEY = "your secret_key"
BASE_URL = "https://coincheck.jp/"
SSL_FLAG = true

cc = CoincheckClient.new(USER_KEY,
                         USER_SECRET_KEY,
                         {base_url: BASE_URL,
                          ssl: SSL_FLAG})

loop do
  response = cc.read_positions(status: "open")
  positions = JSON.parse(response.body)["data"]
  # step1 btc_jpy 181,790円のロング発注
  if positions.empty?
    # ポジション無し
    response = cc.create_orders(order_type: "leverage_buy",
                                rate: 181790,
                                amount: 0.01,
                                market_buy_amount: nil,
                                position_id: nil,
                                pair: "btc_jpy")
  else
    # ポジションあり
    # step2 step1のポジションを持っていたら194600円で決済注文
    response = cc.create_orders(order_type: "close_long",
                                rate: 194600,
                                amount: positions.first["amount"],
                                market_buy_amount: nil,
                                position_id: positions.first["id"],
                                pair: "btc_jpy")

    # 10秒待機
    sleep 10
  end
end

これを以下のようにコンソールで実行すれば、ビットコインに対し、181,790円でロングポジションを取り、194,600円で決済することを繰り替えす自動売買ができます。

$ ruby first_trade.rb

簡単ですね。 さすがにこれでは芸がないので、簡単なスキャルピングを行うように拡張してみましょう。

スキャルピングとは短期で低い利益を積み上げる投資スタイルです。 一回の利益が少ない分、勝率を上げることと、損切りによる損益管理を確実に行うことが求められます。

人間が手動で行うと、プラスになっているときは「もっと上がるはず」、マイナスのときは「いつか持ち直すはず」となり、いつのまにか虎の子の証拠金が減ってしまいます。

では自動売買ではどうでしょうか? 人間のように余計な欲をかかないから、確実にルール通りにやってくれる・・・・はず!

今回はボリンジャーバンドを使ってシステムを組んでみましょう。 coincheckのgemにはテクニカル分析を行うような機能はないのでこの辺りは別のライブラリ(例えば これ とか)を探すか、自前で作るかになります。

今回はあくまでサンプルです。ボリンジャーバンドの正しい使い方をしていないので、これでトレードやったらボロ負けします!

require 'ruby_coincheck_client'
require 'bigdecimal'
require './technical_analysis_services/bollinger_band_service'

USER_KEY = "your user key"
USER_SECRET_KEY = "your secret_key"

INTERVAL_TIME = 10
BASE_URL = "https://coincheck.jp/"
SSL = true

cc = CoincheckClient.new(USER_KEY,
                         USER_SECRET_KEY,
                         {base_url: BASE_URL,
                          ssl: SSL})

bollinger_band_service = BollingerBandService.new()

loop do
  # 現在のレート確認
  puts "count:#{count}"
  rate_res = cc.read_ticker
  btc_jpy_bid_rate =  BigDecimal(JSON.parse(rate_res.body)['bid']) # 現在の買い注文の最高価格
  btc_jpy_ask_rate =  BigDecimal(JSON.parse(rate_res.body)['ask']) # 現在の売り注文の最安価格
  btc_jpy_rate = (btc_jpy_bid_rate + btc_jpy_ask_rate)/2
  bollinger_band_service.set_rate(rate: btc_jpy_rate)

  # ポジションの確認
  response = cc.read_positions(status: "open")
  positions = JSON.parse(response.body)["data"]

  # 証拠金の確認
  response = cc.read_leverage_balance
  margin_available = JSON.parse(response.body)['margin_available']['jpy']
  if positions.empty?
    # ポジション無し
    result = bollinger_band_service.check_signal_exec(btc_jpy_rate)
    # TODO 暫定 (これだとダメ)
    if [BollingerBandService::PLUS_SIGNAL_LV_3, BollingerBandService::MINUS_SIGNAL_LV_3].include?(result)
      if result == BollingerBandService::PLUS_SIGNAL_LV_3
        # +2σを超えたため逆張りとしてショートポジション
        order_rate = btc_jpy_bid_rate.to_i
        order_type = "leverage_sell"
      else
        # -2σを超えたため逆張りとしてロングポジション
        order_rate =  btc_jpy_ask_rate.to_i
        order_type = "leverage_buy"
      end

      order_amount = (margin_available / order_rate * 5).to_f.round(2)
      cc.create_orders(order_type: order_type,
                       rate: order_rate,
                       amount: order_amount,
                       market_buy_amount: nil,
                       position_id: nil,
                       pair: "btc_jpy")
    end

  else
    open_rate = positions[0]["open_rate"]
    # 1.5%以上の利益で利確
    # -2.0%以下のロス発生で損切り
    if positions[0]["side"] == "buy"
      gain_rate = (open_rate * 1.015).to_i
      loss_cut_rate = (open_rate * 0.98).to_i
      if gain_rate <= btc_jpy_ask_rate || loss_cut_rate >= btc_jpy_ask_rate
        # 現在の売り注文が利確金額以上なら利確
        # 現在の売り注文が損切り金額以下なら損切り
        cc.create_orders(order_type: "close_long",
                         rate: btc_jpy_ask_rate.to_i,
                         amount: positions.first["amount"],
                         market_buy_amount: nil,
                         position_id: positions.first["id"],
                         pair: "btc_jpy")
      end

    elsif positions[0]["side"] == "sell"
      gain_rate = (open_rate * 0.985).to_i
      loss_cut_rate = (open_rate * 1.02).to_i
      if gain_rate >= btc_jpy_bid_rate || loss_cut_rate <= btc_jpy_bid_rate
        # 現在の買い注文が利確金額以下なら利確
        # 現在の買い注文が損切り金額以上なら損切り
        cc.create_orders(order_type: "close_short",
                         rate: btc_jpy_bid_rate.to_i,
                         amount: positions.first["amount"],
                         market_buy_amount: nil,
                         position_id: positions.first["id"],
                         pair: "btc_jpy")
      end
    end
  end

  # INTERVAL_TIME秒待機
  sleep INTERVAL_TIME
end

BollingerBandServiceここ を参照してください。

あとは売買ロジックをいろんなケースに対応できるように拡張していくだけです。

とはいえ、過去の経験上テクニカル分析だけで利益を出すのはかなり難しいと思います。。。

また、バックテストで良い成績を上げる為にテクニカル指標のパラメータを過剰にいじるのはカーブフィッティングと言い、システムトレード初心者がよくやる悪手の一つのようです。 (恥ずかしながら、この言葉自体は最近知りました。。) このあたりも注意が必要です。

コンソールで実行ではなく、daemon化することもできます。 また、webアプリケーションにしたい場合はrailsに組み込むことでも対処可能です。

また自前で作る最大のメリットとして、自分で自由に拡張できるというものがあります。

例えばtwitter等で市場が動きそうな情報をいち早くキャッチしたり、他の取引所のデータも調査対象にしたり、チャットサービスなどのAPIを組み込んで、損益を通知させたり、こちらからのメッセージ送信で緊急停止を行ったり、などが可能です。

次回、機会があったらもう少しマシなものをご紹介できればと思います。 つづく、、、?  

最後に

マネーフォワードでは、自動化が大好きなエンジニアを募集しています。 ご応募お待ちしています。

【採用サイト】 ■マネーフォワード採用サイトWantedly | マネーフォワード

【プロダクト一覧】 自動家計簿・資産管理サービス『マネーフォワード』 ■WebiPhone,iPadAndroid

ビジネス向けクラウドサービス『MFクラウドシリーズ』 ■会計ソフト『MFクラウド会計』確定申告ソフト『MFクラウド確定申告』請求書管理ソフト『MFクラウド請求書』給与計算ソフト『MFクラウド給与』経費精算ソフト『MFクラウド経費』入金消込ソフト『MFクラウド消込』マイナンバー管理ソフト『MFクラウドマイナンバー』資金調達サービス『MFクラウドファイナンス』

メディア ■くらしの経済メディア『MONEY PLUS』