前回の記事に引き続き、今回も仮想通貨botの開発状況をまとめていきます。
本記事は、MMBotのコード解析をChatGPTにさせた上で執筆した内容です。
参考にした記事はこちらです。
それでは早速、コードの解説に移っていきましょう。
指値注文する関数
# 指値注文する関数 def limit(side, size, price): while True: try: value = bitflyer.create_order(PAIR, type = 'limit', side = side, amount = size, price = price) break except Exception as e: logger.info(e) time.sleep(2) time.sleep(0.5) return value
このコードは、指定された条件(side
(買いか売りか)、size
(数量)、price
(価格))に基づいて、BitFlyerで指値注文を行う関数です。
具体的には以下の処理を行っています:
bitflyer.create_order
メソッドを呼び出して、指定された条件で注文を作成します。- もし注文作成が成功した場合は、そのまま結果を返します。
- もし注文作成が失敗した場合(
except
ブロック)、エラーメッセージをログに記録して、2秒間待機してから再試行します。これを注文が成功するまで繰り返します。
この関数は、BitFlyerでの注文作成を行う際に、通信エラーやサーバーエラーなどが発生しても対処できるようにリトライメカニズムを組み込んでいます。
注文をキャンセルする関数
# 注文をキャンセルする関数 def cancel(id): try: value = bitflyer.cancelOrder(symbol = PAIR, id = id) except Exception as e: logger.info(e) # 指値が約定していた(=キャンセルが通らなかった)場合、 # 注文情報を更新(約定済み)して返す value = get_status(id) time.sleep(0.5) return value
このコードは、BitFlyerという取引所で出した注文をキャンセルするための関数です。
簡潔に言えば、この関数は注文のキャンセルを試み、もしキャンセルできなかった場合は注文の最新のステータス情報を取得する役割を果たします。
主な動作は以下の通りです。
bitflyer.cancelOrder
メソッドを使用して、指定された注文 ID (id
) に対応する注文をキャンセルしようとします。- もし注文のキャンセルが成功した場合、その結果を返します。
- もし注文が既に約定していた場合、
bitflyer.cancelOrder
はキャンセルできないため、エラーが発生します。この場合、例外が捕捉されて、エラーメッセージがログに記録されます。 - キャンセルができなかった場合、代わりに
get_status
関数を呼び出して、指定された注文 ID に関する最新のステータス情報を取得します。これは、約定済みかどうかなどの情報を含みます。 - 最後に、0.5秒のウェイト(
time.sleep(0.5)
)を入れてから結果を返します。
指定した注文idのステータスを参照する関数
# 指定した注文idのステータスを参照する関数 def get_status(id): if PAIR == 'BTC/JPY': PRODUCT = 'BTC_JPY' else: PRODUCT = PAIR while True: try: value = bitflyer.private_get_getchildorders(params = {'product_code': PRODUCT, 'child_order_acceptance_id': id})[0] break except Exception as e: logger.info(e) time.sleep(2) # APIで受け取った値を読み換える if value['child_order_state'] == 'ACTIVE': status = 'open' elif value['child_order_state'] == 'COMPLETED': status = 'closed' else: status = value['child_order_state'] # 未約定量を計算する remaining = float(value['size']) - float(value['executed_size']) time.sleep(0.1) return {'id': value['child_order_acceptance_id'], 'status': status, 'filled': value['executed_size'], 'remaining': remaining, 'amount': value['size'], 'price': value['price']}
このコードは、指定された注文 ID (id
) に対応する注文のステータスを取得するための関数です。
簡潔に言えば、この関数は指定された注文 ID に対応する注文の詳細情報を取得し、その情報を整理して返すものです。
主な動作は以下の通りです。
bitflyer.private_get_getchildorders
メソッドを使用して、指定された注文 ID に対応する注文の詳細情報を取得しようとします。- もし注文情報の取得が成功した場合、その結果を処理します。
- 取得した注文の状態 (
child_order_state
) を確認し、それに基づいて注文がオープン中かクローズ済みかを判断します。状態が 'ACTIVE' ならば 'open'、'COMPLETED' ならば 'closed' としています。 - 未約定量 (
remaining
) を計算します。これは、注文のサイズ (size
) から既に約定済みのサイズ (executed_size
) を引いたものです。 - 最後に、取得した注文情報を辞書形式で整理して返します。返される情報には注文 ID (
id
)、注文のステータス (status
)、約定済みサイズ (filled
)、未約定量 (remaining
)、注文サイズ (amount
)、および注文価格 (price
) が含まれています。
変数の初期化
# 未約定量が存在することを示すフラグ remaining_ask_flag = 0 remaining_bid_flag = 0 # 指値の有無を示す変数 pos = 'none'
このコードは、いくつかの変数を初期化しています。各変数の役割は以下の通りです。
remaining_ask_flag
およびremaining_bid_flag
: これらは未約定量が存在するかどうかを示すフラグです。0 の場合は未約定量がないことを示し、1 の場合は未約定量が存在することを示します。pos
: これは指値の有無を示す変数で、初期値は 'none' です。アルゴリズムが特定の方向に注文を出すと、この変数が 'buy' または 'sell' に変更され、指値注文が発行されたことを示します。'none' の場合は、指値注文がないことを示します。
このような変数の初期化は、アルゴリズム内で異なる状態や条件を追跡するために使用され、後続のコードでこれらの変数の値が変更されることを意図しています。
ログ取引に関する情報
logger.info('--------TradeStart--------') logger.info('BOT TYPE : MarketMaker @ bitFlyer') logger.info('SYMBOL : {0}'.format(PAIR)) logger.info('LOT : {0} {1}'.format(LOT, COIN)) logger.info('SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)) logger.info('SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100))
このコードは、ログに取引に関する情報を出力しています。各 logger.info
行は、特定の情報をログに記録するためのものです。以下はそれぞれの行が出力する情報についての簡単な説明です:
'--------TradeStart--------'
: 取引が開始されたことを示す区切り線。'BOT TYPE : MarketMaker @ bitFlyer'
: ボットのタイプ(MarketMaker)、および使用している取引所(bitFlyer)。'SYMBOL : {0}'.format(PAIR)
: 取引しているシンボル(通貨ペア)。PAIR
はこのコード内で定義された変数です。'LOT : {0} {1}'.format(LOT, COIN)
: ロットサイズ(取引量)を示し、LOT
とCOIN
はこのコード内で定義された変数です。'SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)
: 注文を入れるためのスプレッド(差額)の設定。SPREAD_ENTRY
はこのコード内で定義された変数で、パーセント表示に変換されています。'SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100)
: 注文をキャンセルするためのスプレッド(差額)の設定。SPREAD_CANCEL
はこのコード内で定義された変数で、パーセント表示に変換されています。
これらのログメッセージは、プログラムの実行中に取引の開始とその設定に関する重要な情報を確認するために使用されます。
残高取得とログへの記録
# 残高取得 asset = float(get_asset()['info'][0]['amount']) colla = float(get_colla()['collateral']) logger.info('--------------------------') logger.info('ASSET : {0}'.format(int(asset))) logger.info('COLLATERAL : {0}'.format(int(colla))) logger.info('TOTAL : {0}'.format(int(asset + colla)))
このコードは、取引アカウントの残高と証拠金情報を取得し、それらの情報をログに記録しています。以下はそれぞれの行が行っていることについての簡単な説明です。
asset = float(get_asset()['info'][0]['amount'])
:get_asset
関数を呼び出してアカウントの残高情報を取得し、その中から'info'
フィールドの最初の要素(通常は通貨の残高情報)の'amount'
フィールドを取得して、浮動小数点数に変換しています。colla = float(get_colla()['collateral'])
:get_colla
関数を呼び出してアカウントの証拠金情報を取得し、その中から'collateral'
フィールドを取得して、浮動小数点数に変換しています。logger.info('--------------------------')
: 区切り線をログに記録しています。logger.info('ASSET : {0}'.format(int(asset)))
: アカウント残高をログに記録しています。logger.info('COLLATERAL : {0}'.format(int(colla)))
: 証拠金情報をログに記録しています。logger.info('TOTAL : {0}'.format(int(asset + colla)))
: 残高と証拠金を合算して、合計の情報をログに記録しています。
これらのログメッセージは、プログラムの実行中に取引アカウントの残高や証拠金の変動を確認するために使用されます。
メインのループ処理
# メインループ while True: # 未約定量の繰越がなければリセット if remaining_ask_flag == 0: remaining_ask = 0 if remaining_bid_flag == 0: remaining_bid = 0 # フラグリセット remaining_ask_flag = 0 remaining_bid_flag = 0 # 自分の指値が存在しないとき実行する if pos == 'none': # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) ask = float(tick['ask']) bid = float(tick['bid']) # 実効スプレッドを計算する spread = (ask - bid) / bid logger.info('--------------------------') logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) # 実効スプレッドが閾値を超えた場合に実行する if spread > SPREAD_ENTRY: # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える amount_int_ask = LOT + remaining_bid amount_int_bid = LOT + remaining_ask # 実効Ask/Bidからdelta離れた位置に指値を入れる trade_ask = limit('sell', amount_int_ask, ask - DELTA) trade_bid = limit('buy', amount_int_bid, bid + DELTA) trade_ask['status'] = 'open' trade_bid['status'] = 'open' pos = 'entry' logger.info('--------------------------') logger.info('entry') time.sleep(5) # 自分の指値が存在するとき実行する if pos == 'entry': # 注文ステータス取得 if trade_ask['status'] != 'closed': trade_ask = get_status(trade_ask['id']) if trade_bid['status'] != 'closed': trade_bid = get_status(trade_bid['id']) # 板情報を取得、実効Ask/Bid(指値を入れる基準値)を決定する tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=float(trade_ask['price']), size_ask=float(trade_ask['amount']), rate_bid=float(trade_bid['price']), size_bid=float(trade_bid['amount'])) ask = float(tick['ask']) bid = float(tick['bid']) spread = (ask - bid) / bid logger.info('--------------------------') logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) logger.info('ask status:{0}, filled:{1}/{2}, price:{3}'.format(trade_ask['status'], trade_ask['filled'], trade_ask['amount'], trade_ask['price'])) logger.info('bid status:{0}, filled:{1}/{2}, price:{3}'.format(trade_bid['status'], trade_bid['filled'], trade_bid['amount'], trade_bid['price'])) # Ask未約定量が最小注文量を下回るとき実行 if trade_ask['status'] == 'open' and trade_ask['remaining'] <= AMOUNT_MIN: # 注文をキャンセル cancel_ask = cancel(trade_ask['id']) # ステータスをCLOSEDに書き換える trade_ask['status'] = 'closed' # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる remaining_ask = float(trade_ask['remaining']) remaining_ask_flag = 1 logger.info('--------------------------') logger.info('ask almost filled.') # Bid未約定量が最小注文量を下回るとき実行 if trade_bid['status'] == 'open' and trade_bid['remaining'] <= AMOUNT_MIN: # 注文をキャンセル cancel_bid = cancel(trade_bid['id']) # ステータスをCLOSEDに書き換える trade_bid['status'] = 'closed' # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる remaining_bid = float(trade_bid['remaining']) remaining_bid_flag = 1 logger.info('--------------------------') logger.info('bid almost filled.') #スプレッドが閾値以上のときに実行する if spread > SPREAD_CANCEL: # Ask指値が最良位置に存在しないとき、指値を更新する if trade_ask['status'] == 'open' and trade_ask['price'] != ask - DELTA: # 指値を一旦キャンセル cancel_ask = cancel(trade_ask['id']) # 注文数が最小注文数より大きいとき、指値を更新する if trade_ask['remaining'] >= AMOUNT_MIN: trade_ask = limit('sell', trade_ask['remaining'], ask - DELTA) trade_ask['status'] = 'open' # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする elif AMOUNT_MIN > trade_ask['remaining'] > 0: trade_ask['status'] = 'closed' remaining_ask = float(trade_ask['remaining']) remaining_ask_flag = 1 # 注文数が最小注文数より小さく0のとき、CLOSEDとする else: trade_ask['status'] = 'closed' # Bid指値が最良位置に存在しないとき、指値を更新する if trade_bid['status'] == 'open' and trade_bid['price'] != bid + DELTA: # 指値を一旦キャンセル cancel_bid = cancel(trade_bid['id']) # 注文数が最小注文数より大きいとき、指値を更新する if trade_bid['remaining'] >= AMOUNT_MIN: trade_bid = limit('buy', trade_bid['remaining'], bid + DELTA) trade_bid['status'] = 'open' # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする elif AMOUNT_MIN > trade_bid['remaining'] > 0: trade_bid['status'] = 'closed' remaining_bid = float(trade_bid['remaining']) remaining_bid_flag = 1 # 注文数が最小注文数より小さく0のとき、CLOSEDとする else: trade_bid['status'] = 'closed' # Ask/Bid両方の指値が約定したとき、1サイクル終了、最初の処理に戻る if trade_ask['status'] == 'closed' and trade_bid['status'] == 'closed': pos = 'none' logger.info('--------------------------') logger.info('completed.') time.sleep(5)
このコードは、仮想通貨取引の自動取引ボット(マーケットメイカーボット)の主要なロジックを含んでいます。以下は、主な機能と流れです。
- メインループ: プログラムは無限ループ (
while True
) で動作し、取引戦略を継続的に実行します。 - 未約定量の管理とフラグのリセット: 前回のサイクルで未約定量が存在すれば、今回の注文数に加え、フラグをリセットします。
- 自分の指値が存在しないときの処理:
- 板情報を取得し、実効的なask/bid(指値を入れる基準値)を決定します。
- 実効スプレッドを計算し、閾値を超えた場合に注文を出します。
- 注文が出された場合:
- 注文ステータスを取得します。
- 板情報を再度取得し、実効Ask/Bidを決定します。
- Ask/Bidの未約定量が最小注文量を下回ると注文をキャンセルし、注文ステータスを更新します。
- スプレッドが閾値以上の場合、指値を最良位置に更新します。
- Ask/Bid両方の指値が約定した場合:
- ポジションを「none」に戻し、1サイクルの処理が終了し、次のサイクルが開始されます。
- 時間のスリープ:
time.sleep(5)
を挿入し、一定時間待機してから次のサイクルを開始します。
このコードは、取引所からの情報取得、注文の発行、キャンセル、ポジションの管理など、マーケットメイキング戦略を実装しています。
未約定量のフラグとその繰り返し
# 未約定量の繰越がなければリセット if remaining_ask_flag == 0: remaining_ask = 0 if remaining_bid_flag == 0: remaining_bid = 0 # フラグリセット remaining_ask_flag = 0 remaining_bid_flag = 0
このコードは、未約定量のフラグとその繰り越しを管理しています。以下が各行の説明です:
- 未約定量の繰越がなければリセット:
remaining_ask_flag
とremaining_bid_flag
がそれぞれ0の場合、remaining_ask
およびremaining_bid
を0にリセットします。これにより、新しいサイクルが始まるときに未約定量をリセットできます。 - フラグリセット:
remaining_ask_flag
とremaining_bid_flag
を0にリセットします。これにより、次のサイクルで新しい未約定量が発生する前に、フラグがリセットされます。
指値が存在しない時の処理
# 自分の指値が存在しないとき実行する if pos == 'none': # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) ask = float(tick['ask']) bid = float(tick['bid']) # 実効スプレッドを計算する spread = (ask - bid) / bid logger.info('--------------------------') logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) # 実効スプレッドが閾値を超えた場合に実行する if spread > SPREAD_ENTRY: # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える amount_int_ask = LOT + remaining_bid amount_int_bid = LOT + remaining_ask # 実効Ask/Bidからdelta離れた位置に指値を入れる trade_ask = limit('sell', amount_int_ask, ask - DELTA) trade_bid = limit('buy', amount_int_bid, bid + DELTA) trade_ask['status'] = 'open' trade_bid['status'] = 'open' pos = 'entry' logger.info('--------------------------') logger.info('entry') time.sleep(5)
このコードは、自分の指値が存在しない場合に実行されるブロックです。以下は、具体的な処理の流れです。
まず、if pos == 'none':で「指値が存在しない時」という条件分岐を設定しています。
- 板情報の取得:
get_effective_tick
関数を呼び出して、取引ペアの板情報から実効的なask/bid(指値を入れる基準値)を取得します。 - 実効スプレッドの計算: 取得したaskとbidから実効スプレッドを計算します。スプレッドは
(ask - bid) / bid
で計算されます。 - ログの出力: 計算した実効スプレッドやask/bidの情報をログに出力します。
- 実効スプレッドが閾値を超えた場合の処理: もし実効スプレッドが指定された閾値
SPREAD_ENTRY
を超えた場合、以下の処理が実行されます:- 前回のサイクルにて未約定量が存在すれば、今回の注文数にそれを加えます。
- 実効Ask/Bidから一定の価格差
DELTA
離れた位置に指値を入れる注文(trade_ask
およびtrade_bid
)を発行します。 - 注文のステータスを "open" に設定し、ポジションの状態を "entry" に変更します。
- ログに「entry」のメッセージを出力します。
- 時間のスリープ:
time.sleep(5)
を挿入し、一定時間待機してから次のサイクルを開始します。
この部分の目的は、指定されたスプレッド閾値を超えた場合に指値を出すことで、市場メイキングの戦略を実行しています。
板情報の取得・実効スプレッドの計算・ログの出力
if pos == 'none': # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) ask = float(tick['ask']) bid = float(tick['bid']) # 実効スプレッドを計算する spread = (ask - bid) / bid logger.info('--------------------------') logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100))
以下はこのコードの動作の詳細な説明です。
pos
(ポジション)が "none" の場合、つまりボットが現在何も注文していない状態である場合、次のステップが実行されます。get_effective_tick
関数を使用して、市場の板情報から実効的なask(売り注文)とbid(買い注文)の価格を取得します。これにより、実際に取引が成立する価格帯を把握します。- 取得したaskとbidから実効スプレッドを計算します。スプレッドは、askとbidの価格差をbidで割ったものです。これにより、市場の流動性や注文の執行可能性を評価します。
- 計算した実効スプレッドやask/bidの情報をログに出力します。これにより、ボットの動作や市場の状況が視覚化され、トラブルシューティングや最適化が行いやすくなります。
- 計算した実効スプレッドが事前に設定された閾値を超えた場合、新しい注文を出すための準備が整います。残りのコードでは、実際に指値注文を出すための手順が続きます。
この部分は、市場の状況を監視し、スプレッドが一定の閾値を超えるときに新しい注文を出すかどうかを決定するための基本的な処理を行います。
実効スプレッドが閾値を超えた場合の処理
# 実効スプレッドが閾値を超えた場合に実行する if spread > SPREAD_ENTRY: # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える amount_int_ask = LOT + remaining_bid amount_int_bid = LOT + remaining_ask # 実効Ask/Bidからdelta離れた位置に指値を入れる trade_ask = limit('sell', amount_int_ask, ask - DELTA) trade_bid = limit('buy', amount_int_bid, bid + DELTA) trade_ask['status'] = 'open' trade_bid['status'] = 'open' pos = 'entry' logger.info('--------------------------') logger.info('entry') time.sleep(5)
このコードは、実効的なスプレッドが指定された閾値 (SPREAD_ENTRY
) を超えた場合に、新しい注文を出すための処理を実行しています。
spread > SPREAD_ENTRY:
:- 実効スプレッドが
SPREAD_ENTRY
を超えているかどうかを確認します。
- 実効スプレッドが
- 前回のサイクルにて未約定量が存在すれば今回の注文数に加える:
- 過去のサイクルで未約定量があれば、それを考慮して新しい注文数を計算します。
- 実効Ask/Bidからdelta離れた位置に指値を入れる:
get_effective_tick
関数を使用して市場の板情報から実効的なask(売り注文)とbid(買い注文)の価格を取得し、それぞれask
とbid
に代入します。- 計算された価格から一定の差異
DELTA
を引いたり足したりして、新しい売り注文 (trade_ask
) と買い注文 (trade_bid
) の価格を設定します。
- 注文ステータスを 'open' に設定し、
pos
を 'entry' に変更:- 新しい注文はまだ約定していないので、注文ステータスを 'open' に設定します。
pos
を 'entry' に変更して、ボットが新しい注文を出したことを覚えています。
- ログの出力:
- ログに 'entry' というメッセージを出力し、注文が実行されたことを確認します。
time.sleep(5)
:- 注文が出された後、一定時間スリープします。これは、次のサイクルまで待つためです。
指値が存在する時の処理
# 自分の指値が存在するとき実行する if pos == 'entry': # 注文ステータス取得 if trade_ask['status'] != 'closed': trade_ask = get_status(trade_ask['id']) if trade_bid['status'] != 'closed': trade_bid = get_status(trade_bid['id']) # 板情報を取得、実効Ask/Bid(指値を入れる基準値)を決定する tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=float(trade_ask['price']), size_ask=float(trade_ask['amount']), rate_bid=float(trade_bid['price']), size_bid=float(trade_bid['amount'])) ask = float(tick['ask']) bid = float(tick['bid']) spread = (ask - bid) / bid logger.info('--------------------------') logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) logger.info('ask status:{0}, filled:{1}/{2}, price:{3}'.format(trade_ask['status'], trade_ask['filled'], trade_ask['amount'], trade_ask['price'])) logger.info('bid status:{0}, filled:{1}/{2}, price:{3}'.format(trade_bid['status'], trade_bid['filled'], trade_bid['amount'], trade_bid['price'])) # Ask未約定量が最小注文量を下回るとき実行 if trade_ask['status'] == 'open' and trade_ask['remaining'] <= AMOUNT_MIN: # 注文をキャンセル cancel_ask = cancel(trade_ask['id']) # ステータスをCLOSEDに書き換える trade_ask['status'] = 'closed' # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる remaining_ask = float(trade_ask['remaining']) remaining_ask_flag = 1 logger.info('--------------------------') logger.info('ask almost filled.') # Bid未約定量が最小注文量を下回るとき実行 if trade_bid['status'] == 'open' and trade_bid['remaining'] <= AMOUNT_MIN: # 注文をキャンセル cancel_bid = cancel(trade_bid['id']) # ステータスをCLOSEDに書き換える trade_bid['status'] = 'closed' # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる remaining_bid = float(trade_bid['remaining']) remaining_bid_flag = 1 logger.info('--------------------------') logger.info('bid almost filled.') #スプレッドが閾値以上のときに実行する if spread > SPREAD_CANCEL: # Ask指値が最良位置に存在しないとき、指値を更新する if trade_ask['status'] == 'open' and trade_ask['price'] != ask - DELTA: # 指値を一旦キャンセル cancel_ask = cancel(trade_ask['id']) # 注文数が最小注文数より大きいとき、指値を更新する if trade_ask['remaining'] >= AMOUNT_MIN: trade_ask = limit('sell', trade_ask['remaining'], ask - DELTA) trade_ask['status'] = 'open' # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする elif AMOUNT_MIN > trade_ask['remaining'] > 0: trade_ask['status'] = 'closed' remaining_ask = float(trade_ask['remaining']) remaining_ask_flag = 1 # 注文数が最小注文数より小さく0のとき、CLOSEDとする else: trade_ask['status'] = 'closed' # Bid指値が最良位置に存在しないとき、指値を更新する if trade_bid['status'] == 'open' and trade_bid['price'] != bid + DELTA: # 指値を一旦キャンセル cancel_bid = cancel(trade_bid['id']) # 注文数が最小注文数より大きいとき、指値を更新する if trade_bid['remaining'] >= AMOUNT_MIN: trade_bid = limit('buy', trade_bid['remaining'], bid + DELTA) trade_bid['status'] = 'open' # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする elif AMOUNT_MIN > trade_bid['remaining'] > 0: trade_bid['status'] = 'closed' remaining_bid = float(trade_bid['remaining']) remaining_bid_flag = 1 # 注文数が最小注文数より小さく0のとき、CLOSEDとする else: trade_bid['status'] = 'closed' # Ask/Bid両方の指値が約定したとき、1サイクル終了、最初の処理に戻る if trade_ask['status'] == 'closed' and trade_bid['status'] == 'closed': pos = 'none' logger.info('--------------------------') logger.info('completed.') time.sleep(5)
このコードは、ボットが既に出した注文が存在する場合の処理を示しています。以下はこのコードの主な機能です。
- 注文ステータス取得:
- 既存の売り注文 (
trade_ask
) と買い注文 (trade_bid
) のステータスを取得します。まだ約定していない場合は、get_status
関数を使用して注文の最新ステータスを取得します。
- 既存の売り注文 (
- 板情報を取得、実効Ask/Bidを決定:
- 既存の注文が存在する場合、その注文がどの位置にあるかを確認するために、
get_effective_tick
関数を使用して市場の板情報を取得します。
- 既存の注文が存在する場合、その注文がどの位置にあるかを確認するために、
- ログの出力:
- 板情報や注文ステータスなどの情報をログに出力します。
- Ask未約定量が最小注文量を下回るとき:
- 売り注文がまだ約定しておらず、未約定量が最小注文量 (
AMOUNT_MIN
) を下回った場合、注文をキャンセルします。 - キャンセルした場合は、注文ステータスを 'closed' に変更し、未約定量を記録して次のサイクルでそれを考慮します。
- 売り注文がまだ約定しておらず、未約定量が最小注文量 (
- Bid未約定量が最小注文量を下回るとき:
- 買い注文がまだ約定しておらず、未約定量が最小注文量 (
AMOUNT_MIN
) を下回った場合、注文をキャンセルします。 - キャンセルした場合は、注文ステータスを 'closed' に変更し、未約定量を記録して次のサイクルでそれを考慮します。
- 買い注文がまだ約定しておらず、未約定量が最小注文量 (
- スプレッドが閾値以上のとき:
- 実効スプレッドが指定された閾値 (
SPREAD_CANCEL
) を超えた場合、注文の指値価格が最良位置に存在しない場合、指値を更新します。 - 指値を一旦キャンセルしてから、新しい価格で指値を出します。
- 更新した場合、注文ステータスを 'open' に変更します。
- 実効スプレッドが指定された閾値 (
- Ask/Bid両方の指値が約定したとき:
- 既存の売り注文と買い注文が両方とも約定している場合、サイクルが終了し、最初の処理に戻ります。
条件分岐と注文ステータス取得
if pos == 'entry': # 注文ステータス取得 if trade_ask['status'] != 'closed': trade_ask = get_status(trade_ask['id']) if trade_bid['status'] != 'closed': trade_bid = get_status(trade_bid['id'])
この部分のコードは、ボットが既に出した注文が存在する場合、その注文のステータスを更新しています。以下はこのコードの主な機能です。
- 注文ステータス取得:
- 既存の売り注文 (
trade_ask
) と買い注文 (trade_bid
) のステータスを取得します。 get_status
関数を使用して、指定された注文IDに関する最新の注文ステータスを取得します。
- 既存の売り注文 (
- 注文ステータスの更新:
- まだ約定していない場合、取得した注文ステータスで既存の注文情報を更新します。
- これにより、最新の注文ステータスが反映されます。
板情報の取得・指値の基準値を決定
# 板情報を取得、実効Ask/Bid(指値を入れる基準値)を決定する tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=float(trade_ask['price']), size_ask=float(trade_ask['amount']), rate_bid=float(trade_bid['price']), size_bid=float(trade_bid['amount'])) ask = float(tick['ask']) bid = float(tick['bid']) spread = (ask - bid) / bid logger.info('--------------------------') logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) logger.info('ask status:{0}, filled:{1}/{2}, price:{3}'.format(trade_ask['status'], trade_ask['filled'], trade_ask['amount'], trade_ask['price'])) logger.info('bid status:{0}, filled:{1}/{2}, price:{3}'.format(trade_bid['status'], trade_bid['filled'], trade_bid['amount'], trade_bid['price']))
このコードは、現在の市場の板情報を取得し、ボットが出した売り注文 (trade_ask
) と買い注文 (trade_bid
) の実効Ask(買い注文を出す基準価格)および実効Bid(売り注文を出す基準価格)を決定しています。以下はコードの主な機能です。
get_effective_tick
関数を使用して板情報を取得:get_effective_tick
関数は、指定された量 (size_thru
) の板情報を取得します。また、ボットが出した売り注文と買い注文の価格および量も指定します。- 取得した板情報から、実効Askと実効Bidを計算します。
- ロギング:
- 取得した実効Ask、実効Bid、およびスプレッド(AskとBidの価格の差)をログに記録します。
- 同時に、売り注文 (
trade_ask
) と買い注文 (trade_bid
) の現在のステータス、約定済み量、注文量、注文価格もログに記録します。
この情報は、ボットが市場に出す注文の価格やスプレッドをモニタリングし、必要に応じて新しい注文を出すために使用されます。
Ask未約定量が最小注文量を下回るときに実効する処理
# Ask未約定量が最小注文量を下回るとき実行 if trade_ask['status'] == 'open' and trade_ask['remaining'] <= AMOUNT_MIN: # 注文をキャンセル cancel_ask = cancel(trade_ask['id']) # ステータスをCLOSEDに書き換える trade_ask['status'] = 'closed' # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる remaining_ask = float(trade_ask['remaining']) remaining_ask_flag = 1 logger.info('--------------------------') logger.info('ask almost filled.')
このコードは、ボットが出した売り注文 (trade_ask
) に対して、未約定量が最小注文量 (AMOUNT_MIN
) を下回った場合に実行されます。具体的な処理は以下の通りです。
trade_ask
のステータスが 'open' かつ未約定量 (trade_ask['remaining']
) が最小注文量を下回る場合に入る条件文です。- 注文をキャンセル:
cancel_ask = cancel(trade_ask['id'])
で、trade_ask
の注文をキャンセルします。
- ステータスを 'closed' に更新:
trade_ask['status'] = 'closed'
で、trade_ask
のステータスを 'closed' に更新します。
- 未約定量を記録:
remaining_ask = float(trade_ask['remaining'])
で、未約定量をremaining_ask
に記録します。
- 次サイクルで未約定量を加えるフラグを立てる:
remaining_ask_flag = 1
で、次のサイクルで未約定量を考慮して注文を出すためのフラグを立てます。
- ロギング:
- 未約定量が最小注文量を下回ったことをログに記録します。
この処理は、売り注文が一部約定され、残りの未約定量が最小注文量を下回った場合に、注文をキャンセルして次のサイクルで新しい注文を出すための準備を行います。
Bid未約定量が最小注文量を下回るとき実行する処理
if trade_bid['status'] == 'open' and trade_bid['remaining'] <= AMOUNT_MIN: # 注文をキャンセル cancel_bid = cancel(trade_bid['id']) # ステータスをCLOSEDに書き換える trade_bid['status'] = 'closed' # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる remaining_bid = float(trade_bid['remaining']) remaining_bid_flag = 1 logger.info('--------------------------') logger.info('bid almost filled.')
このコードは、ボットが出した買い注文 (trade_bid
) に対して、未約定量が最小注文量 (AMOUNT_MIN
) を下回った場合に実行されます。具体的な処理は以下の通りです。
trade_bid
のステータスが 'open' かつ未約定量 (trade_bid['remaining']
) が最小注文量を下回る場合に入る条件文です。- 注文をキャンセル:
cancel_bid = cancel(trade_bid['id'])
で、trade_bid
の注文をキャンセルします。
- ステータスを 'closed' に更新:
trade_bid['status'] = 'closed'
で、trade_bid
のステータスを 'closed' に更新します。
- 未約定量を記録:
remaining_bid = float(trade_bid['remaining'])
で、未約定量をremaining_bid
に記録します。
- 次サイクルで未約定量を加えるフラグを立てる:
remaining_bid_flag = 1
で、次のサイクルで未約定量を考慮して注文を出すためのフラグを立てます。
- ロギング:
- 未約定量が最小注文量を下回ったことをログに記録します。
この処理は、買い注文が一部約定され、残りの未約定量が最小注文量を下回った場合に、注文をキャンセルして次のサイクルで新しい注文を出すための準備を行います。
スプレッドが閾値以上のときに実行する処理
#スプレッドが閾値以上のときに実行する if spread > SPREAD_CANCEL: # Ask指値が最良位置に存在しないとき、指値を更新する if trade_ask['status'] == 'open' and trade_ask['price'] != ask - DELTA: # 指値を一旦キャンセル cancel_ask = cancel(trade_ask['id']) # 注文数が最小注文数より大きいとき、指値を更新する if trade_ask['remaining'] >= AMOUNT_MIN: trade_ask = limit('sell', trade_ask['remaining'], ask - DELTA) trade_ask['status'] = 'open' # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする elif AMOUNT_MIN > trade_ask['remaining'] > 0: trade_ask['status'] = 'closed' remaining_ask = float(trade_ask['remaining']) remaining_ask_flag = 1 # 注文数が最小注文数より小さく0のとき、CLOSEDとする else: trade_ask['status'] = 'closed' # Bid指値が最良位置に存在しないとき、指値を更新する if trade_bid['status'] == 'open' and trade_bid['price'] != bid + DELTA: # 指値を一旦キャンセル cancel_bid = cancel(trade_bid['id']) # 注文数が最小注文数より大きいとき、指値を更新する if trade_bid['remaining'] >= AMOUNT_MIN: trade_bid = limit('buy', trade_bid['remaining'], bid + DELTA) trade_bid['status'] = 'open' # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする elif AMOUNT_MIN > trade_bid['remaining'] > 0: trade_bid['status'] = 'closed' remaining_bid = float(trade_bid['remaining']) remaining_bid_flag = 1 # 注文数が最小注文数より小さく0のとき、CLOSEDとする else: trade_bid['status'] = 'closed'
このコードは、スプレッドが閾値以上の場合に実行される処理です。具体的な動作は以下の通りです:
spread > SPREAD_CANCEL
が成り立つ場合、つまり現在のスプレッドが閾値以上である場合に処理が実行されます。trade_ask
のステータスが 'open' で、かつ指値の価格がask - DELTA
と一致しない場合、指値を更新します。cancel_ask = cancel(trade_ask['id'])
で、trade_ask
の指値を一旦キャンセルします。- 注文数が最小注文量以上の場合は、新しい指値を
ask - DELTA
に設定して、ステータスを 'open' に更新します。 - 注文数が最小注文量より小さく0でない場合、未約定量を記録してステータスを 'closed' に更新し、次サイクルで未約定量を加えるフラグを立てます。
- 注文数が最小注文量より小さく0の場合、ステータスを 'closed' にします。
- 同様に、
trade_bid
のステータスが 'open' で、かつ指値の価格がbid + DELTA
と一致しない場合、指値を更新します。cancel_bid = cancel(trade_bid['id'])
で、trade_bid
の指値を一旦キャンセルします。- 注文数が最小注文量以上の場合は、新しい指値を
bid + DELTA
に設定して、ステータスを 'open' に更新します。 - 注文数が最小注文量より小さく0でない場合、未約定量を記録してステータスを 'closed' に更新し、次サイクルで未約定量を加えるフラグを立てます。
- 注文数が最小注文量より小さく0の場合、ステータスを 'closed' にします。
この処理は、スプレッドが閾値以上の状態で指値が最良位置に存在しない場合、指値を最良位置に更新して取引の最適化を図るものです。
Ask指値が最良位置に存在しないとき、指値を更新する処理
# Ask指値が最良位置に存在しないとき、指値を更新する if trade_ask['status'] == 'open' and trade_ask['price'] != ask - DELTA: # 指値を一旦キャンセル cancel_ask = cancel(trade_ask['id']) # 注文数が最小注文数より大きいとき、指値を更新する if trade_ask['remaining'] >= AMOUNT_MIN: trade_ask = limit('sell', trade_ask['remaining'], ask - DELTA) trade_ask['status'] = 'open' # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする elif AMOUNT_MIN > trade_ask['remaining'] > 0: trade_ask['status'] = 'closed' remaining_ask = float(trade_ask['remaining']) remaining_ask_flag = 1 # 注文数が最小注文数より小さく0のとき、CLOSEDとする else: trade_ask['status'] = 'closed'
このコードは、Ask指値が最良位置に存在しない場合に、指値を更新する処理です。以下が具体的な動作です。
trade_ask['status']
が 'open' であり、かつ指値の価格がask - DELTA
と一致しない場合に処理が実行されます。cancel_ask = cancel(trade_ask['id'])
で、trade_ask
の指値を一旦キャンセルします。- 注文数が最小注文量以上の場合は、新しい指値を
ask - DELTA
に設定して、ステータスを 'open' に更新します。 - 注文数が最小注文量より小さくかつ0でない場合は、未約定量を記録して、ステータスを 'closed' に更新します。同時に、次サイクルで未約定量を加えるフラグを立てます。
- 注文数が最小注文量より小さくかつ0の場合は、ステータスを 'closed' にします。
この処理は、Ask指値が最良位置に存在しない場合、新しい指値を最良位置に更新して取引の最適化を図るものです。
Bid指値が最良位置に存在しないとき、指値を更新する処理
# Bid指値が最良位置に存在しないとき、指値を更新する if trade_bid['status'] == 'open' and trade_bid['price'] != bid + DELTA: # 指値を一旦キャンセル cancel_bid = cancel(trade_bid['id']) # 注文数が最小注文数より大きいとき、指値を更新する if trade_bid['remaining'] >= AMOUNT_MIN: trade_bid = limit('buy', trade_bid['remaining'], bid + DELTA) trade_bid['status'] = 'open' # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする elif AMOUNT_MIN > trade_bid['remaining'] > 0: trade_bid['status'] = 'closed' remaining_bid = float(trade_bid['remaining']) remaining_bid_flag = 1 # 注文数が最小注文数より小さく0のとき、CLOSEDとする else: trade_bid['status'] = 'closed'
このコードは、Bid指値が最良位置に存在しない場合に、指値を更新する処理です。以下が具体的な動作です:
trade_bid['status']
が 'open' であり、かつ指値の価格がbid + DELTA
と一致しない場合に処理が実行されます。cancel_bid = cancel(trade_bid['id'])
で、trade_bid
の指値を一旦キャンセルします。- 注文数が最小注文量以上の場合は、新しい指値を
bid + DELTA
に設定して、ステータスを 'open' に更新します。 - 注文数が最小注文量より小さくかつ0でない場合は、未約定量を記録して、ステータスを 'closed' に更新します。同時に、次サイクルで未約定量を加えるフラグを立てます。
- 注文数が最小注文量より小さくかつ0の場合は、ステータスを 'closed' にします。
この処理は、Bid指値が最良位置に存在しない場合、新しい指値を最良位置に更新して取引の最適化を図るものです。
Ask/Bid両方の指値が約定したとき、1サイクル終了、最初の処理に戻る処理
# Ask/Bid両方の指値が約定したとき、1サイクル終了、最初の処理に戻る if trade_ask['status'] == 'closed' and trade_bid['status'] == 'closed': pos = 'none' logger.info('--------------------------') logger.info('completed.')
このコードは、AskとBidの両方の指値が約定した場合に、1つのサイクルを終了し、最初の処理に戻るための処理です。
具体的な動作は以下の通りです。
trade_ask['status']
が 'closed' かつtrade_bid['status']
が 'closed' の場合、つまりAskとBidの両方の指値が約定している状態である場合に処理が実行されます。pos
の値が 'none' にリセットされます。これにより、次のサイクルで再び新しい指値を出す条件分岐が実行されるようになります。- ログに "completed." というメッセージが記録されます。これにより、1つのサイクルが正常に終了したことがわかります。
この部分のコードは、トレードサイクルが完了したときに処理を初期状態に戻すためのものです。
まとめ
今回はかなり長くなりましたが、ロジックの詳細な部分まで掘り下げて解析しました。
引き続き、一つずつ学習を進めていきます。
開発状況をSNSで呟いています。