トレードや株取引の戦略・手法を考えたら,それがどれくらい良い戦略なのかを試すために過去のデータを使ってシミュレーションを行うと思います.考えた戦略でいきなり取引するのは少し怖いですからね.これをバックテストといいます.
今回は,簡単にバックテストできるPythonライブラリ『Backtesting.py』を使ってみます.
Backtesting.pyでは,変数を移動させて戦略を最適化する機能(例えば,移動平均線の日数を変化させる)もあるので,そちらも試してみます.
#Backtesting.pyのインストール
pipなら
pip install backtesting
でできます.
#株価データの取得
今回は,2018/1/1~現在までのKO(コカ・コーラ)の株価を取得したいと思います.
株価はpandas_datareaderで取得します.もちろんウェブやローカルにあるCSVファイルを読み込むなどでも構いません.
import pandas_datareader.data as web
import datetime
start = datetime.date(2018,1,1)
end = datetime.date.today()
data = web.DataReader('KO', 'yahoo', start, end)
pandas-datareaderで取得したデータは以下のように,High(高値),Low(低値),Open(始値),Close(終値),Volume(出来高),Adj Close(調整後終値)を持つDataFarameとなっています.
High Low Open Close Volume Adj Close
Date
2018-01-02 45.939999 45.509998 45.910000 45.540001 10872200.0 40.913391
2018-01-03 45.689999 45.340000 45.490002 45.439999 12635600.0 40.823551
2018-01-04 46.220001 45.450001 45.560001 46.080002 12709400.0 41.398529
2018-01-05 46.200001 45.790001 46.020000 46.070000 13113100.0 41.389545
2018-01-08 46.099998 45.880001 45.950001 46.000000 7068600.0 41.326656
... ... ... ... ... ... ...
2021-04-12 53.549999 53.099998 53.330002 53.349998 8565300.0 53.349998
2021-04-13 53.290001 52.810001 53.040001 53.090000 11071700.0 53.090000
2021-04-14 53.189999 52.650002 52.980000 53.080002 9787600.0 53.080002
2021-04-15 53.660000 53.119999 53.130001 53.330002 13078100.0 53.330002
2021-04-16 53.799999 53.380001 53.740002 53.680000 17958400.0 53.680000
Backtesting.pyを使用するには,データが"Open", "High", "Low", "Close"のカラムを持つ必要があります.pandas-datareaderで取得した場合はすでにこれらのカラムを持っているので問題ないですね.
#Backtesting.pyでバックテスト
##事前準備(戦略記述)
今回はお試しなので,2つのSMA(単純移動平均線)によるゴールデンクロス,デッドクロスで売買をする戦略でいきたいと思います.
SMAには,10日と30日を使ってみます(特に深い意味はありません).
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
class SmaCross(Strategy):
n1 = 10 # 短期SMA
n2 = 30 # 長期SMA
def init(self):
self.sma1 = self.I(SMA, self.data.Close, self.n1)
self.sma2 = self.I(SMA, self.data.Close, self.n2)
def next(self): #チャートデータの行ごとに呼び出される
if crossover(self.sma1, self.sma2): #sma1がsma2を上回った時
self.buy() # 買い
elif crossover(self.sma2, self.sma1):
self.position.close() # 売り
##バックテストを実施
bt = Backtest(
data, # チャートデータ
SmaCross, # 売買戦略
cash=1000, # 最初の所持金
commission=0.00495, # 取引手数料
margin=1.0, # レバレッジ倍率の逆数(0.5で2倍レバレッジ)
trade_on_close=True, # True:現在の終値で取引,False:次の時間の始値で取引
exclusive_orders=True #自動でポジションをクローズ
)
output = bt.run() # バックテスト実行
print(output) # 実行結果(データ)
bt.plot() # 実行結果(グラフ)
##結果
Start 2018-01-02 00:00:00
End 2021-04-16 00:00:00
Duration 1200 days 00:00:00
Exposure Time [%] 63.0435
Equity Final [$] 938.099
Equity Peak [$] 1205.96
Return [%] -6.19008
Buy & Hold Return [%] 17.8744
Return (Ann.) [%] -1.92598
Volatility (Ann.) [%] 14.1436
Sharpe Ratio 0
Sortino Ratio 0
Calmar Ratio 0
Max. Drawdown [%] -28.3291
Avg. Drawdown [%] -3.40125
Max. Drawdown Duration 420 days 00:00:00
Avg. Drawdown Duration 50 days 00:00:00
# Trades 15
Win Rate [%] 33.3333
Best Trade [%] 15.0922
Worst Trade [%] -7.21554
Avg. Trade [%] -0.449168
Max. Trade Duration 177 days 00:00:00
Avg. Trade Duration 49 days 00:00:00
Profit Factor 0.881756
Expectancy [%] -0.293761
SQN -0.288313
_strategy SmaCross
_equity_curve ...
_trades Size EntryB...
※各項目の意味はこちらの記事の一番下に記載しています
15回取引を行い,所持金1000に対して最終資産は938,リターンは-6.2%という結果になりました.
この戦略では損をしてしまうようですね.
戦略を少し変えてみましょう.
##戦略を最適化
戦略を変えると言っても,2つのSMA(単純移動平均線)によるゴールデンクロス,デッドクロスで売買をする戦略はそのままで,SMAの期間を変えてみたいと思います.
以下のようにすることで,SMAを10~70日の範囲で5日刻みで変化させてEquity Final(最終資産額)が最大になるようなSMAの期間を探してくれます.
※maximizeはデフォルトではSQN(System Quality Number)です
#最適化
output2=bt.optimize(n1=range(10, 70, 5), n2=range(10, 70, 5), maximize='Equity Final [$]')
print(output2)
bt.plot()
※ちなみに,以下のようにconsraintオプションを付けることで短期SMAが長期SMAより長くなることを禁止できます.
output2=bt.optimize(n1=range(10, 70, 5), n2=range(10, 70, 5), maximize='Equity Final [$]', constraint=lambda p: p.n1 < p.n2)
###最適化の結果
Start 2018-01-02 00:00:00
End 2021-04-16 00:00:00
Duration 1200 days 00:00:00
Exposure Time [%] 68.7198
Equity Final [$] 3217.68
Equity Peak [$] 3414.71
Return [%] 221.768
Buy & Hold Return [%] 211.529
Return (Ann.) [%] 42.715
Volatility (Ann.) [%] 32.9843
Sharpe Ratio 1.29501
Sortino Ratio 2.87914
Calmar Ratio 1.69859
Max. Drawdown [%] -25.1473
Avg. Drawdown [%] -3.24492
Max. Drawdown Duration 227 days 00:00:00
Avg. Drawdown Duration 24 days 00:00:00
# Trades 13
Win Rate [%] 61.5385
Best Trade [%] 55.2328
Worst Trade [%] -6.4954
Avg. Trade [%] 9.68661
Max. Trade Duration 175 days 00:00:00
Avg. Trade Duration 62 days 00:00:00
Profit Factor 7.3015
Expectancy [%] 10.9874
SQN 1.86309
_strategy SmaCross(n1=20,n...
_equity_curve ...
_trades Size EntryB...
最終資産は3217,リターンは221%という結果になりました.
移動平均線が20日と25日,取引回数が13回となっています.
このような取引をしていれば,利益がプラスとなることになります.もちろん,これは「過去のデータでおこなった場合」なので,この日数が未来においても最適なのかを考えるにはいろいろな期間を設定して良い結果を出すのかを調べてみる必要があります.
#まとめコード
import pandas_datareader.data as web
import datetime
start = datetime.date(2018,1,1)
end = datetime.date.today()
data = web.DataReader('KO', 'yahoo', start, end) #データの取得
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
class SmaCross(Strategy):
n1 = 10 # 短期SMA
n2 = 30 # 長期SMA
def init(self):
self.sma1 = self.I(SMA, self.data.Close, self.n1)
self.sma2 = self.I(SMA, self.data.Close, self.n2)
def next(self): # チャートデータの行ごとに呼び出される
if crossover(self.sma1, self.sma2): # sma1がsma2を上回った時
self.buy() # 買い
elif crossover(self.sma2, self.sma1):
self.position.close() # 売り
# バックテストを設定
bt = Backtest(
data, # チャートデータ
SmaCross, # 売買戦略
cash=1000, # 最初の所持金
commission=0.00495, # 取引手数料
margin=1.0, # レバレッジ倍率の逆数(0.5で2倍レバレッジ)
trade_on_close=True, # True:現在の終値で取引,False:次の時間の始値で取引
exclusive_orders=True #自動でポジションをクローズ
)
output = bt.run() # バックテスト実行
print(output) # 実行結果(データ)
bt.plot() # 実行結果(グラフ)
#最適化
output2=bt.optimize(n1=range(10, 70, 5), n2=range(10, 70, 5), maximize='Equity Final [$]')
print(output2)
bt.plot()
#他の戦略をバックテスト
今後は,色々な投資戦略を試していきたいと思います.
手始めに,MACDでやってみました.
ちなみに,SMAやMACDのような指標の生成はTA-Libを使うと楽だと思います.
投資に関する免責事項
プログラムや考え方の情報の提供・作業代行を目的としており,投資勧誘を目的とするものではありません.また,この記事は投資成績を保証するものではありません.投資はあくまで自己責任でお願いします.