2021.04.07

アルゴリズムトレード入門
~ python で自動取引を始めてみる ~

はじめに

こんにちは、次世代システム研究室のT.I.です。

みなさま、本日もお仕事お疲れ様です。今回は楽して儲けたい(?)というテーマで株式や為替などの自動取引について紹介したいと思います。過去にも同様のテーマでのブログはありますが、いったん初心にかえって1から入門編として始めたいと思います。AIで自動取引というと小難しい数学を駆使して応用すると思われるかもしれませんが、実は簡単な計算の組み合わせだけで出来るので実際に手を動かして感覚を掴んでいただければと思います。

Pythonでゼロ(?)から始める自動取引

データ収集

まず、最初にデータを準備する必要があります。為替レートや株価などは様々なsiteで公開されていますが、一旦 download したり、少々手間ですよね。python library の1つである、pandas_datareader を利用すると、簡単に様々なデータソースから過去のチャートの情報を取得することができます。例えば、Apple の株価を2010/4/1から2021/3/31まで取得するには、以下のようにします。(以下、サンプル・コードは、Google Colaboratory や Jupyter Lab へコピペするだけで動かすことができます)
import pandas_datareader as pdr
from datetime import datetime
AAPL = pdr.DataReader('AAPL', data_source='yahoo', 
                       start=datetime(2010, 4, 1), end=datetime(2021, 3, 31))
Yahoo の data source を利用すると、指定した期間の日毎の高値、安値、始値、終値、出来高、調整後終値が pandas library の DataFrameとして取得できます。pandas とは python のデータ分析で不可欠な library で、様々なデータの確認・集計や可視化が簡単にできます。


 
AAPL.plot(y='Adj Close') # 結果の可視化

ただ、default のままでは文字が小さかったりするので(個人の好みもあるとは思いますが)、seaborn library を import として色々と option を変えてみるのも良いですね。
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

import seaborn as sns
sns.set_theme(context='talk', style='whitegrid', palette='Set2',
              font_scale=1.5, 
              rc={'figure.figsize': (16, 6), 
                  'grid.linestyle': '--'}) # ここらへんのoptionは各人の好みで


AAPL.plot(y='Adj Close')

また、plotly という library を利用すると interactive なグラフを作ってグリグリと動かしながらデータを分析できます。
import plotly
import plotly.express as px
import plotly.io as pio
import plotly.graph_objects as go
 
pio.templates.default = 'plotly_white+presentation' # これも default だと文字が小さいので修正

fig = px.line(data_frame=AAPL.reset_index(), x='Date', y='Adj Close')
fig.update_layout(title='AAPL')
fig.show()

上の例ではAppleの株価を取得しましたが、他にも様々なデータが取得できます。例えば、Goldman Sachsの株価とか(これをあとで使います)
GS = pdr.DataReader('GS', 
                      data_source='yahoo',
                      start=datetime(2010, 4,1),
                      end=datetime(2021, 3, 31))
GS.plot(y='Adj Close', title='GS')

株価だけでなく、ドル円の為替レート
USDJPY = pdr.DataReader('USDJPY=X', data_source='yahoo', start=datetime(2010, 4, 1), end=datetime(2021, 3, 31))
USDJPY.plot(y='Adj Close', title='USDJPY')

30年ぶりに3万円台まで回復した日経225とか
NIKKEI225 = pdr.DataReader('NIKKEI225', data_source='fred', start=datetime(1990, 4, 1), end=datetime(2021,3,31))
ax = NIKKEI225.plot()
ax.axhline(30000, color='C2')

などなど、多様なチャートのデータを手軽に入手できます。

 

取引戦略を実装してみる

さて、データを取得できたので、ルールを決めて取引をして、そのパフォーマンスを調べてみましょう(最初は簡単のため取引コストは無視します)。ここでは長期移動平均(SMA_L)と短期移動平均(SMA_S)を比較し、長期SMA < 短期SMAならば、上昇トレンドとして買いポジション(long)、逆に長期SMA > 短期SMAなら下降トレンドとして売りポジション(short)を取ります。

以下のコードは、SMA_SとSMA_Lの大小を比較して position を +1 or -1 を選択し、その strategy に応じて return を得ます。なお、投資戦略に未来のデータが混ざらないように注意しましょう。(以下のコードでは対数収益率 \begin{align*} r_t = \ln [ p_{t+1}/p_t ] \end{align*} を元に収益を評価します。これは1ステップ後の時刻での価値の増加率に対応し、strategy として position を short にとっているならば、マイナス1を掛けているので元の資産が減少=資産の増加になります。)
def SMA_trade(data, sym, SMA_S, SMA_L):
    df = (
        data['Adj Close'].to_frame().copy().rename(columns={'Adj Close': sym})
    )
    df['SMA_S'] = df[sym].rolling(SMA_S).mean()
    df['SMA_L'] = df[sym].rolling(SMA_L).mean()
    df.dropna(inplace=True)
    df['position'] = np.where(df['SMA_S'] > df['SMA_L'], +1, -1)
    df['returns'] = np.log(df[sym]/df[sym].shift(1))
    df['strategy'] = df['position'].shift(1) * df['returns']
 
    return df
さて、実際に試してみましょう。
sym = 'GS'
start, end = '2016-04-01', '2019-03-31'
sma_s, sma_l = 20, 80
df = SMA_trade(data=eval(sym)[start:end], sym=sym, SMA_S=sma_s, SMA_L=sma_l)
df[[sym, 'SMA_S', 'SMA_L', 'position']].plot(secondary_y=['position'])
Goldman Sachs demo trade 1
するとこのように Goldman Sachs 株価のトレンドに応じて自動的にposition を long (+1) か short (-1) に取り直す戦略で取引がなされました。この取引戦略による収益の時系列は以下のようにlong し続けた場合(緑の線)とこの戦略を取った場合(オレンジの線)の対数収益率の累積として計算できます。2018年末の株価が下がる際にタイミングよく position を取り替えたために収益が得られました。
df[['returns','strategy']].cumsum().apply(np.exp).plot(title='GS')
np.exp(df[['returns','strategy']].sum())
# returns     1.241492
# strategy    1.825306
具体的な数値は上で計算できます。単純に株を保持していた場合の1.24と比べて、この移動平均による position 管理により、1.83とより多くの収益が得られました。

長期・短期の移動平均の組み合わせ毎のパフォーマンスを比較して、より良い設定を探索してみます。
sym = 'GS'
start, end = '2016-04-01', '2019-03-31'
GS_test = eval(sym)[start:end]
results = []
for sma_s in np.arange(5, 26, 5):
    for sma_l in np.arange(50, 121, 10):
        df = SMA_trade(data=GS_test, sym=sym, SMA_S=sma_s, SMA_L=sma_l)
        perf = np.exp(df[['returns','strategy']].sum())
        results.append({'sma_s': sma_s,
                        'sma_l': sma_l,
                        'market': perf['returns'],
                        'strategy': perf['strategy'],
                        'out': perf['strategy'] - perf['returns']
                        })
results = pd.DataFrame(results)
results.sort_values('out', ascending=False).head(5)

表だけではパフォーマンス(strategy – market の差)が把握しにくいので、以下のようにheatmapで見てみるのも良いですね。
sns.heatmap(results.pivot(index='sma_s', columns='sma_l', values='out'),
            annot=True, fmt='+.2f', center=0, cmap='bwr_r')

長期としては80日前後、短期としては20~25日がパフォーマンスが良さそうです。この組み合わせでは短期25日, 長期80日がベストです。


しかし、この結果は要注意です。今回の訓練データの範囲で最適化されており、別の期間での検証して性能評価をするべきです。実際にこの戦略を他の期間で実施してみると以下のようにそれほど高い性能が出ていない事がわかります。


 

バックテストツールを使ってみる

以上は実際に、python でゼロ(?)からアルゴリズム・トレードのデモをしましたが、すでに様々な library や専門のツールが公開されていますので、それを使って実践してみたいと思います。
既に別の blog の記事で高機能な vnpy というツールが紹介されていますが、ここでは、backtesting (https://kernc.github.io/backtesting.py/) を紹介します。(Google Colaboratoryなどでは、!pip install backtesting でinstallできます)あまり複雑な機能はありませんが、シンプルで判りやすく、結果の可視化が便利です。

まず、先ほど紹介した2つの移動平均線を元に取引する戦略を実装します。これは Strategy class を用いて先ほどの取引ルール
長期・短期移動平均がクロスする際に position を close し、long または short の position を取り直す
は以下のコードできます。
from backtesting import Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
 
class SmaCross(Strategy):
    n1 = 15
    n2 = 60
 
    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):
            self.position.close()
            self.buy()
 
        elif crossover(self.sma2, self.sma1):
            self.position.close()
            self.sell()

投資戦略を実装したら、データを読み込み以下のようにバックテストを実施します。

from backtesting import Backtest
 
start, end = '2016-04-01', '2019-03-31'
data = GS[start:end]
bt = Backtest(data, SmaCross, cash=10_000)
stats = bt.run()

bt.plot()
ここで結果のレポートが詳細に表示され、収益やトレードの勝率など結果について様々な情報がわかります。


また、トレードの状況についても以下の様に表示され大変わかりやすいです。上から Equity、Profit & Loss、株価と各種指標、そして取引量の時系列がプロットされています。これらのグラフは一部を選択したり拡大したりできとても便利です。


 

さて、移動平均の期間などのパラメータの最適化も、以下のように探索範囲や条件、何を最大化(今回は Equity Final)するなど指定し投資戦略を探すことができます。
stats = bt.optimize(n1=range(5, 26, 5), n2=range(10, 121, 10),
                    maximize='Equity Final [$]',
                    constraint=lambda param: param.n1 < param.n2)
 
print(stats._strategy) # SmaCross(n1=25,n2=70)

さいごに

今回は、まず導入編として自動取引の考え方を簡単なpythonによる実装から紹介しました。機械学習やAIというと一見難しそうですが、ステップごとにやってみると流れがよく判ると思います。次回はより本格的に機械学習を使った収益改善に取り組む予定です。

次世代システム研究室では、ビッグデータ解析プラットホームの設計・開発を行うアーキテクトとデータサイエンティストを募集しています。興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧からご応募をお願いします。

一緒に勉強しながら楽しく働きたい方のご応募をお待ちしております。

参考資料

  • Yves Hilpisch(著)、黒川利明(訳)、中妻照雄(技術監修), “Python によるファイナンス”
  • Backtesting https://pypi.org/project/Backtesting/
  • マルコス・ロペス・デ・プラド (著), 長尾 慎太郎 (監修, 翻訳), “ファイナンス機械学習”
  • マルコス・ロペス・デ・プラド (著), 鹿子木 亨紀 (翻訳), “アセットマネージャーのためのファイナンス機械学習”

Pocket

関連記事