AIとファイナンス

AIとファイナンスの架け橋、それがこのブログの目指すところです。兼業投資家向けに、Pythonを駆使して株やFXの分析を「自分で」行えるようになるための情報を提供します。ニューラルネットワークを活用した市場予測から、実証済みの金融理論まで、全てのコードを公開し、誰もが活用できるように!是非色々なコードで遊んでみてください!

強化学習を通じて学ぶ日経平均株価予測:データ収集から結果分析まで 【その4】

はじめに

前回の記事では、DQNを用いた株価予測モデルの設計と開発について説明しました。今回は、強化学習におけるシミュレーション環境の設計と、トレーディングパフォーマンスの評価に使用するメトリクスについて詳しく見ていきます。

TradingEnv クラスの設計

このプロジェクトでは、TradingEnv というカスタム環境を設計しました。この環境は、強化学習エージェントが株価予測の決定を行うためのシミュレーション環境を提供します。

クラスの構造

  • 初期化 (__init__): 日経平均株価、S&P500、Dowのデータセットと初期資金額を設定します。初期金額を1万円に設定してしまっていますがご存知の通り日経225は2023年現在1万円では買えないので実はあまり意味がありません。
  •     def __init__(self, nikkei_data, sp500_data, dow_data, initial_balance=10000):
            self.nikkei_data = nikkei_data
            self.sp500_data = sp500_data
            self.dow_data = dow_data
            self.initial_balance = initial_balance
            self.reset()
  • リセット (reset): トレーディングセッションの開始時に呼び出され、エージェントの状態を初期化します。
  •     def reset(self):
            self.balance = self.initial_balance
            self.current_step = 0
            self.total_trades = 0
            self.win_trades = 0
            self.lose_trades = 0
            return self._get_state()
  • 状態の取得 (_get_state): 現在の市場状態を表すデータを提供します。今回過去の市況を見るように設定している(lookback_days)ので初日は過去のデータを提供できないため何回もエラーが出てしまい苦労しましたが、3行目のようなものを加えることで回避できました。
  •     def _get_state(self, lookback_days=3):
            if self.current_step < lookback_days:
                return np.zeros(lookback_days * 8)  # 十分なデータがない場合はゼロを返す

            state = []
            for offset in range(lookback_days):
                prev_day = self.current_step - offset - 1
                dow_values = self.dow_data.iloc[prev_day][['Open', 'High', 'Low', 'Close']].values
                sp500_values = self.sp500_data.iloc[prev_day][['Open', 'High', 'Low', 'Close']].values
                state.extend(dow_values)
                state.extend(sp500_values)

            return np.array(state)
  • ステップ (step): エージェントの行動に基づいて市場状態を更新し、報酬を計算します。強化学習の場合はこの報酬設計がかなり曲者です。単純に勝った金額を報酬にしてしまうと常に「何もしない」を選択してしまうエージェントになってしまいます。今回はこの報酬設計を気軽に変えられるように「calculate_rewardメソッド」を別で作成しています。これについては次回の記事で説明しますね!
  •     def step(self, action):
            # 現在の市場データを取得
            current_data = self.nikkei_data.iloc[self.current_step]
            previous_close = self.nikkei_data.iloc[self.current_step - 1]['Close'] if self.current_step > 0 else current_data['Open']

            # 報酬の計算
            reward = calculate_reward(current_data, previous_close, action)

            # 次の状態に更新
            self.current_step += 1
            done = self.current_step >= len(self.nikkei_data)
            next_state = self._get_state()

            return next_state, reward, done

メトリクスの計算

TradingEnv クラスでは、トレーディングセッションのパフォーマンスを評価するために複数のメトリクスを計算します。

メトリクスの一覧と計算方法

  • メトリクス 説明 コードでの計算
    買い取引数 エージェントが行った買い取引の総数 buy_trades = self.total_trades - self.lose_trades
    売り取引数 エージェントが行った売り取引の総数 sell_trades = self.lose_trades
    勝率 取引における勝利の割合 win_rate = self.win_trades / self.total_trades if self.total_trades > 0 else 0
    リスクリワード比 勝利取引と敗北取引の比率 risk_reward_ratio = self.win_trades / self.lose_trades if self.lose_trades > 0 else 0
    トータルリターン トレーディングセッションにおける利益の総額 total_return = self.balance - self.initial_balance

これらのメトリクスは、エージェントのトレーディング戦略の効果を定量的に評価するために重要です。このような短期取引の場合様々なものが検討できますが今回は特にリスクリワード比と勝率、トータルリターンの三つを重視しようと思います。

シミュレーション環境の重要性

強化学習において、シミュレーション環境はエージェントが学習するための「プレイグラウンド」を提供します。TradingEnv クラスは、実際の市場データに基づいてリアルなトレーディング環境を模倣し、エージェントが株価予測の意思決定を行うための様々なシナリオを提供します。このような環境を作ることで「報酬設計」で書きましたように色々とPlay aroundすることができるようになります。

まとめ

TradingEnv クラスの設計とメトリクスの計算は、強化学習を用いた株価予測システムにおいて、エージェントが実際の市場状況を模倣した環境で学習し、そのパフォーマンスを定量的に評価するための重要な手段です。次回の記事では、報酬設計について桑h仕組みていきます。

今回のコードは以下の通りです。皆さま是非参考にしてみてください。

class TradingEnv:
    def __init__(self, nikkei_data, sp500_data, dow_data, initial_balance=10000):
        self.nikkei_data = nikkei_data
        self.sp500_data = sp500_data
        self.dow_data = dow_data
        self.initial_balance = initial_balance
        self.reset()

    def reset(self):
        self.balance = self.initial_balance
        self.current_step = 0
        self.total_trades = 0
        self.win_trades = 0
        self.lose_trades = 0
        return self._get_state()

    def _get_state(self, lookback_days=3):
        if self.current_step < lookback_days:
            return np.zeros(lookback_days * 8)  # 十分なデータがない場合はゼロを返す

        state = []
        for offset in range(lookback_days):
            prev_day = self.current_step - offset - 1
            dow_values = self.dow_data.iloc[prev_day][['Open', 'High', 'Low', 'Close']].values
            sp500_values = self.sp500_data.iloc[prev_day][['Open', 'High', 'Low', 'Close']].values
            state.extend(dow_values)
            state.extend(sp500_values)

        return np.array(state)

    def step(self, action):
        # 現在の市場データを取得
        current_data = self.nikkei_data.iloc[self.current_step]
        previous_close = self.nikkei_data.iloc[self.current_step - 1]['Close'] if self.current_step > 0 else current_data['Open']

        # 報酬の計算
        reward = calculate_reward(current_data, previous_close, action)

        # 次の状態に更新
        self.current_step += 1
        done = self.current_step >= len(self.nikkei_data)
        next_state = self._get_state()

        return next_state, reward, done

    def get_metrics(self):
        # メトリクスの計算
        buy_trades = self.total_trades - self.lose_trades
        sell_trades = self.lose_trades
        win_rate = self.win_trades / self.total_trades if self.total_trades > 0 else 0
        risk_reward_ratio = self.win_trades / self.lose_trades if self.lose_trades > 0 else 0
        total_return = self.balance - self.initial_balance
        return buy_trades, sell_trades, win_rate, risk_reward_ratio, total_return