BTC-FXストラテジー「クロコダイル」のduelbot版を無償公開

こんにちわ。DuelMaster@イナトレです。

この BTC-FX 界隈では知らない人のほうが少ないかもしれませんが、つい先日テクノファンダさん(@TechnoFunda0)がクロコダイルという TradingView ストラテジーを無償公開されました。

せっかく本文の中で duelbot をご紹介いただくも python は覚える気力が...とのことなので、移植の許可をいただき duelbot 版のストラテジーを作成してみました。duelbot 本体内蔵の関数を呼んでる部分が多々あるので、他の bot ではそのまま動きませんが、python コードということで他の bot に移植可能な部分もあるかと思います。ここで公開するコードは許可なく持っていって構いません(※オリジナルの著作権はテクノファンダさんにありますのでご留意ください)。

ちなみにどのようなストラテジーなのかは、上記のトレード大学院第二回講義を熟読いただくとともに、すでに ProjectBBB さんの方でロジックの検証をされてますので、そちらの note を読んでください。私があえて何かを書かなくても良いかなーと。

■DuelBot 版ストラテジーのコード

さて、以下コードです。ver310 向けのロジックとして書きました。またベータ段階ではありますが duelbot をお使いの皆様は、これを機会に ver310 ベータ2への移行を検討されてください。

使い方はいつもと同じく strategy フォルダ配下に以下のコードをコピペしたファイルを作成いただき配置するだけです。面倒だと思いますのでコミュニティーの方でがファイルをダウンロードできるようにしておきます。

st_crocodile_config.json

{
    "==comment=="   : "↓↓↓↓ ここから先は strategy 向けの config を記述する ↓↓↓↓",

    "==comment=="   : "↓ 一度の注文で使うロット数を資産額の割合で指定",
    "LEVERAGE"      : 1.0,

    "==comment=="   : "↓ 保持できるロット数を最大レバレッジで制限する",
    "MAX_LEVERAGE"  : 9.0,

    "==comment=="   : "↓ FASTRSIの期間",
    "FASTRSI_TERM" : 7,

    "==comment=="   : "↓ FASTRSIの上限",
    "FASTRSI_UPPER" : 80,

    "==comment=="   : "↓ FASTRSIの下限",
    "FASTRSI_LOWER" : 20,

    "==comment=="   : "↓ 注文を成行か指値か選択。成行なら true を指定。指値使い場合は false",
    "IS_USE_TAKER"  : true,

    "==comment=="   : "↓ ここは変更しない",
    "isBottomJson" : true
}

st_crocodile.py

## ver3.1専用
###################################################################################
## DuelBot - st_crocodile_std / ver180701
'''
【説明】
    - @TechnoFunda0 さんから許可いただいたクロコダイル duelbot 版です。足=1800推奨
    - https://sayatori-arbi.com/2018/06/28/graduateschool2/

【カスタマイズの方法】
    - st_crocodile_config.json でパラメータ調節してみてください

'''
###################################################################################

###################################################################################
## ★★★ ユーザが設定変更して良い変数は全て config に入れてください ★★★
###################################################################################

###################################################################################
## 定数定義
###################################################################################
REQUIRE_BOT_VERSION = 310

###################################################################################
## calcIndicator: 指標を生成
##  input:  -
##  output: -
##  アドバイス:他のTA指標を使いたい場合に、ここで解析してGLB_indicator変数に入れます
###################################################################################
def calcIndicator():
    global GLB_indicator
    global GLB_order_signal

    ## テクニカル分析変数の準備(指標はTVに合わせて数値ブレがないように一つ前の足で計算する)
    candles = getCandles(GLB_config['candleTimeFrame'])[:-1]
    GLB_indicator['candles'] = candles
    hlc3_piddle = np.array([(ohlcv[H] + ohlcv[L] + ohlcv[C]) / 3 for ohlcv in candles], dtype='f8')
    body_piddle = np.array([abs(ohlcv[O] - ohlcv[C]) for ohlcv in candles], dtype='f8')

    open = candles[-1][O]
    high = candles[-1][H]
    low = candles[-1][L]
    close = candles[-1][C]

    ## Average Body
    body = abs(close - open)
    GLB_indicator['BODY'] = body
    GLB_indicator['AVG_BODY'] = (ta.EMA(body_piddle, 5))[-1]

    ## RSI
    period = GLB_STconfig['FASTRSI_TERM']
    rsi0 = fastrsi(hlc3_piddle, period)
    rsi1 = fastrsi(hlc3_piddle[:-1], period)
    rsi2 = fastrsi(hlc3_piddle[:-2], period)
    rsi3 = fastrsi(hlc3_piddle[:-3], period)
    rsi_sma = (rsi0 + rsi1 + rsi2 + rsi3) / 4
    GLB_indicator['RSI'] = rsi0
    GLB_indicator['RSI_SMA'] = rsi_sma

    ## 判定
    th_body = GLB_indicator['AVG_BODY'] * 1.1
    up = close < open and th_body < body and rsi_sma < GLB_STconfig['FASTRSI_LOWER']
    dn = open < close and th_body < body and GLB_STconfig['FASTRSI_UPPER'] < rsi_sma
    GLB_indicator['UP'] = up
    GLB_indicator['DN'] = dn

    ## debug
    logger.debug('Indicator OHLCV=' + str(candles[-1]))
    logger.debug('Indicator RSI=' + str(GLB_indicator['RSI']))
    logger.debug('Indicator RSI_SMA=' + str(GLB_indicator['RSI_SMA']))
    logger.debug('Indicator BODY=' + str(GLB_indicator['BODY']))
    logger.debug('Indicator AVG_BODY=' + str(GLB_indicator['AVG_BODY']))
    logger.debug('Indicator UP=' + str(GLB_indicator['UP']))
    logger.debug('Indicator DN=' + str(GLB_indicator['DN']))

    GLB_indicator['EXIT_SIGNAL'] = False
    GLB_order_signal = OrderSignal.NONE

###################################################################################
## fastrsi: python版 FasrRSI
##  input:  close_piddle, period
##  output: -
###################################################################################
def fastrsi(close_piddle, period):
    close = pd.Series(close_piddle)
    diff = close.diff(1)
    positive = diff.clip_lower(0).ewm(alpha=1 / period).mean()
    negative = diff.clip_upper(0).ewm(alpha=1 / period).mean()
    up = abs(positive.tail(1).values[0])
    dn = abs(negative.tail(1).values[0])
    if up == 0:
        return 100
    elif dn == 0:
        return 0
    else:
        return 100 - 100 / (1 + up / dn)

###################################################################################
## createTradeSignal: 指標から売買サインを判断
##  input:  position
##  output: -
###################################################################################
def createTradeSignal(position):
    global GLB_indicator
    global GLB_order_signal

    candles = GLB_indicator['candles']
    open = candles[-1][O]
    high = candles[-1][H]
    low = candles[-1][L]
    close = candles[-1][C]

    rsi = GLB_indicator['RSI']

    if position is not None:
        psize = position['currentQty']
        if ((0 < psize and GLB_STconfig['FASTRSI_LOWER'] < GLB_indicator['RSI']) or (psize < 0 and GLB_indicator['RSI'] < GLB_STconfig['FASTRSI_UPPER'])):
            GLB_indicator['EXIT_SIGNAL'] = True

    logger.debug('Signal UP=' + str(GLB_indicator['UP']))
    logger.debug('Signal DN=' + str(GLB_indicator['DN']))
    logger.debug('Signal EXIT=' + str(GLB_indicator['EXIT_SIGNAL']))

    if GLB_indicator['UP']:
        GLB_order_signal = OrderSignal.BUY
        logger.info('Find a long position signal')
    if GLB_indicator['DN']:
        GLB_order_signal = OrderSignal.SELL
        logger.info('Find a short position signal')

###################################################################################
## noPositionStrategy: ノーポジ時の戦略アルゴリズム
##  input:  position, balance, lastOrderTimestamp
##  output: True / False
###################################################################################
def noPositionStrategy(position, balance, lastOrderTimestamp):
    global GLB_indicator
    global GLB_order_signal
    last = GLB_tickers[-1]['last']

    logger.debug('Judge no position logic')

    ## マスタークロックと現在時刻を判定して、トレードする足との時間軸を揃える(そうじゃないとTVの結果と全然異なる)
    if not isSynchronizedWithMasterClock():
        logger.debug('Do nothing because clock is out of sync')
        return False

    ## 残高が足りなければ何もしない(初期ロットは指定の2倍でインする)
    lot = getLotPerOrder(GLB_STconfig['LEVERAGE'], last, balance, None)
    if lot < 1:
        logger.warn('Order was canceled due to insufficient assets')
        return False

    ## 売買チャンスあるか判定する
    createTradeSignal(position)

    ## シグナルがないときは何もしない
    if GLB_order_signal == OrderSignal.NONE:
        return False

    ## シグナルがあればシグナルに応じた売買注文をだす
    logger.debug('Try creating position: signal=' + str(GLB_order_signal))
    newPosition(GLB_order_signal, lot, GLB_STconfig['IS_USE_TAKER'])

    return True

###################################################################################
## positionStrategy:ポジション時の戦略アルゴリズム
##  input:  position, balance, lastOrderTimestamp
##  output: True / False
###################################################################################
def positionStrategy(position, balance, lastOrderTimestamp):
    global GLB_indicator
    global GLB_order_signal
    last = GLB_tickers[-1]['last']

    logger.debug('Judge position logic')

    ## ポジションがなぜか見つからないときは何もしない(Mex側でロスカなど)
    if position is None:
        return False

    ## 売買チャンスあるか判定する
    createTradeSignal(position)

    ## マスタークロックと現在時刻を判定して、トレードする足との時間軸を揃える(そうじゃないとTVの結果と全然異なる)
    if not isSynchronizedWithMasterClock():
        logger.debug('Do nothing because clock is out of sync')
        return False

    ## 利確シグナル(基本プラマイ0以上を要求する)
    if GLB_indicator['EXIT_SIGNAL']:
        lot = abs(position['currentQty'])
        logger.info('Try exit positiob: signal=' + str(GLB_order_signal) + ', lot=' + str(lot))
        closePosition(position)
        return True

    return False

###################################################################################
## losscutStrategy: ロスカット戦略アルゴリズム
##  input:  position, balance, lastOrderTimestamp
##  output: True / False
###################################################################################
def losscutStrategy(position, balance, lastOrderTimestamp):
    return False

###################################################################################
## getLotPerOrder: 最新の状態で出せるロット数を取得
##  input:  leverage, last, balance, position
##  output: lot
###################################################################################
def getLotPerOrder(leverage, last, balance, position):
    base_symbol = GLB_config['base_symbol']

    price = last if GLB_config['api_symbol'] == 'XBTUSD' else 1 / last
    lot = int(leverage * balance['free'][base_symbol] * price)

    max_lot = int(GLB_STconfig['MAX_LEVERAGE'] * balance['total'][base_symbol] * price)
    if position is not None and max_lot < abs(position['currentQty']) + lot:
        lot = max_lot - abs(position['currentQty'])
    lot = max(0, lot)
    logger.debug('LotPerOrder=' + str(lot))
    return lot

■2018-01-01からのパフォーマンス

まずは TradingView での結果。30分足が一番性能が良かったため、これを基準に正しく移植できているかの検証を行っていました。レバレッジはいずれの検証も 1 倍で検証しました。

オリジナルと同じロジックの結果

duelbot のバックテストを使って比較検証を行いました。TradingView の結果では 1 月はノートレードでしたが、duelbot 版では 1 月も取引履歴が存在しており、パフォーマンスは 65.5% 増となりました。値幅による利確・損切りロジックが存在しないのに、きっちりと利益とってて優秀です。

コードの有効性を検証するためにトレードのタイミングも比較しました。TradingView と計算結果が若干ずれる期間があるため、完璧に一緒ではありませんが、ほぼ同じ傾向であることを確認しました。

2018-02-01 19:00:00,10345,9515.5,buy,backtest_1517479200000
2018-02-01 19:00:00,0,0,fee,-0.000815380169197625
2018-02-01 20:30:00,10345,9540.5,sell,backtest_1517484600000
2018-02-01 20:30:00,0,0,fee,-0.0008132435406949321

2018-02-01 21:30:00,10149,9325,buy,backtest_1517488200000
2018-02-01 21:30:00,0,0,fee,-0.0008162734584450402
2018-02-01 23:30:00,10149,9315.5,sell,backtest_1517495400000
2018-02-01 23:30:00,0,0,fee,-0.0008171058987708657

2018-02-02 21:30:00,8440,7774,buy,backtest_1517574600000
2018-02-02 21:30:00,0,0,fee,-0.0008142526369951119
2018-02-02 22:00:00,8440,8240,sell,backtest_1517576400000
2018-02-02 22:00:00,0,0,fee,-0.0007682038834951456

2018-02-06 05:00:00,7528,6572.5,buy,backtest_1517860800000
2018-02-06 05:00:00,0,0,fee,-0.0008590338531761126
2018-02-06 05:30:00,7528,7078,sell,backtest_1517862600000
2018-02-06 05:30:00,0,0,fee,-0.0007976829612884995

同期間の TradingView でのトレード一覧は下記の通りでした。

コミュニティー限定の改造ロジック版の結果

こちらは残念ながら duelbot をご購入の方にしか公開しないコミュニティー限定版のロジックですが、そのパフォーマンス結果は以下のとおりです。同じくレバレッジ 1 倍でのバックテスト結果です。こちらは 152.8% 増の結果となりました。

ちなみにレバレッジを 2 倍にすると改造版のパフォーマンスは 501% 増になりました。

■あとがき

今後もオリジナルロジックも公開していきますし、許可が頂けるものは移植し公開していきます。DuelBot が気になった方は是非購入のご検討をよろしくお願い致します。

■免責事項

最後にお約束事項ではございますが、いかなる損失や損害などの被害が発生したとしても、私はその責任を負いかねます。ご利用は読者様個人の判断により自己責任でお願いいたします。

Let's enjoy bot trading !! 😁


この記事が気に入ったらサポートをしてみませんか?