見出し画像

backtestingを使った時の備忘録

※初めに実際の運用ではAPI制限や遅延などが原因でバックテストでは勝っていても実際には負けることもあります

株やFXなどのバックテストには様々な方法があります。
ベクトル演算で高速でやるもの
ループでやるもの(遅いけど作るのが簡単
今回は後者の方法でやります。

バックテストをする目的

パラメータのどこをいじれば改善するのか?
時間軸を変えて変化を見る
パラメータの最適化
とにかくどこがどのように変化したのかを見る

なぜbacktestingを使うのか?

理由は単純に簡単だから

メリット
- 可視化が楽
- コードを書くのが楽
- ATRトレーリングストップの実装も簡単

デメリット
- イグジットが次の足のopen価格か今のclose価格になるので実運用と乖離が起きやすい
- ループ演算型なので遅い

backtestingのインストール

pip install backtesting

今回は高頻度botのbacktestをします
ロジックはバンド系のロジックでナンピンをするもの
下のsampleデータを使用します。
事前にロジックのデータを入れてます。

bidとaskで指値liquidateを越えたら清算するというロジックです。

※コードについて特に説明はしませんがコメントアウトしといたのでそちらを見てください

# インポート
import pandas as pd
from backtesting import Strategy, Backtest
import warnings
warnings.filterwarnings('ignore')
# 上のcsvを実行環境に置いて配列をつくる.OHLCはint型にしておく(バグるので)
example_df = pd.read_csv("example.csv", dtype={
    "Open": int,
    "High": int,
    "Low": int,
    "Close": int,
    "Volume": float,
    "deviation": float,
    "bid1": float,
    "bid2": float,
    "bid3": float,
    "ask1": float,
    "ask2": float,
    "ask3": float,
    "liquidate": float
    })
example_df["datetime"] = pd.to_datetime(example_df["datetime"])
example_df = example_df.set_index("datetime")
print(example_df)
# 可視化だけしたいときはTrue
plt_only = False
# self.Iに入れるだけの関数 これをしないと反映されない
def any_data(data):
    return data

class Dev(Strategy):
    def init(self):
        # API制限を想定
        self.counter = 0    # カウント
        self.api_limit = 3  # 〇期間毎(デフォルトは3期間)

        # logic
        self.deviation = self.I(any_data, example_df["deviation"], name="deviation")
        self.bid1 = self.I(any_data, example_df["bid1"], name="bid1")
        self.bid2 = self.I(any_data, example_df["bid2"], name="bid2")
        self.bid3 = self.I(any_data, example_df["bid3"], name="bid3")
        self.ask1 = self.I(any_data, example_df["ask1"], name="ask1")
        self.ask2 = self.I(any_data, example_df["ask2"], name="ask2")
        self.ask3 = self.I(any_data, example_df["ask3"], name="ask3")
        self.liquidate = self.I(any_data, example_df["liquidate"], name="liquidate")

    def next(self):
        if not plt_only:    # 可視化だけのときはTrue
            self.counter += 1

            # ロングポジションを持っていたら
            if self.position.is_long and (self.data.High[-1] >= self.liquidate[-1]):
                self.position.close()
                # print(f"{self.data.Close[-1]}  で決済します")

            # ショートポジションを持っていたら
            if self.position.is_short and (self.data.Low <= self.liquidate[-1]):
                self.position.close()
                # print(f"{self.data.Close[-1]}  で決済します")

            # 指値のキャンセル
            if self.counter % self.api_limit == 0:
                self.orders.cancel()

            pos_size = self.position.size   # ポジションを持っている数

            if self.counter % self.api_limit == 0:
                # buy
                if self.deviation[-1] < 0.03:
                    if pos_size < 1:
                        self.buy(size=1, limit=self.bid1[-1])   # bid1に指値

                if self.deviation[-1] < 0.06:
                    if pos_size < 2:
                        self.buy(size=1, limit=self.bid2[-1])   # bid2に指値

                if self.deviation[-1] < 0.1:
                    if pos_size < 3:
                        self.buy(size=1, limit=self.bid3[-1])   # bid3に指値

                # sell
                if self.deviation[-1] > -0.03:
                    if pos_size > -1:
                        self.sell(size=1, limit=self.ask1[-1])   # ask1に指値

                if self.deviation[-1] > -0.06:
                    if pos_size > -2:
                        self.sell(size=1, limit=self.ask2[-1])   # ask2に指値

                if self.deviation[-1] > -0.1:
                    if pos_size > -3:
                        self.sell(size=1, limit=self.ask3[-1])   # ask3に指値

        else:
            pass

# 1000万円 手数料0 レバレッジ1倍 closeで清算する
bt = Backtest(example_df, Dev, cash=10_000_000, commission=0, margin=1, trade_on_close=True)
stats = bt.run()
print(stats)
bt.plot(resample=False) # リサンプルはしない

可視化した結果などは実際に試してみてください

いいなと思ったら応援しよう!