2024.04.01

ChatGPTにトレーディング戦略を実装させてみる

はじめに

グループ研究開発本部・AI研究開発室のS.Sです。
最近ではLLMというと、Stable Diffusion 3やOpenAI Soraといったマルチモーダル方面での発展がめざましいですが、多くのモデルはオープンになっておらずいじってみるということもできないので、引き続きGPT3で金融のタスクを解かせてみるという検証に取り組みたいと思います。

GPTに実装させるストラテジ

S&P 500の銘柄ごとの過去12ヶ月のリターンでランキングを行い、上位をLong・下位をShortするというcross sectional momentumのストラテジをChatGPTにコーディングさせてみます。
このストラテジのイメージを画像に示します。

Take1: 素朴なLong/Short

まずは細かいことは考えずに、過去12ヶ月のリターンを見て、上位50%をLong・下位50%をShortするストラテジをChatGPTに組ませてみます。
以下にプロンプトを示します。

(中略)

# ポートフォリオのリターンを格納するリスト
portfolio_returns = []

# 各月のポートフォリオのリターンを計算
for i in range(len(returns_data)):
    if i < 11:  # 最初の11ヶ月は過去12ヶ月のデータが揃わないのでスキップ
        continue
    # 過去12ヶ月のデータを取得
    past_12_months_returns = returns_data.iloc[i-11:i+1]
    # 各銘柄の過去12ヶ月のリターンの平均を計算し、モーメンタムとする
    momentum = past_12_months_returns.mean()
    # モーメンタムが大きい銘柄をロングポジション(+1)、小さい銘柄をショートポジション(-1)とする
    long_stocks = momentum[momentum >= momentum.median()].index
    short_stocks = momentum[momentum < momentum.median()].index
    # ポートフォリオのリターンを計算し、リストに追加
    portfolio_return = returns_data.iloc[i+1][long_stocks].mean() - returns_data.iloc[i+1][short_stocks].mean()
    portfolio_returns.append(portfolio_return)

# 結果をDataFrameに変換して表示
result_df = pd.DataFrame({
    'Date': returns_data.index[11:],  # 開始日は最初の11ヶ月をスキップしているのでそれに対応
    'Portfolio_Return': portfolio_returns
})
print(result_df)

ほぼ動きそうなコードが回答として返ってきました。
微妙な言い回しの違いにより、末尾の1ヶ月のリターンだけ返すコードになったり、各月毎のリターンを正しく計算してくれたりと回答にブレがあるので、何度か試行錯誤をする必要がありました。
またデータフレームについてもある程度明示的にイメージを説明しないと、うまく実行できないコードが出力される場合があります。

インプットデータとして2001~2017の月次リターンデータを別途用意して上記のコードに与えてみると、ループの回数が1回ずれてしまい、エラーとなります。
ここは以下のように、手で修正を加えると想定通りに動かすことができました。

# ポートフォリオのリターンを格納するリスト
portfolio_returns = []

# 各月のポートフォリオのリターンを計算
for i in range(len(returns_data)-1):
    if i < 11:  # 最初の11ヶ月は過去12ヶ月のデータが揃わないのでスキップ
        continue
    # 過去12ヶ月のデータを取得
    past_12_months_returns = returns_data.iloc[i-11:i+1]
    # 各銘柄の過去12ヶ月のリターンの平均を計算し、モーメンタムとする
    momentum = past_12_months_returns.mean()
    # モーメンタムが大きい銘柄をロングポジション(+1)、小さい銘柄をショートポジション(-1)とする
    long_stocks = momentum[momentum >= momentum.median()].index
    short_stocks = momentum[momentum < momentum.median()].index
    # ポートフォリオのリターンを計算し、リストに追加
    portfolio_return = returns_data.iloc[i+1][long_stocks].mean() - returns_data.iloc[i+1][short_stocks].mean()
    portfolio_returns.append(portfolio_return)

# 結果をDataFrameに変換して表示
result_df = pd.DataFrame({
    'Date': returns_data.index[12:],  # 開始日は最初の11ヶ月をスキップしているのでそれに対応
    'Portfolio_Return': portfolio_returns
})
print(result_df)

得られたリターンをストラテジのパフォーマンスを可視化するquantstatsに食わせてみると、以下のような出力を得ることができました。
特に注目すべき指標はCAGR(複利の年率), Sharpe Ratio(投資の効率性指標), Max Drawdown(ピークからの運用資産の最大下落幅)となります。
素朴な実装だと2008~2009にかけて大きなマイナスリターンを記録しており、直視しにくい結果となっています。
そこで次のセクションではストラテジにいくつか調整を加えていくことにします。

Take2: longバイアスの導入

株価は他の資産と比べると一定ペースで増えていく傾向にあり、よほどの情報を持たない限りはlongポジションを持った方がよいとされています。
longより少し少ない割合でshortポジションも持つことにより、株価が大きく下落するような局面でのマーケットのリスクにも備えることができます。
ここではLong:Short=2:1でポジションを持つようにしてみます。

出力されたコードを以下に示します。

import pandas as pd

# サンプルのインプットデータ(この部分は実際のデータに置き換えてください)
returns_data = pd.DataFrame({
    'AAPL': [0.05, 0.03, -0.02, 0.04, 0.06, -0.01, 0.02, 0.03, -0.05, 0.01, 0.04, -0.02],
    'GOOG': [0.04, 0.02, 0.03, 0.01, 0.05, 0.02, -0.01, 0.03, 0.02, -0.03, 0.01, 0.02],
    'MSFT': [0.03, 0.01, 0.02, 0.03, -0.01, 0.04, 0.05, -0.02, 0.01, 0.02, 0.03, 0.02],
    'AMZN': [0.06, -0.01, 0.02, 0.03, 0.04, 0.05, 0.02, -0.03, 0.01, 0.02, 0.03, 0.01]
}, index=pd.date_range(start='2022-01-01', periods=12, freq='M'))

# ポートフォリオのリターンを格納するリスト
portfolio_returns = []

# 各月のポートフォリオのリターンを計算
for i in range(len(returns_data)):
    if i < 11:  # 最初の11ヶ月は過去12ヶ月のデータが揃わないのでスキップ
        continue
    # 過去12ヶ月のデータを取得
    past_12_months_returns = returns_data.iloc[i-11:i+1]
    # 各銘柄の過去12ヶ月のリターンの平均を計算し、モーメンタムとする
    momentum = past_12_months_returns.mean()
    # モーメンタムが大きい銘柄をロングポジション(+1)、小さい銘柄をショートポジション(-1)とする
    long_stocks = momentum[momentum >= momentum.median()].index
    short_stocks = momentum[momentum < momentum.median()].index
    # ポートフォリオのリターンを計算し、リストに追加
    long_return = returns_data.iloc[i+1][long_stocks].mean()
    short_return = returns_data.iloc[i+1][short_stocks].mean()
    portfolio_return = (2 * long_return) - short_return  # longとshortの重みを2と-1にする
    portfolio_returns.append(portfolio_return)

# 結果をDataFrameに変換して表示
result_df = pd.DataFrame({
    'Date': returns_data.index[11:],  # 開始日は最初の11ヶ月をスキップしているのでそれに対応
    'Portfolio_Return': portfolio_returns
})
print(result_df)

 

同じくquantstatsでパフォーマンスを評価してみると、今度はまずまずの結果になりました。
結果をよくみてみるとMax Drawdownに関しては、まだ大きすぎる水準です。

 

Take3: 上位下位10%の銘柄だけをLong/Short

さらにパフォーマンスを改善するために上位下位10%の銘柄だけをLong/Shortするようにしてみます。

出力されたコードを以下に示します。

import pandas as pd

# サンプルのインプットデータ(この部分は実際のデータに置き換えてください)
returns_data = pd.DataFrame({
    'AAPL': [0.05, 0.03, -0.02, 0.04, 0.06, -0.01, 0.02, 0.03, -0.05, 0.01, 0.04, -0.02],
    'GOOG': [0.04, 0.02, 0.03, 0.01, 0.05, 0.02, -0.01, 0.03, 0.02, -0.03, 0.01, 0.02],
    'MSFT': [0.03, 0.01, 0.02, 0.03, -0.01, 0.04, 0.05, -0.02, 0.01, 0.02, 0.03, 0.02],
    'AMZN': [0.06, -0.01, 0.02, 0.03, 0.04, 0.05, 0.02, -0.03, 0.01, 0.02, 0.03, 0.01]
}, index=pd.date_range(start='2022-01-01', periods=12, freq='M'))

# ポートフォリオのリターンを格納するリスト
portfolio_returns = []

# 各月のポートフォリオのリターンを計算
for i in range(len(returns_data)):
    if i < 11:  # 最初の11ヶ月は過去12ヶ月のデータが揃わないのでスキップ
        continue
    # 過去12ヶ月のデータを取得
    past_12_months_returns = returns_data.iloc[i-11:i+1]
    # 各銘柄の過去12ヶ月のリターンの平均を計算し、モーメンタムとする
    momentum = past_12_months_returns.mean()
    # モーメンタムの上位と下位の10%の銘柄を抽出
    long_short_threshold = int(0.1 * len(momentum))
    top_momentum_stocks = momentum.nlargest(long_short_threshold).index
    bottom_momentum_stocks = momentum.nsmallest(long_short_threshold).index
    # ポートフォリオのリターンを計算し、リストに追加
    long_return = returns_data.iloc[i+1][top_momentum_stocks].mean()
    short_return = returns_data.iloc[i+1][bottom_momentum_stocks].mean()
    portfolio_return = (2 * long_return - short_return)  # longとshortの重みを2:1にする
    portfolio_returns.append(portfolio_return)

# 結果をDataFrameに変換して表示
result_df = pd.DataFrame({
    'Date': returns_data.index[11:],  # 開始日は最初の11ヶ月をスキップしているのでそれに対応
    'Portfolio_Return': portfolio_returns
})
print(result_df)

Take3ではShortサイドの重みがプラスになってしまっていたので、手で修正を加えて実行してみましたがパフォーマンスは改善しませんでした。

 

まとめ

株のcross-sectional momentumに基づくlong/short戦略をChatGPTに実装させてみると、そのままでも動くコードをうまく出力してくれました。
さらにパフォーマンスを改善するために、いくつかの調整を加えるように指示するとちゃんと修正したコードが出てきました。
Github copilotのようなコーディング支援ツールも様々な企業で導入が進みつつありますが、そのベースとなっているChatGPTのコーディング能力はなかなかなので、日常のコーディングも捗ることでしょう。

最後に

グループ研究開発本部 AI研究開発室では、データサイエンティスト/機械学習エンジニアを募集しています。ビッグデータの解析業務などAI研究開発室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事