20
22

More than 5 years have passed since last update.

できる!GolangでBTC自動取引

Last updated at Posted at 2017-12-25

謝罪文

メリークリスマス。アドベントカレンダー19日目なのに最後の投稿になりました。
ごめんなさい。楽しんでいってね。

はじめに

最近、猫も杓子もBTCですね。
というわけで超短期取引を実装して資産を増やすよ!!

「つべこべ言わずコードで語れ」という方はこちらのコードを見て修正コメントください。
雑魚エンジニアでごめんなさい。
https://gist.github.com/tng527/279af0087e401c757ec41586ff825530

注意事項

※本記事のリンクにはアフィリエイトを貼っています。
嫌いな方はGoogle先生に訪ねてください。
なお、クリックしただけではインセンティブが発生しないのでご安心を。

Coincheckに未登録で自動取引に興味がある人は、まずは登録しておくとやる気が出たときにすぐに始められて便利。
https://coincheck.com/?c=1_YPIYCl2xA

Why 自動取引?

  • 仮想通貨はボラタリティが大きく、短期間に大きく値動きする
  • ニンゲンには不可能な速度の超短期取引でなら利益が上がるのではないか

Why BTC?

  • FXや株は十分に自動取引が組まれてる
  • 仮想通貨は比較的手動取引が多く、自動取引を導入するだけで勝てるのではないか

Why Golang?

  • 少ない経費で始めるため、少ないサーバリソースでも高速なGolangで実装する
  • あとGolangを愛しているから

この記事のゴール

  • 簡単なアルゴリズムでBTC自動取引を実現する

Coincheck

経緯

初心者に優しいという触れ込みのCoincheckで手動取引を始めたので、
流れでCoincheckを利用してみます。

bitflyerZaifも考えましたが、
個人認証まで時間がかかるので、認証済みだったCoincheckを利用します。

ライブラリ

なんと、CoincheckさんはGithubでライブラリを公開しています。
Golangも公開されているなんて、素晴らしいですね。
しかし、今思えばこれが苦難の始まりだった...

疎通

Golangに詳しい皆様はご存知の通り、Golangはのライブラリはgo getできるようpackage化されているのが普通です。
しかし、このライブラリ?はpackage mainとか書いてある......

他にも、謎のfinal commitというcommitがなされており...

というわけで、PR送るべくリファクタに勤しんでいたのですが...
PR送らずにissueで直してる人がいたので、そっちに乗っかることにしました。
大体リファクタ終わってたんだけどね!

疎通(public)

というわけで疎通を取ります。まずはTickerを取得します。
Tickerとは、

各種最新情報を簡易に取得することができます。

だそうです。

忙しい方はGistに上げたのでそちらで。
以下解説。

まずはimportして

import (
    "encoding/json"

    coincheckgo "github.com/Akagi201/coincheckgo"
)

structを準備して、

type TickerResult struct {
    Last      float64
    Bid       float64
    Ask       float64
    High      float64
    Low       float64
    Volume    float64
    Timestamp int
}

ココでアクセスキーとシークレットキーを用意する。
(どうやら既にアクセスキーとシークレットキーを求めてくる仕様っぽい)
clientを準備して、

    client := new(coincheckgo.CoinCheck).NewClient("YourAccessKey", "YourSecretKey")

function用意して、

func getTicker(client coincheckgo.CoinCheck) TickerResult {
    resp := client.Ticker.All()
    var tick_result TickerResult
    json_err := json.Unmarshal([]byte(resp), &tick_result)
    if json_err != nil {
        panic(json_err)
    }
    return tick_result
}

呼ぶ。

    fmt.Println(getTicker(client))

結果がでる。

$ go run auto_coincheck.go
{1.6445e+06 1.6445e+06 1.644627e+06 1.675e+06 1.54e+06 41678.89315871 1514202963}

疎通(private)

publicは誰が呼んでも同じ情報を表示するが、privateは個々人によって違う結果を返す。
取引リクエストなんかもこっち。

APIキー作成がまだの人は、必要なのでココで作っておく。

とりあえず残高照会を呼んで見る。

    balance := client.Account.Balance()
    fmt.Println(balance)
$ go run auto_coincheck.go
{"success":true,"jpy":"68.32417006","btc":"0.0","eth":"0","etc":"0","dao":"0","lsk":"0","fct":"0","xmr":"0","rep":"0","xrp":"0","zec":"0","xem":"0","ltc":"0","dash":"0","bch":"0","jpy_reserved":"0.0","btc_reserved":"0.02445665","eth_reserved":"0","etc_reserved":"0","dao_reserved":"0","lsk_reserved":"0","fct_reserved":"0","xmr_reserved":"0","rep_reserved":"0","xrp_reserved":"0","zec_reserved":"0","xem_reserved":"0","ltc_reserved":"0","dash_reserved":"0","bch_reserved":"0","jpy_lend_in_use":"0.0","btc_lend_in_use":"0.0","eth_lend_in_use":"0.0","etc_lend_in_use":"0.0","dao_lend_in_use":"0.0","lsk_lend_in_use":"0.0","fct_lend_in_use":"0.0","xmr_lend_in_use":"0.0","rep_lend_in_use":"0.0","xrp_lend_in_use":"0.0","zec_lend_in_use":"0.0","xem_lend_in_use":"0.0","ltc_lend_in_use":"0.0","dash_lend_in_use":"0.0","bch_lend_in_use":"0.0","jpy_lent":"0.0","btc_lent":"0.0","eth_lent":"0.0","etc_lent":"0.0","dao_lent":"0.0","lsk_lent":"0.0","fct_lent":"0.0","xmr_lent":"0.0","rep_lent":"0.0","xrp_lent":"0.0","zec_lent":"0.0","xem_lent":"0.0","ltc_lent":"0.0","dash_lent":"0.0","bch_lent":"0.0","jpy_debt":"0.0","btc_debt":"0.0","eth_debt":"0.0","etc_debt":"0.0","dao_debt":"0.0","lsk_debt":"0.0","fct_debt":"0.0","xmr_debt":"0.0","rep_debt":"0.0","xrp_debt":"0.0","zec_debt":"0.0","xem_debt":"0.0","ltc_debt":"0.0","dash_debt":"0.0","bch_debt":"0.0"}

なお、板に並べた(取引中の)残高は表示されない。

BTC自動取引

戦略

ようやく自動取引に入る。
とは言っても、Tickerが取得出来た時点で、あとは注文用のAPIを呼ぶだけである。

ここでは、1秒毎に10回値を取得して、以下2つの条件で取引するアルゴリズムを組む。

  • 9回の値動き中、7回以上値下がりしていて、値動き率が一定以上だと、「買う」
  • 9回の値動き中、7回以上値上がりしていて、値動き率が一定以上だと、「売る」

忙しい方はGistに上げたのでそちらで。

実装

まずは、過去10回分のTickerを入れたときに、売るべきか買うべきかの判断をするロジックを書く。
リファクタして無くて汚いですが、ご勘弁。

func judgement(tick_history []TickerResult) (bool, string, int64) {
    bid_up_cnt := 0
    bid_down_cnt := 0
    ask_up_cnt := 0
    ask_down_cnt := 0
    if len(tick_history) < TICKER_HISTORY_NUM {
        panic("tick_history不足")
    }
    for i := 0; i < len(tick_history)-1; i++ {
        // 売り安値を比較して買うか判断
        bid_diff := tick_history[i+1].Bid - tick_history[i].Bid
        if bid_diff > 0 {
            bid_up_cnt++
        } else {
            bid_down_cnt++
        }
        // 買い高値を比較して売るか判断
        ask_diff := tick_history[i+1].Ask - tick_history[i].Ask
        if ask_diff > 0 {
            ask_up_cnt++
        } else {
            ask_down_cnt++
        }
    }
    // 直近10秒間で、売値が下がり基調かつ値幅の動きが2%以上あれば買う
    last_bid := tick_history[len(tick_history)-1].Bid
    first_bid := tick_history[0].Bid
    bid_diff_rate := float64(first_bid-last_bid) / float64(first_bid) // 値幅の動き
    if bid_down_cnt > TICKER_HISTORY_NUM-2-1 && bid_diff_rate > SELL_RATE_THRESHOLD {
        // rateは下がり基調なので現在の値より安い価格で買う
        return true, "buy", int64(last_bid - 100)
    }
    // 直近10秒間で、買値が上がり基調かつ値幅の動きが2%以上あれば売る
    last_ask := tick_history[len(tick_history)-1].Ask
    first_ask := tick_history[0].Ask
    ask_diff_rate := float64(last_ask-first_ask) / float64(first_ask) // 値幅の動き
    fmt.Printf("[judge][ask]last: %v, first: %v, diff_rate: %v, up_cnt: %v\n", last_ask, first_ask, ask_diff_rate, ask_up_cnt)
    if ask_up_cnt > TICKER_HISTORY_NUM-2-1 && ask_diff_rate > BUY_RATE_THRESHOLD {
        // rateは上がり基調なので現在の値より高い価格で売る
        return true, "sell", int64(last_ask + 100)
    }
    // 何も無いときは何もしない。
    return false, "", 0
}

売買のどちらか、レート、数量を受けて、売買注文を出すメソッドを書く。

func order(client coincheckgo.CoinCheck, order_type, rate, amount string) (bool, int64) {
    resp := client.Order.Create(`{"rate":"` + rate + `","amount":"` + amount + `", "order_type":"` + order_type + `", "pair":"btc_jpy"}`)
    var result interface{}
    decoder := json.NewDecoder(strings.NewReader(resp))
    if err := decoder.Decode(&result); err != nil {
        panic(err)
    }
    success := result.(map[string]interface{})["success"].(bool)
    id := result.(map[string]interface{})["id"].(float64)

    return success, int64(id)
}

main関数内では、まず初期値のTickerを10回確保して、

    // 取引判断のときにも毎回取得するが、初期値として一定回数分のtickerを取得しておく
    var tick_history []TickerResult
    for i := 0; i < TICKER_HISTORY_NUM; i++ {
        tick_history = append(tick_history, getTicker(client))
        time.Sleep(1000 * time.Millisecond)
    }

judgement関数にTickerを引き渡して結果をまるっとorder関数に引き渡す。
数量は最低量の0.005固定とした。

    for true {
        // 新しいTickを入れて、古いTickを消す(メモリ配慮)
        tick_history = append(tick_history, getTicker(client))
        tick_history = tick_history[1:]
        // 取引を行うべきか、行うならどの取引をどの値で行うかを判断
        gen_tx_flag, order_type, rate := judgement(tick_history)
        if gen_tx_flag {
            // 取引
            success, id := order(client, order_type, fmt.Sprint(rate), "0.005")
            if !success {
                panic("order 失敗")
            }
        }
        time.Sleep(1000 * time.Millisecond)
    }

これで回していくと、極々少量ではあるが、大きな値下がりのときは少しずつ買い、大きな値上がりのときはちょっとずつ売ることになる。はず。

結果

完全に目を離すのは怖いので、会社から帰って寝るまでの間に数日回してみた。
1週間ほど回してみた所、10%程度の利益が出た。

今後について

現時点での懸念

  • 値が滑ったら塩漬けされる
  • 一回出した注文はキャンセルできない
  • 高値掴みの安値売りが発生するかも

追加で考えられる実装

  • ゴールデンクロス、デッドクロスに追従する
  • 過去のデータを蓄積しておいて、ロジックをテストできるようにする
  • 一定以上の値動きに乗っかる動きをする
  • ...etc

戦略については、FXサイトでも見てください。

最後に

普段サラリーマンをしていると、いくら働いてもお給料は変わりません。
しかし、投資を始めると自分の頭脳でお金を稼ぎ出す快感が味わえます。

私はこの頭脳で、3日で2ヶ月分の給料を稼ぎ出し、1日で7ヶ月分の給料を失いました。
ご利用は計画的に。

なお、上記損益は手動取引によるもので、自動取引では10%程度の利益が出ていました。

Coincheckに未登録で自動取引に興味がある人は、こちらから登録して取引を始めると私にお金が入ります。

20
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
22