このページでは、Pythonで作成したFX自動売買ツールのバックテストを行う方法をご紹介しています。
Pythonでバックテストを行うメリットは以下の通りです。
- MT4やMT5のバックテストでは確認することが難しかった細かいところまで手が届くオリジナルのバックテストツールを作成できる。
- バックテスト結果を簡単にグラフ化やデータ分析を行なって可視化できる。
この記事は、以下の前回の記事の続きです。
ループ処理のコード全文
前回の記事までに作成したループ処理部分についてまとめたコード全文は以下の通りです。
for i in range(len(df_tick)):
##現在時刻、価格の取得
time = df_tick['time'][i]
bid = df_tick['bid'][i]
ask = df_tick['ask'][i]
##ポジションの含み損益の確認
if buy_lot == 0:
buy_profit = 0
else:
buy_profit = round((bid - buy_price) * buy_lot * pip_value / 0.01, 0)
if sell_lot == 0:
sell_profit = 0
else:
sell_profit = round(-(ask - sell_price) * sell_lot * pip_value / 0.01, 0)
##新規buyエントリー
if buy_position == 0: # buyポジションがない場合
buy_position = 1 # buyポジション数
current_buy_lot = first_lot # 最新のbuyポジションのlot数
buy_lot = first_lot # buyポジションのlot数合計
current_buy_price = ask # 最新のbuyポジション価格
buy_price = ask # buyポジションの平均価格
##新規sellエントリー
if sell_position == 0: # sellポジションがない場合
sell_position = 1 # sellポジション数
current_sell_lot = first_lot # 最新のsellポジションのlot数
sell_lot = first_lot # sellポジションのlot数合計
current_sell_price = bid # 最新のsellポジション価格
sell_price = bid # sellポジションの平均価格
##追加buyエントリー
if buy_position > 0 and ask < current_buy_price - nanpin_range * point:
buy_position += 1 # buyポジション数
x = buy_lot * buy_price # 平均価格算出用
current_buy_lot = round(current_buy_lot * 1.5 + 0.001, 2) # 最新のbuyポジションのlot数
buy_lot += current_buy_lot # buyポジションのlot数合計
current_buy_price = ask # 最新のbuyポジション価格
y = current_buy_lot * current_buy_price # 平均価格算出用
buy_price = round(( x + y ) / buy_lot, 2) # buyポジションの平均価格
##追加sellエントリー
if sell_position > 0 and bid > current_sell_price + nanpin_range * point:
sell_position += 1 # sellポジション数
x = sell_lot * sell_price # 平均価格算出用
current_sell_lot = round(current_sell_lot * 1.5 + 0.001, 2) # 最新のsellポジションのlot数
sell_lot += current_sell_lot # sellポジションのlot数合計
current_sell_price = bid # 最新のsellポジション価格
y = current_sell_lot * current_sell_price # 平均価格算出用
sell_price = round(( x + y ) / sell_lot, 2) # sellポジションの平均価格
##buyクローズ
if buy_position > 0 and buy_profit > profit_target * buy_position:
buy_position = 0 # buyポジション数の初期化
buy_profit = 0 # buy_profitの初期化
current_buy_lot = 0 # 最新のbuyポジションのlot数の初期化
buy_lot = 0 # buyポジションのlot数合計の初期化
current_buy_price = 0 # 最新のbuyポジション価格の初期化
buy_price = 0 # buyポジションの平均価格の初期化
##sellクローズ
if sell_position > 0 and sell_profit > profit_target * sell_position:
sell_position = 0 # sellポジション数の初期化
sell_profit = 0 # sell_profitの初期化
current_sell_lot = 0 # 最新のsellポジションのlot数の初期化
sell_lot = 0 # sellポジションのlot数合計の初期化
current_sell_price = 0 # 最新のsellポジション価格の初期化
sell_price = 0 # sellポジションの平均価格の初期化
インプットデータであるdf_tickを用意して、必要な初期設定を行なった後、この部分をコピーして実行すれば、普通に動くはずです。用意したティックデータ分だけ全レコードについてナンピンマーチンロジックが働いて、注文を繰り返すでしょう。これで完成です。
と、言いたいところですが、実行してみたらわかりますが、ただループ処理が実行されて完了するだけです。何もアウトプットがありません。
そこでこの記事では、このループ処理の中で必要なアウトプット(結果出力)を追加する作業を行っていきます。
アウトプット用データフレーム(df)の作成
まずは、結果を出力するためのデータフレーム(df)の箱を用意します。
cols = [
'time',
'bid',
'ask',
'type', # 注文タイプ
'position', # ポジション数
'profit', # ポジションの含み損益
'lot', # ポジションのlot数合計
'price', # ポジションの平均価格
]
df_orders = pd.DataFrame(index=[], columns=cols)
これは、ループ処理の手前のどこか適当なところに用意して実行しておくだけでOKです。
作成したdf_ordersは、以下の通りただの箱(空の器)です。
ここに、各注文(オーダー)を格納していくことになります。
各オーダーの格納処理
ここからは、それぞれの注文処理の中にdf_ordersへの格納処理を追記していきます。
新規エントリー注文(buy)
buyポジションの新規エントリー注文は以下の通りでした。
##新規buyエントリー
if buy_position == 0: # buyポジションがない場合
buy_position = 1 # buyポジション数
current_buy_lot = first_lot # 最新のbuyポジションのlot数
buy_lot = first_lot # buyポジションのlot数合計
current_buy_price = ask # 最新のbuyポジション価格
buy_price = ask # buyポジションの平均価格
この一番下に、以下のコードを追加します。
##df_ordersに出力
order = pd.Series([
time,
bid,
ask,
'first_buy', # 注文タイプ
buy_position, # buyポジション数
0, # クローズ注文ではないので実現損益ゼロ
current_buy_lot, # 最新のbuyポジションのlot数
current_buy_price, # 最新のbuyポジション価格
], index=df_orders.columns)
df_orders = df_orders.append(order, ignore_index=True)
例えば、最初のオーダーは以下のように格納されます。
time、bid、ask
それぞれ注文時の時間や価格情報を記録しています。
type
注文の種類です。新規エントリー注文の場合は、’first_buy’としています。
position
ポジション数です。この場合はbuyポジションのポジション数を表します。
profit
実現した損益を記録しておくための枠なので、クローズ注文時以外はゼロとしておきます。
lot
注文のロット数です。最初のエントリーなので、first_lotである0.01です。
price
buyオーダーなので、askと同じ値が入ります。
新規エントリー注文(sell)
続いて、sellポジションの新規エントリー注文です。
##新規sellエントリー
if sell_position == 0: # sellポジションがない場合
sell_position = 1 # sellポジション数
current_sell_lot = first_lot # 最新のsellポジションのlot数
sell_lot = first_lot # sellポジションのlot数合計
current_sell_price = bid # 最新のsellポジション価格
sell_price = bid # sellポジションの平均価格
この一番下に、以下のコードを追加します。
##df_ordersに出力
order = pd.Series([
time,
bid,
ask,
'first_sell', # 注文タイプ
sell_position, # buyポジション数
0, # クローズ注文ではないので実現損益ゼロ
current_sell_lot, # 最新のsellポジションのlot数
current_sell_price, # 最新のsellポジション価格
], index=df_orders.columns)
df_orders = df_orders.append(order, ignore_index=True)
例えば、最初のオーダーは以下のように格納されます。
time、bid、ask
それぞれ注文時の時間や価格情報を記録しています。
type
注文の種類です。新規エントリー注文の場合は、’first_sell’としています。
position
ポジション数です。この場合はsellポジションのポジション数を表します。
profit
実現した損益を記録しておくための枠なので、クローズ注文時以外はゼロとしておきます。
lot
注文のロット数です。最初のエントリーなので、first_lotである0.01です。
price
sellオーダーなので、bidと同じ値が入ります。
追加エントリー(ナンピン)注文(buy)
buyポジションの追加エントリー注文は以下の通りでした。
##追加buyエントリー
if buy_position > 0 and ask < current_buy_price - nanpin_range * point:
buy_position += 1 # buyポジション数
x = buy_lot * buy_price # 平均価格算出用
current_buy_lot = round(current_buy_lot * 1.5 + 0.001, 2) # 最新のbuyポジションのlot数
buy_lot += current_buy_lot # buyポジションのlot数合計
current_buy_price = ask # 最新のbuyポジション価格
y = current_buy_lot * current_buy_price # 平均価格算出用
buy_price = round(( x + y ) / buy_lot, 2) # buyポジションの平均価格
この一番下に、以下のコードを追加します。
##df_ordersに出力
order = pd.Series([
time,
bid,
ask,
'nanpin_buy', # 注文タイプ
buy_position, # buyポジション数
0, # クローズ注文ではないので実現損益ゼロ
current_buy_lot, # 最新のbuyポジションのlot数
current_buy_price, # 最新のbuyポジション価格
], index=df_orders.columns)
df_orders = df_orders.append(order, ignore_index=True)
新規buyエントリーとの違いは、注文タイプのコメントを’nanpin-buy’に変えているのみです。
追加エントリー(ナンピン)注文(sell)
sellポジションの追加エントリー注文は以下の通りでした。
##追加sellエントリー
if sell_position > 0 and bid > current_sell_price + nanpin_range * point:
sell_position += 1 # sellポジション数
x = sell_lot * sell_price # 平均価格算出用
current_sell_lot = round(current_sell_lot * 1.5 + 0.001, 2) # 最新のsellポジションのlot数
sell_lot += current_sell_lot # sellポジションのlot数合計
current_sell_price = bid # 最新のsellポジション価格
y = current_sell_lot * current_sell_price # 平均価格算出用
sell_price = round(( x + y ) / sell_lot, 2) # sellポジションの平均価格
この一番下に、以下のコードを追加します。
##df_ordersに出力
order = pd.Series([
time,
bid,
ask,
'nanpin_sell', # 注文タイプ
sell_position, # buyポジション数
0, # クローズ注文ではないので実現損益ゼロ
current_sell_lot, # 最新のsellポジションのlot数
current_sell_price, # 最新のsellポジション価格
], index=df_orders.columns)
df_orders = df_orders.append(order, ignore_index=True)
新規sellエントリーとの違いは、注文タイプのコメントを’nanpin-sell’に変えているのみです。
ポジションクローズ注文(buy)
buyポジションのポジションクローズ注文は以下の通りでした。
##buyクローズ
if buy_position > 0 and buy_profit > profit_target * buy_position:
buy_position = 0 # buyポジション数の初期化
buy_profit = 0 # buy_profitの初期化
current_buy_lot = 0 # 最新のbuyポジションのlot数の初期化
buy_lot = 0 # buyポジションのlot数合計の初期化
current_buy_price = 0 # 最新のbuyポジション価格の初期化
buy_price = 0 # buyポジションの平均価格の初期化
このif文のすぐ下(各変数を初期化する前)に、以下のコードを追加します。
##df_ordersに出力
order = pd.Series([
time,
bid,
ask,
'buy_close', # 注文タイプ
buy_position, # buyポジション数
buy_profit, # buyポジションの含み損益
buy_lot, # buyポジションのlot数合計
buy_price, # buyポジションの平均価格
], index=df_orders.columns)
df_orders = df_orders.append(order, ignore_index=True)
ポジションクローズ注文(sell)
sellポジションのポジションクローズ注文は以下の通りでした。
##sellクローズ
if sell_position > 0 and sell_profit > profit_target * sell_position:
sell_position = 0 # sellポジション数の初期化
sell_profit = 0 # sell_profitの初期化
current_sell_lot = 0 # 最新のsellポジションのlot数の初期化
sell_lot = 0 # sellポジションのlot数合計の初期化
current_sell_price = 0 # 最新のsellポジション価格の初期化
sell_price = 0 # sellポジションの平均価格の初期化
このif文のすぐ下(各変数を初期化する前)に、以下のコードを追加します。
##df_ordersに出力
order = pd.Series([
time,
bid,
ask,
'sell_close', # 注文タイプ
sell_position, # sellポジション数
sell_profit, # sellポジションの含み損益
sell_lot, # sellポジションのlot数合計
sell_price, # sellポジションの平均価格
], index=df_orders.columns)
df_orders = df_orders.append(order, ignore_index=True)
出力結果の確認
以上、これまでの記事を全て踏まえて実行すると出力される結果は以下のようになります。
※0~11行目までの12行のみ抜粋
buyポジションの方は、「first_buy→buy_close」と、1つのポジションだけで価格幅1.00程度の利益を積み重ねています。
一方のsellポジションの方は、「first_sell→nanpin_sell→nanpin_sell→sell_close」と、ナンピンを2回重ねてポジション数が3つになった後、目標利益に到達してポジションをクローズしています。
ナンピン幅が2.00程度になっていること、1つのポジションでの利益が147円程度になっていること、ナンピンのマーチン倍率(ロット数)が想定通りであること、あたりを確認して、問題なく動作が出来ていることを確認します。
続きの記事
以上、これまでの記事で紹介したコードを順番にコピペしていけば、基本的なバックテストモデルは本当に完成です。インプットデータを変えたり、ナンピンロジックを変更したりして検証することが可能になりました。
以下の記事では、出力結果をグラフで表示して視覚化するなどもう少し発展的な内容も解説しています。ぜひご覧ください。
numbaで高速化
Pythonでバックテストする方法について、numbaによる高速化も取り入れてより効率的に行える仕組みついてまとめました。是非あわせてご覧ください。