2020-05-09

「バックテストに騙されるな!」仮想通貨BOTのバックテスト機能を作る #1

バックテスト機能を作るシリーズ第一回。とりあえず動けば良いところまで。

Article Image

初回

初めてバックテストの領域に足を踏み入れることにする。

当面の間は強いストラテジを作ることよりも気軽に戦略を入力できるような手軽さを目指したい。

目標

初回は次の実装を考える。

  • こちらの記事で作成した5秒足プログラムから出力されたOHLCVファイルから計算
  • ショート、ロング、ドテンロング、ドテンショート、クローズを実装
  • フロンエンドで損益グラフ確認

また最後にバックテストの正当性を考える。

INPUTデータ

{

date: 3177646225510,

O_date: '2020-05-07T02:15:26.310Z',

O: 987236,

H: 987261,

L: 987155,

C: 987261,

C_date: '2020-05-07T02:15:29.479Z',

V: { BUY: 0.12000000000000001, SELL: 2.79155104 }

}

ファイルはJSON形式。CSVのほうが若干データ量で有利かと思うが面倒なので扱いやすいJSONのままにした。

dateは5秒足の時間の区切りの開始時刻。00時00分00秒00時00分4.999...秒の足となるが、この00時00分00秒の方が記録されているだけだ。dayjsに放り込むだけで使える形式(UNIX秒)

VはVolume。BUYとSELLに分けた。TradingViewではわからない情報だ。

OUTPUTデータ

損益の配列 []

クローズが発生するごとにプロット。ドテン時は内部でクローズしてから反転という構造で動作している。

主要コード

// 設定 ----
const slippage = 0
// ---------

let position = 0 // 1:long, -1:short, 0:ノーポジ
let entryPoint = null //エントリーした価格
let totalProfit = 0 //トータルの損益
let profitHistory = [] //損益をプロットする配列

const long = (d) => {
    switch (position) {
        case 1:
            //何もしない
            break
        case -1: //ドテンロング
            close(d)
            long(d)
            break
        case 0: //新規ロング
            entryPoint = d.C + (d.C * slippage)
            position = 1
            break
    }
}

const short = (d) => {
    switch (position) {
        case -1:
            //何もしない
            break
        case 1: //ドテンショート
            close(d)
            short(d)
            break
        case 0: //新規ショート
            entryPoint = d.C - (d.C * slippage)
            position = -1
            break
    }

}

const close = (d) => {
    let profit = 0
    let closePoint
    switch (position) {
        case 1: //ロング決済
            closePoint = d.C - (d.C * slippage)
            profit = closePoint - entryPoint
            break
        case -1: //ショート決済
            closePoint = d.C + (d.C * slippage)
            profit = entryPoint - closePoint
            break
    }
    position = 0 //ノーポジ
    entryPoint = null //エントリー無し
    totalProfit += profit //トータル計算
    profitHistory.push(totalProfit) //損益履歴
}

メインのコードは本当にこれだけ。

とりあえず急ぎで実装した。関数で処理するよりクラス化したほうが長い目で見て良いかも?少しずつ改修して汎用化していきたいところだ。

遅延・滑り・サーバーエラー・・・などなど課題は山積みだがTradingViewも基本はクローズ価格でエントリーするのでまずはこれでよいだろう。

戦略の部分について

実際に戦略を書く場合はこれに続いて次のように書いていく。

for (const d of data) {
	if(d){ //足が抜けてる場合があるので考慮する
		if(BUY条件){long(d)}
		if(SELL条件){short(d)}
		if(クローズ条件){close(d)}
	}
}

関数内でプロットが行われるのであとはフロントエンドにグラフを出すだけだ。

グラフ表示(フロントエンド)

Socket.ioでフロントエンド(Vue.js)にprofitHistoryの配列ごと渡している。

また損益グラフは私がこれまで書いてきた通りvue-chart.jsにて描画を行っている。

正直なところPythonmatplotlibと思うと何倍もの実装時間コストが必要かもしれないがグラフィカルに再描画が可能であり何度もテストを行う意味でWEB-UI上からパラメータを自在に変化させることも出来るので時間コスト分の価値はあるはずだ。

基本的なライングラフであれば20分もあれば実装できる。

特別新しいコードではないため記述しないが、グラフ描画用のdatacollection作成コードは次のようになった。

ここでのdataは損益履歴がplotされた単純な一次元配列。

export const renderProfit = (data) => {

    let labels = []
    data.map(( d, index) =>{
        labels.push(index)
    })

    return {
        labels,
        datasets: [
            {
                label: "Profit(Yen)",
                data: data,
                fill: false,
                borderColor: "green",
                pointRadius: 0, //ポイントを描画しない
                borderWidth: 1, //ラインの細さ
            }
        ]
    }
}

バックテストのデータ量

3時間の間に記録された5秒足で戦略をテストした。この程度ならサクっと処理が終わるため。

実際には様々な時間で試していく必要がある。

戦略非公開について

今回使用したストラテジはは公開しないことにした。

5分ぐらいで考えた手法のため大したものではないと思っていたがどうやらHFTでは割とよくある考え方のようで、書いてしまうとエッジ消失に留まらず他で戦略を解説している方への中傷となりかねないためご了承いただきたい。

この記事ではそういった意図はなく正しくバックテストを行うためのアドバイスとして見てもらいたい。

実行結果

理論上は1BTCのトレードを繰り返したProfit。

2020-05-08_19h40_18

グラフX軸はトレード総数。3時間で1125回のトレードなので0.16秒に1回トレードしている計算。HFTと言ってもよいだろう。

2020/05/18訂正:計算式を間違えていたため訂正。約12秒に1回のトレードであるため単なるスキャルピングの誤り。グラフ上はドテン時2回のトレードとカウントされるのでそちらも考慮したらもう少しトレード間隔は短くなる。

1年半で億り人

3時間で3万円に近い収益を出している。24時間稼働したら日時20万超え。1年半あれば億り人だ。

と、思うだろう?

そう、我々素人はこうやって騙されるのである。確かにグラフは稼いでいる。理論上は稼いでいる。

だが、騙されてはいけない。

本当にそのグラフの利益が得られるのだろうか?

これ成り行きじゃないですか?

そう、上のコードではクローズ時LTPの価格で約定したことになっている。更に約定率100%。

これはかなり有利な値段で約定しているのでは?と疑問が起こる。

これを成り行きで取引した場合どうなるだろうか。

私はHFTトレーダーではないのでざっくりしかわからないが現在稼働しているBOTの滑りを目視すると0.1BTCの成り行きでも200-500円ぐらいの値幅を滑っている。1000円以上滑ることもしばしば。

別のBotterから貰った情報だとbitFlyerでは0.05%平均ほど滑るらしい。

だいたい私の目視と一致している。まして1BTCなら更に滑るだろう。

価格を滑らせよう

さて、既に気づいておられる方もいるだろうが私のコードに次の一行がある。

const slippage = 0

ここに滑る係数を入力する。0.05%なら 0.05 / 100 。

私のコード上はロングもショートもクローズ時価格から「不利な方へ」slippage分動いて約定する想定だ。

実際には本当に約定履歴や板から逆算し約定するかどうかのコードを追記してバックテストする必要がありそうだが今回は初回のためざっくり上記の方法で0.05%滑らせてみる。

実行結果2

image-20200508195555280

yuris-alhumaydy-mSXMHkgRs8s-unsplash

これ、ぜんぜんあかんやつやん・・・

6万円負けている。1年待たずして逆億り人である。

せや、ならストラテジ逆にしたろ!

永遠に負け続けるストラテジがあるとすればそれを逆にトレードすれば勝てるはずである。

よし、エントリー条件を逆にしてみよう(slippageは0.05%のまま)

2020-05-08_19h59_56

pim-chu-dWzWo22F0mA-unsplash

ちょっと考えたら分かるが・・・そりゃそうだ。

滑りを手数料と考えテストに織り込むことがいかに重要かが分かったと思う。

つまり強BOTTERというのはこの条件も織り込んでトータルプラスとなっているのである。

非常に難しい領域であることは間違いない。

所感

さて、コードはサクっと完成したがHFTがいかに難しいかが実感できた。

しかし落ち込むのはまだ早い。指値を使って思い通りの価格で約定できたなら時給1万円とはいかないまでもプラス側になる可能性は残っているはずである(その指値コントロールがとてつもなく難しいのだが)

さて、まだバックテストの挑戦は始まったばかり。

当面の目標は勝つストラテジを作ることではなくより手軽にバックテストを行える環境を作ることである。

※グラフが綺麗すぎる気がするのでバグ等あれば教えて頂けたら幸いである。



この記事をシェア


謎の技術研究部 (謎技研)