みなさんbot作ってますか?
以前cronを使ってbotを定期的に再起動する記事をお届けしました。
これは一定時間によってbotをリフレッシュするものです。
しかし、botを動かしていると時間ではなく特定の条件によって起動と停止を使い分けたい時がきますよね。
例えばボラティリティが高い時だけ動かしたい、現物と乖離が低い時だけ動かしたいなどです。
私の場合はbitflyerでmmbotを動かしているのですが、SFDが始まるとそれまで何日もかけてコツコツ増やしていたものを一瞬で奪い取られるということが何度もありました。
そこで今回はSFD始まる前にbotを止めて、SFD終わったらbotを動かすプログラムを作ります。地味に効いてくる施策かと思いますので、みなさんもぜひコピペして使ってください。
今日からSFDbot養分卒業!!!!
監視botの構成
今回はpybottersを使用します。
なぜならwebsocketを使ってデータを受信したいからです。
websocketを使用する時は速度が求められるときだけなのでは?と、お考えの方も多いとは思いますが、もうひとつ大きなメリットがあります。
それは、apiの使用制限を消費しないことです。
mmbotなどの高頻度系はapiの消費制限に容易に達してしまいます。そのため、HTTP APIを安易に叩いてしまうと、その分発注や取り消しに使える量が減ってしまうからです。
現物の約定と先物の板を監視して、一定以上でbotをストップします。
今回は上乖離のみを題材に扱います。
監視botのコード
こちらがコードになります。
コピペして使ってください。
import asyncio
from asyncio.exceptions import SendfileNotAvailableError
from asyncio.tasks import create_task
import pybotters
import time
from rich import print
import json
import numpy as np
import subprocess
import atexit
# 親プロセスが終了する際に子プロセスも終了させる関数
def cleanup():
global process # グローバル変数を使用
if process is not None:
process.terminate()
atexit.register(cleanup) # cleanup関数を登録
symbol = "FX_BTC_JPY"
# apis
apis = {'bitflyer': ["key", "secret"]}
async def kanshi():
global process
async with pybotters.Client(apis=apis, base_url='https://api.bitflyer.com') as client:
store = pybotters.bitFlyerDataStore()
await store.initialize(
client.get("/v1/me/getpositions?product_code=FX_BTC_JPY"),
)
wstask = await client.ws_connect(
"wss://ws.lightstream.bitflyer.com/json-rpc",
send_json=[
{"method": "subscribe", "params": {"channel": "lightning_board_snapshot_FX_BTC_JPY"}, "id": 1},
{"method": "subscribe", "params": {"channel": "lightning_board_FX_BTC_JPY"}, "id": 2},
{"method": "subscribe", "params": {"channel": "lightning_ticker_FX_BTC_JPY"}, "id": 3},
{"method": "subscribe", "params": {"channel": "lightning_executions_FX_BTC_JPY"}, "id": 4},
],
hdlr_json=store.onmessage,
)
gstore = pybotters.bitFlyerDataStore()
await gstore.initialize(
client.get("/v1/me/getpositions?product_code=BTC_JPY"),
)
gwstask = await client.ws_connect(
"wss://ws.lightstream.bitflyer.com/json-rpc",
send_json=[
{"method": "subscribe", "params": {"channel": lightning_board_snapshot_BTC_JPY"}, "id": 1},
{"method": "subscribe", "params": {"channel": "lightning_board_BTC_JPY"}, "id": 2},
{"method": "subscribe", "params": {"channel": "lightning_ticker_BTC_JPY"}, "id": 3},
{"method": "subscribe", "params": {"channel": "lightning_executions_BTC_JPY"}, "id": 4},
],
hdlr_json=gstore.onmessage,
)
while not len(store.executions):
await store.board.wait()
while not len(gstore.executions):
await gstore.executions.wait()
gprice = 0
ask_price = 0
bid_price = 0
process = None
while True:
gtransaction = gstore.executions.find()
board = store.board.sorted()
gprice = gtransaction[-1]["price"]
bid_price = board["BUY"][0]["price"]
ask_price = board["SELL"][0]["price"]
if (ask_price - gprice)/gprice < 0.043:
if process is None:
process = subprocess.Popen(["python", "banana.py"])
elif (ask_price - gprice)/gprice > 0.048:
if process is not None:
process.terminate() # プロセスを停止
process = None # プロセス変数をリセット
await asyncio.sleep(5)
await store.board.wait()
# 非同期メイン関数を実行(Ctrl+Cで終了)
if __name__ == '__main__':
asyncio.run(kanshi())
コードの説明
簡単に説明をします。作りかけのSFDbotからコードをツギハギしているので不要な部分も含まれているかとは思いますが、ご容赦ください(少なくとも動きます!)。
pybottersに関して詳しいことを知りたい場合、pybottersのGithubのreleaseがおすすめです(pybottersのreleaseをおすすめする記事mmbot神教材まとめ)。
まずはじめに必要なライブラリをインストールします。
import asyncio
from asyncio.exceptions import SendfileNotAvailableError
from asyncio.tasks import create_task
import pybotters
import time
from rich import print
import json
import numpy as np
import subprocess
import atexit
次に、監視botが終了したら、監視されているbotも終了するための登録をします。
# 親プロセスが終了する際に子プロセスも終了させる関数
def cleanup():
global process # グローバル変数を使用
if process is not None:
process.terminate()
atexit.register(cleanup) # cleanup関数を登録
websocketの購読をします。
今回は現物と先物の乖離が知りたいので、ふたつデータストアを作ります。
symbol = "FX_BTC_JPY"
# apis
apis = {'bitflyer': ["key", "secret"]}
async def kanshi():
global process
async with pybotters.Client(apis=apis, base_url='https://api.bitflyer.com') as client:
store = pybotters.bitFlyerDataStore()
await store.initialize(
client.get("/v1/me/getpositions?product_code=FX_BTC_JPY"),
)
wstask = await client.ws_connect(
"wss://ws.lightstream.bitflyer.com/json-rpc",
send_json=[
{"method": "subscribe", "params": {"channel": "lightning_board_snapshot_FX_BTC_JPY"}, "id": 1},
{"method": "subscribe", "params": {"channel": "lightning_board_FX_BTC_JPY"}, "id": 2},
{"method": "subscribe", "params": {"channel": "lightning_ticker_FX_BTC_JPY"}, "id": 3},
{"method": "subscribe", "params": {"channel": "lightning_executions_FX_BTC_JPY"}, "id": 4},
],
hdlr_json=store.onmessage,
)
gstore = pybotters.bitFlyerDataStore()
await gstore.initialize(
client.get("/v1/me/getpositions?product_code=BTC_JPY"),
)
gwstask = await client.ws_connect(
"wss://ws.lightstream.bitflyer.com/json-rpc",
send_json=[
{"method": "subscribe", "params": {"channel": "lightning_board_snapshot_BTC_JPY"}, "id": 1},
{"method": "subscribe", "params": {"channel": "lightning_board_BTC_JPY"}, "id": 2},
{"method": "subscribe", "params": {"channel": "lightning_ticker_BTC_JPY"}, "id": 3},
{"method": "subscribe", "params": {"channel": "lightning_executions_BTC_JPY"}, "id": 4},
],
hdlr_json=gstore.onmessage,
)
次にデータストアが貯まるまで待って、変数を初期化しておきます。
待たないとデータストアを参照したときなにもなくてエラーが出ます。
while not len(store.executions):
await store.board.wait()
while not len(gstore.executions):
await gstore.executions.wait()
gprice = 0
ask_price = 0
bid_price = 0
process = None
現物の約定値と、先物のbid、askを呼び出します。
今回はとりあえず上乖離だけをターゲットにしているのでaskだけでもかまいません。
while True:
gtransaction = gstore.executions.find()
board = store.board.sorted()
gprice = gtransaction[-1]["price"]
bid_price = board["BUY"][0]["price"]
ask_price = board["SELL"][0]["price"]
最後に乖離が4.3%以下でbotが動いていなかったら、botを起動。4.8%乖離したらbotを停止。
開始と停止を同じ値にしないのは、その値に張り付いてしまったときに頻繁にプログラムの起動と終了が行われてしまうのを避けるためです。
if (ask_price - gprice)/gprice < 0.043:
if process is None:
process = subprocess.Popen(["python", "banana.py"])
elif (ask_price - gprice)/gprice > 0.048:
if process is not None:
process.terminate() # プロセスを停止
process = None # プロセス変数をリセット
await asyncio.sleep(5)
await store.board.wait()