ぷるぷるした直方体

元職業エンジニアの雑記です

トレーディングシステム開発・調査メモ (1)

自動トレーディングシステム(=bot)が毎日ちょっとずつお金を稼いでくれると、精神が安らぎ健康になることが知られています [要出典]。

現在そこそこ安定して小銭を稼ぐbotが1体いますが、改善の余地は多くいずれ頭打ちになるのは目に見えています。そこで、botの理論強化・利益のスケールを目指し、他の環境や取引所で動くbotを作ることにしました。

まだ取り組んで2週間ほどで基幹部分を作っただけなので、収益を出すには多くの改善が必要に思われます。本記事では改善する上で取り組んだことを記載し、研究成果とbot作りの楽しさを伝えたいと思います。

とどのつまりは、ただの自分用のメモです。

前提

取引所はBitflyer、銘柄はFX_BTC_JPYです。ここは取引高が多いため流動性が高く、botを動かすのに適してると考えられます。一方で、既にbotが跋扈していたり、システムがすぐ不安定になったりと暴れん坊な側面もあり、一筋縄では行きません。

なお、スプレッドは安定時であれば0.02%程度(表示を信じれば)です。1単位500,000円だとすると100円、0.01ロットなら1円の損が積み重なることになります(実際のスプレッド、成行のスリッページは必要が生じたら計測します)。

手法はシンプルで、相場を監視して条件を満たしたらポジションを1つだけ取り、1分程度でクローズする超短期取引(=Scalping)です。ポジションを取るトリガーについては秘密としておきます。

ゴール

短期的なゴールは、ほぼ最小ロットで3日間のトータルがプラスとしました。1週間でN円などの利益目標を置くのはとりあえず後回しで、まずは損失を減らすことに重点を置きます。毎日プラスという目標でも良いですが、相場環境に依存する所が大きいので幅を持たせています。

botの現状

現在のbotは下記のような基本的な機能が出来ています。が、検証期間はこれからなのでバグが潜んでいる可能性は高いです。

  • 相場環境の監視
  • オーダーの発注
  • ポジションの監視・クローズ
  • 取引所の監視(不安定そうなら取引しない)

ちなみに、このbotは第3世代です。第1世代は他所で頑張っており、第2世代は諸事情で動いていません。第1と第2はPython製、今回はRust製です。

現在はポジションをIFD注文(成行、+900にTake Profit、-600にStop Lossを設定)で取って30秒でクローズするようにしています。

収益の現状

この状態で、0.02ロットで0時ごろから11時間ほど動かしてみました。テンションも下がる右肩下がりです。

このグラフ表示だけでは詳細がイマイチ良く分からないので、Pythonで雑に取引履歴を出すツールを作り、以下のような表示が出るようにしました。APIの/v1/me/getchildordersから引っ張ってきています(後述しますが、これが問題を引き起こしていました)。

微妙に損益が合いませんが、僅かな差なので無視します(アプリの表示が正しいとは限らない)。

...
2018-12-02 00:27:14, 0:01:12s, prof: -3.9, start: SELL, diff: 195.0, amount: 0.02,0.02
2018-12-02 01:31:36, 0:00:09s, prof: 15.48, start: BUY, diff: 774.0, amount: 0.02,0.02
2018-12-02 01:32:15, 0:00:17s, prof: 1.56, start: BUY, diff: 78.0, amount: 0.02,0.02
2018-12-02 01:33:04, 0:00:30s, prof: -18.26, start: SELL, diff: 913.0, amount: 0.02,0.02
2018-12-02 01:35:34, 0:00:18s, prof: -13.56, start: BUY, diff: -678.0, amount: 0.02,0.02
2018-12-02 01:36:04, 0:01:22s, prof: 9.8, start: SELL, diff: -490.0, amount: 0.02,0.02
2018-12-02 01:39:27, 0:00:02s, prof: 3.42, start: BUY, diff: 171.0, amount: 0.02,0.02
profit: -165.18000000000006, 120 trade

さらに加工する必要が生じたら、jsonなりCSVなりで吐き出すようにもできます。

仮説1: 逆に取引をすれば儲かる

誰しも思う仮説です。時間が決まっているなら上がるか下がるかしかないので、逆の取引をすれば爆益なのでは?という発想です。

履歴を見ると、取引回数は120回でした。仮にスプレッドが0.02%(約100円幅)であれば、lotは0.02なので2 * 120 = 240円払っていることになります。実際は-165円なので、スプレッドが無い世界なら+75円になっていると考えられます。そのため、逆の取引をすると-315円になってしまい、より損が拡がってしまいます。

ただし、これはスプレッドが0.02%で、かつ個別の取引に特性がないことを仮定しています。特定条件では逆の取引をすると良い、というのは十分起こり得るので、この発想には時折振り返って考える価値はあるでしょう。

実際、数時間逆の取引をするようにした結果は右肩下がりだったので、間違ってはいないと思われます。

結論1: 現在の取引方向は全体的には合っているので、一旦このまま動かす

仮説2: 異常なポジションが損失を生み出している

履歴を見ると、以下のような取引が散見されます。

2018-12-01 21:17:04, 0:00:01s, prof: -4.5600000000000005, start: SELL, diff: 228.0, amount: 0.02,0.02
2018-12-01 21:17:05, 0:00:01s, prof: -1.06, start: SELL, diff: 53.0, amount: 0.02,0.02

現在の設定では、この値幅では30秒後にクローズするはずです。一瞬で閉じるとスプレッドを払うだけになってしまうので、損失に寄与している可能性が高いです。

仮に取引所側の問題であれば、スプレッドが一瞬だけ異常に拡がってStop Lossで成行がトリガーされているか、単に適当にクローズをしているという事が考えられます。が、さすがに金融商品を扱っているシステムでそんなことは無いと信じたいところです。

また、(最もあり得る)bot側のバグであれば、変なパラメーターで発注をしているか、クローズ時点で新たな発注をしているか、オーダー時のローカルの状態がおかしくなっていると思われます。更には表示がおかしいだけ……ということもあるので気は抜けません。

問題点はいくつかありますが、以下の2つをまず調べてみることにします。

  • 仮説2-1: 数秒でクローズしているのは、botが異常なオーダーを出しているためである
  • 仮説2-2: 数秒でクローズしているのは、タイムラグによって乖離した価格で約定していることが原因である

Motivating Example

原因を追求すべく、発注時点のログを漁ってみました。運良く最後の発注がおかしくなっていたので、これにフォーカスします。

2018-12-02 01:39:27, 0:00:02s, prof: 3.42, start: BUY, diff: 171.0, amount: 0.02,0.02, open:482126.0-close:482297.0

このオーダーのパラメーターは下記になっていました。この後こちらから新たな発注をしたログは確認できませんでした。

{"minute_to_expire":10000,"order_method":"IFDOCO","parameters":[{"condition_type":"MARKET","product_code":"FX_BTC_JPY","side":"BUY","size":"0.02"},{"condition_type":"LIMIT","price":477918.0,"product_code":"FX_BTC_JPY","side":"SELL","size":"0.02"},{"condition_type":"STOP","product_code":"FX_BTC_JPY","side":"SELL","size":"0.02","trigger_price":476234.0}],"time_in_force":"GTC"}

これを見ると、___TP: 477918.0、SL: 476234.0でオーダーしています。さて、実際の約定価格を見ると……。

open:482126.0, close:482297.0

全然ちゃうやんけ!発注時にはローカルの価格データを使っているので、これには以下のような原因が考えられます。

  • ローカルで持っている価格データが違う
    • 違う価格がやってきている
    • 価格データに大きなラグがある
  • ローカルで持っている価格データは合っている
    • 発注までの時間差で価格が大きく動いている
    • 成行注文が凄く滑ってる
      • 板がとんでもなくスカスカ
      • 発注時だけスプレッドが異常に拡がっている
    • 取引所が異次元価格で約定させている

起こりそうも無い原因もありますが、一応調べてみます。ログに情報を追加し、またしばらく動かしてみました。

調査ログ

ログを追加して動かし、おかしそうな2018-12-02 14:32:20のオーダーに対してログを観察してみます。これは4秒でクローズされているように見えます。

2018-12-02 14:32:20, 0:00:04s, prof: 5.8, start: BUY, diff: 580.0, amount: 0.01,0.01, open:466470.0-close:467050.0

直前の価格はこちら。2018-12-02 14:32:20.393550181Z

before order price: {"bid":466056.0,"ask":466150.0}
bid: 2018-12-02T14:32:19.071246200Z
ask: 2018-12-02T14:32:19.118118700Z

新規オーダー(2018-12-02 14:32:20.405687224 UTC)のパラメーターはこちら。

{"minute_to_expire":10000,"order_method":"IFDOCO","parameters":[{"condition_type":"MARKET","product_code":"FX_BTC_JPY","side":"BUY","size":"0.01"},{"condition_type":"LIMIT","price":467050.0,"product_code":"FX_BTC_JPY","side":"SELL","size":"0.01"},{"condition_type":"STOP","product_code":"FX_BTC_JPY","side":"SELL","size":"0.01","trigger_price":465456.0}],"time_in_force":"GTC"}

オープンのオーダー注文詳細

{
    "id": 735990320,
    "child_order_id": "JFX20181202-143224-489678F",
    "product_code": "FX_BTC_JPY",
    "side": "BUY",
    "child_order_type": "MARKET",
    "price": 0,
    "average_price": 466470,
    "size": 0.01,
    "child_order_state": "COMPLETED",
    "expire_date": "2018-12-09T13:12:20",
    "child_order_date": "2018-12-02T14:32:20",
    "child_order_acceptance_id": "JRF20181202-143220-015334",
    "outstanding_size": 0,
    "cancel_size": 0,
    "executed_size": 0.01,
    "total_commission": 0
}

オープンのオーダー詳細

{
    "id": 615318665,
    "side": "BUY",
    "price": 466470,
    "size": 0.01,
    "exec_date": "2018-12-02T14:32:24.32",
    "child_order_id": "JFX20181202-143224-489678F",
    "commission": 0,
    "child_order_acceptance_id": "JRF20181202-143220-015334"
}

クローズのオーダー注文詳細

{
    "id": 735991752,
    "child_order_id": "JFX20181202-143229-498110F",
    "product_code": "FX_BTC_JPY",
    "side": "SELL",
    "child_order_type": "LIMIT",
    "price": 467050,
    "average_price": 467050,
    "size": 0.01,
    "child_order_state": "COMPLETED",
    "expire_date": "2018-12-09T13:12:20",
    "child_order_date": "2018-12-02T14:32:24",
    "child_order_acceptance_id": "JRF20181202-143220-015343",
    "outstanding_size": 0,
    "cancel_size": 0,
    "executed_size": 0.01,
    "total_commission": 0
}

クローズのオーダー詳細

{
    "id": 615320061,
    "side": "SELL",
    "price": 467050,
    "size": 0.01,
    "exec_date": "2018-12-02T14:32:45.743",
    "child_order_id": "JFX20181202-143229-498110F",
    "commission": 0,
    "child_order_acceptance_id": "JRF20181202-143220-015343"
}

原因の考察

Websocketで取得していますが、1,200msほど遅れた価格データになっています。直近で取引が無かっただけかも知れませんが、遅延していると考えた方が良さそうです。ここは追加で検証します。

価格データのbid/askは466,056/466,150となっており、466,470で約定しています。300ほど滑っていたようですが、遅延があると考えると妥当に見えます。

注文を出してから受け付けられるまでは600ms以下、注文してから約定するまでなんと4秒、TPに引っかかってクローズするまでは21秒掛かっていました。

……4秒じゃない!

結論2-1

仮説2-1: 数秒でクローズしているのは、botが異常なオーダーを出しているためである

Pythonのツールで時間の差を出していたのは、child_order_date同士でした。If-Done-OCO注文でStop LossやTake Profitに引っかかった場合、クローズオーダーのchild_order_dateは親注文が約定した時間になるので、今回は4秒でクローズしたように見えていたというだけでした!

オーダー詳細まで見るようにツールを修正し(/v1/me/getexecutionsから取るようにした)、正常なログが得られるようになりましたとさ。めでたしめでたし。

結論2-1: ツールがおかしく、数秒でクローズされているように見えるものがあった。

結論2-2

仮説2-2: 数秒でクローズしているのは、タイムラグによって乖離した価格で約定していることが原因である

さて、2-1で解決された取引もありますが、Motivating Exampleに出したものは2-1に当てはまりません。 ここで考えられるのは、発注からタイムラグがあると約定価格が乖離し、すぐTP/SLに引っかかってしまう、という現象です。

これに関して、調査過程で気になる情報がいくつか出てきました。

  • WSからの情報が大きく遅延している?
  • 成行注文を出してから約定するまでが異常に遅い(今回は4秒も掛かっている)
    • 噂ではAPI経由での注文だけ遅れるという謎仕様があるとかなんとか

タイムラグの線が濃厚ですが、実際どうだったかはミリ秒単位の詳細な価格データが必要です。1分足データは簡単に手に入りますが、そうでないものは約定履歴から取らないといけません。用意するのに時間がかかるので、次回の記事に回します……。

次回の記事では、2-2の検証、ここの遅延が存在すること、損失に寄与しているという仮説を検証してゆきます。

結論2-2: 数秒のタイムラグはある。が、これが原因か判断するのは詳細な価格データが必要。次回までに用意して検証する。

今回のまとめ

どのようなプロダクトでもそうですが、リリース直後はバグ潰しに追われて本質的な改善の施策が取れないことがしばしばあります。今回はまさにそれですね。

次回は周辺の調査をしてデータを集めつつ、損失が減るような改善ができたら良いと思います。