見出し画像

ストップ退出における太郎チャートの実装

チャネルブレイクアウト戦略botをアップデートし、太郎チャートに対応しました。
例えば私が用意したTradingView用バックテスターはスリッページを0.5USDに設定してありますが、実際の売買では板やサイズにより数USD滑ることもあります。
実際の稼働から得たデータの蓄積は、バックテストの結果と併用して戦略の検証に役立つと思います。

太郎チャートは現在は公開されていませんが、売買ポジションの別、サイズ、参入価格、退出価格を一括して送出するコードをbotスクリプトに置くだけで簡単に組み込め、得られるチャートも閲覧端末を問わないビジュアルに優れたものです。今も利用されている方も多いのではないかと思います。

太郎チャートの基本的な使い方では、指値でポジションを閉じると同時に前述した4要素をチャートエンジンに送るのですが、トリガーによる成行クローズの場合は退出タイミングも退出価格も不確定になりますから、実装には工夫がいります。

チャネルブレイクアウト戦略botでは、建玉が0になったポジション変化を退出タイミングとし、退出価格は直前の執行済トレード記録を読み出して得ています。

まずは全体のコードをご覧ください。

LOGFILE_PREFIX = 'cRe'
TARO = True

if TARO:
    from botview import BotView
    botview = BotView('http://127.0.0.1:8000/api/newTrade')


def fetch_position():
    pos_dict = (
        bitmex.privateGetPosition({
            'symbol': 'XBTUSD',
            'columns': json.dumps([
                'currentQty',
                'avgEntryPrice',
            ]),
        })[0]
    )
    return [
        pos_dict['currentQty'],
        pos_dict['avgEntryPrice'],
    ]


def post_orders(previous_qty, entry_price, params):

    """前処理; 略"""

    """注文処理; 略"""

    if TARO:
        record_on_tarocha(previous_qty, entry_price)


def record_on_tarocha(previous_qty, entry_price):
    lot, exit_price, commission = fetch_filled_order()
    if previous_qty > 0:
        side = 'Long'
        entry_price *= (1 + commission)
        exit_price *= (1 - commission)
    else:
        side = 'Short'
        entry_price *= (1 - commission)
        exit_price *= (1 + commission)
    entry_exit = np.around([
        entry_price,
        exit_price,
    ],
        decimals=4,
    )
    botview.newTrade(
        LOGFILE_PREFIX,
        side,
        lot,
        entry_exit[0],
        entry_exit[1],
    )


def fetch_filled_order():
    filled = (
        bitmex.privateGetExecutionTradeHistory({
            'symbol': 'XBTUSD',
            'columns': json.dumps([
                'orderQty',
                'avgPx',
                'commission',
            ]),
            'count': 1,
            'reverse': True,
        })
    )
    return [
        filled[0]['orderQty'],
        filled[0]['avgPx'],
        filled[0]['commission'],
    ]


def run():
    while True:
        
        """取得処理; 略"""

        current_qty, entry_price = fetch_position()
        if entry_price:
            saved_entry_price = entry_price

        """判定処理; 略"""

        if previous_qty != current_qty and not previous_qty:
            post_orders(
                previous_qty,
                saved_entry_price,
                other_params,
            )

        previous_qty = current_qty


"""略"""

- § -

それでは以下でコードを分解しながら見ていきます。

def fetch_position():
   pos_dict = (
       bitmex.privateGetPosition({
           'symbol': 'XBTUSD',
           'columns': json.dumps([
               'currentQty',
               'avgEntryPrice',
           ]),
       })[0]
   )
   return [
       pos_dict['currentQty'],
       pos_dict['avgEntryPrice'],
   ]
def post_orders(previous_qty, entry_price, other_params):

    """前処理; 略"""

    """注文処理; 略"""

    if TARO:
        record_on_tarocha(previous_qty, entry_price)

関数fetch_position()では、建玉をキー'currentQty'の値で取り、参入価格をキー'avgEntryPrice'の値で取っています。
建玉が0に変化したらポジションクローズと判断できます(判定は後述のメインループでしています)。太郎チャートを呼ぶのはこのタイミングが最適です。

関数post_orders()が呼ばれます。ここでの注文は退出注文ではなく、次のチャネルブレイクに備えたトリガーを新たに張るものです。ですからこのオーダーとチャート記録内容には関係がありません。

関数record_on_tarocha()に渡すのは、退出直前まで建てていたロングショートが保持された変数previous_qtyと、ポジション取得関数から得ておいた参入価格の変数entry_priceです。

def record_on_tarocha(previous_qty, entry_price):
    lot, exit_price, commission = fetch_filled_order()
    if previous_qty > 0:
        side = 'Long'
        entry_price *= (1 + commission)
        exit_price *= (1 - commission)
    else:
        side = 'Short'
        entry_price *= (1 - commission)
        exit_price *= (1 + commission)
    entry_exit = np.around([
        entry_price,
        exit_price,
    ],
        decimals=4,
    )
    botview.newTrade(
        LOGFILE_PREFIX,
        side,
        lot,
        entry_exit[0],
        entry_exit[1],
    )

関数record_on_tarocha()の冒頭で呼んでいる関数fetch_filled_order()については後述します。

変数previous_qtyが正数なら退出したのはロングです。手数料を加えた価格で参入し、同じく手数料の分だけ不利な価格で退出したように補正して、記録数値を実態に近づけています。変数previous_qtyが負数の場合も、同様の考え方で補正をします。

計算結果を小数点以下4桁までに丸めて、太郎チャートに投げています。

def fetch_filled_order():
    filled = (
        bitmex.privateGetExecutionTradeHistory({
            'symbol': 'XBTUSD',
            'columns': json.dumps([
                'orderQty',
                'avgPx',
                'commission',
            ]),
            'count': 1,
            'reverse': True,
        })
    )
    return [
        filled[0]['orderQty'],
        filled[0]['avgPx'],
        filled[0]['commission'],
    ]

関数record_on_tarocha()から呼んでいる関数fetch_filled_order()では、最新の約定履歴から、そのサイズと平均約定価格と手数料率を得ています。

キー'avgPx'の値をサンプリングしてみたところ、値分かれした約定価格の加重平均値とは必ずしも一致しない値が返るのですが、概ね近似値が得られたので採用しました。

変数commissionには常に0.00075が入るはずです。このbotはオーダーを全てストップ成行で執行しているためです。

def run():
    while True:
        
        """取得処理; 略"""

        current_qty, entry_price = fetch_position()
        if entry_price:
            saved_entry_price = entry_price

        """判定処理; 略"""

        if previous_qty != current_qty and not previous_qty:
            post_orders(
                previous_qty,
                saved_entry_price,
                other_params,
            )

        previous_qty = current_qty


"""略"""

変数saved_entry_priceを置きます。建玉が0になるとキー'avgEntryPrice'の値がNoneになるので、建玉があるうちに参入価格を保存しておく変数です。

建玉が変化しかつ直前の値が0以外の場合に、ポジション退出とみなして参入待ちオーダーに遷移します。そのタイミングで太郎チャートに記録するというのが、これまで説明してきた流れになります。

本稿趣旨とは離れますが、太郎チャートの活用に別のソリューションを提案されている方もいます。次の記事です。
証拠金残高の増減を太郎チャートに反映させるのが特徴です。汎用性がある手法で参考になると思います。

- § -

ドンチアン・チャネル・ブレイクアウト戦略は、いざトレンドに乗れば大きく利益が取れる運用戦略です。一方で小さな損が続くことがあるのも特徴です。低いレバレッジでじっくり取り組む手法であることを、あらためてお伝えしておきます。

損益に一喜一憂二憂三憂四憂五憂せず、滑りや手数料を考慮した実績確認に太郎チャートを活用してください。

TARO = True で。

RESOLUTION = '1d' FIRST_PERIOD_STICKS = 14 SECOND_PERIOD_STICKS = 3 MARKET_ID = 'ETHUSD' OC_MODE = True