見出し画像

Perplexity vs Claude vs Copilot比較 - FXトレード戦略のバックテスト・最適化

こんにちは。
AIの進化についていくのが大変ですが、より便利なものがあればやはり活用しない手はないかと思います。
こちらの記事にもしましたが、現時点ではプログラム作成などはClaudeを優先的に利用しています。
なお、AIはすべて無料版のみの利用です。

しかし、Perplexity AIというものが出てきて、Claudeよりもベターなプログラムを作成してくれるという記事がありました。
早速、検討したいと思っていたトレード戦略のバックテスト、最適化のスクリプトの作成に関し、ClaudeおよびCopilotを比較したので、その内容・結果を共有いたします。
なお、この記事ではプログラムの作成まで検討し、プログラムの実行・検証については続編として記事にしたいと思います。


FXトレード戦略

今回検討するFXトレード戦略は、一言でいうと高い勝率で『小利大損』を乗り切るというものです。
株式投資などでは、『損小利大』がいいとよく言われていますが、全く逆の戦略です。
小利大損であっても、大損が小利合計より小さくできればいいわけですが、高い勝率を維持することができるのかが重要です。
この高い勝率を担保すために、ヒストリカルデータを活用して十分勝てそうなトレードルールを見極めるというのが今回やろうとしていることです。
なお、勝率を上げるためにはより安定的に小利を取る必要があり、以下の方針に合うものを対象にします。

  • 市場参加者・取引量が多く、流動性が高い

  • 価格変動(ボラティリティ)が低め

FXでこの条件に合いそうな通貨ペアとして、USD / EUR / JPYがあり、ベストな組み合わせとして、過去3年程度の変動率から EURUSDを選びました。
なお、FXの変動率についてはマネーパートナーズのこちらのサイトが非常に分かりやすいです。

3通貨のすべての組み合わせで比較してみると、下図のとおり青線のEURUSDの変動が少なめです。

加えて、EURUSDが世界で最も多く取引されている通貨ペアであるため今回の戦略の検討では最も適していると考えます。

トレードルールは、単純にボラティリティが低下する時間帯を選び、より安定した高い勝率が出るタイミングでエントリーし、利確・損切り指定で決済されなかった場合でも当日手仕舞うというものです。

時間帯によるボラティリティの傾向については、こちらの記事のやった方法にて確認いたしました。

また、よりざっくりとした傾向については、こちらのサイトでも分かりやすく確認できます。
なお、こちらは日本時間ベースなので米国夏時間などの考慮はされていないと思われます。


OANDA証券提供の ボラティリティ確認ツール


AIに依頼する内容

Perplexity、Claude、Copilotにプログラム作成を依頼しますが、依頼する内容をある程度整理し、作成されたプログラムに手を加えずそのまま実行できるかという観点でも検証したいと思います。
そのため、依頼内容は細かい点も含めて記載しました。
以下のとおりです。

10年分程度の OHLC 1分足データがあります。
このデータを利用して、トレードルールをバックテストおよび最適なパラメータを見つけるPythonプログラムを作成して

トレードルール:
1. 指定時間で 1ユニット購入する。
2. 購入時は成行で、同時に指値(Take profit) と逆指値(Stop loss)を指定する。
3. 購入後の一定時間後にポジションが残る場合には、決められた時間で手じまい、売却する。

最適なパラメータの条件:
1. 勝率は90%以上
2. 指値(Take profit)は0.0003(30mips)で固定
3. リスク・リワード比率(Take Profit/Stop Loss)は、(1.0005/勝率 - 1)以上の値とする
上記条件で勝率が最も高くなるパラメータ、エントリー時間、エクジット時間を見つける。
なお、より効率的に最適パラメータを探すため、ランダムサーチにより最適なパラメータ候補をサンプリングし、並列処理を活用したベイズ最適化を行ってください。

利用するOHLC1分足データ:
1. 実行スクリプトがあるフォルダに含まれる "download_file"フォルダに存在
2. ファイル名 "EURUSD_201301.zip"といった名前で存在し、201301部分は対象のYYYYMM、年月
3. download_fileに含まれるすべてのデータを取り込み分析対象とする
4. zipファイルの中身は YYYYMM形式のフォルダがありその中に1分ごとのデータが"EURUSD_20230201.csv" といった名前で保存されている。
5. zipファイルの中に、"202302_EX"といった _EXがついたフォルダも含まれる場合があるがこれは利用しない
6. CSVデータは二つのパターンがあり、どちらの場合も先頭の5列をOHCLデータをとして利用する
パターン1:
日時,始値,高値,安値,終値
20130102070000,1.32033,1.32058,1.32031,1.32058
パターン2:
日時,始値(BID),高値(BID),安値(BID),終値(BID),始値(ASK),高値(ASK),安値(ASK),終値(ASK)
2024/10/01 06:00:00,1.11332,1.11333,1.11285,1.11286,1.1137,1.11371,1.11368,1.11368
7.最初の日時の2種類のフォーマットに対応し、かつ、New York時間に変換すること

AIからの回答と生成されたプログラム

まず、AIとチェットする場合には、UIの使い勝手や見易さも大事なので確認したいと思います。

【Perplexity】

作成されたプログラムについて説明

【Copilot】

作成されたプログラムについて説明

【Claude】

作成されたプログラムについて説明含む


コード見た目・操作感の比較

という結果になり、Claudeが総合的に最良でした。

  • コード表示の見易さ
    Claudeはチャットが左側、コードが右側に並列で表示され非常に分かりやすいです。補足説明を確認しながら、コードのチェックができます。
    また、配色もClaudeが一番落ち着いた色で個人的には好きです。

  • コピー機能
    作成プログラムはクリップボード経由でコピーして頻繁に利用しますが、こちらの大きな違いはありませんが、Claudeのみコードの最下部に配置されており一番使いやすいです。PerplexityとCopilotは上部にあるためスクロールが必要になり面倒です。

  • コードの説明
    作成されたプログラムはしっかり内容・前提など理解する必要がありますが、その説明があるか、分かりやすいかという点です。
    これはClaudeが最良でした。
    Copilotは短い説明文がありますが、ヘイズ最適化をお願いしたのに実装されず、補足説明もありません。
    Perplexityは、こちらが指示した内容についてきっちり実装し、その内容を整理してありますが、それ以上のことはありませんでした。
    Claudeについて下(↓)に記載しました。

  • 新規会話
    無料アカウントでの上限、また、過去のコンテキストと関係ない話題に移る場合に新規に会話・スレッドを始める必要があります。
    Perplexity、Claudeは問題ないですが、Copilotは最近インターフェースが変わり、新規会話を開始するボタンがなくなりました。
    過去の履歴を考慮せずに会話する場合には、その旨AIに伝える必要があるようです。これは結構面倒であり、大きな減点ポイントです。

Claudeのコード生成の補足説明

こちらがClaudeからのコードの補足説明です。
私が優れていると思った点を太字で強調しました。

・処理内容をカテゴリ分けして説明している
・あった方がいい機能(詳細な取引履歴の保持)も追加している
・パラメータの範囲前提について明記
・追加機能の提案。可視化機能など実用的な提案ばかり。

追加機能・改善の提案をしてくるところが他のAIにはないところです。
かゆいところに手が届く感じの対応で高く評価できます。

このプログラムは以下の主要な機能を持っています:

1. データ処理
- 複数のzipファイルから1分足OHLCデータを読み込み
- 2つの異なる日時フォーマットに対応
- New York時間への変換機能

2. バックテスト機能
- 指定された時間でのエントリー
- Take profit, Stop loss, タイムアウトによる決済
- 詳細な取引記録の保持

3. パラメータ最適化
- ランダムサーチによる初期サンプリング
- 並列処理を活用したベイズ最適化
- 指定された制約条件(勝率90%以上、リスク・リワード比率)の考慮

4. 最適化対象のパラメータ
- エントリー時間(24時間×12(5分間隔)
- 決済タイムアウト(10分〜4時間
- ストップロス(10-50pips
- テイクプロフィット(固定:30pips)

改善や追加機能が必要な場合は、お申し付けください。例えば:
- より詳細な取引統計の追加
- 異なる最適化アルゴリズムの実装
- 可視化機能の追加

などが考えられます。

Claudeからの生成コードの説明

生成されたコードの品質の比較

生成されたコード自体の品質については、Perplexityが最良でした。
ただし、Claudeも僅差で負けていないと思います。

  • 要件およびプラスαの対応

Claudeがひとつとび抜けています。要件の対応は適切で、かつ、あった方がいい機能も実装に含まていました。
Copilotは Backtesting.pyライブラリを活用したためか、ベイズ最適化の実装が抜けていました。

  • コードの読みやすさ・シンプルさ

ここが一番肝心なところではありますが、Perplexityが最良23行程度でした。
ヘイズ最適化の適切なライブラリを利用し、また、データを日単位でグルーピンクすることで、トレードルール部分の実装が非常にシンプル、分かりやすくなっています。
ただし、Claudeの方が利確・損切の処理タイミングを前後を考慮して処理しより正しい実装になっている。
Copilotはコードだけでは一番シンプルですが、肝心のベイズ最適化がないためここの得点も少し下げています。

  • パフォーマンス対応

ベイズ最適化を利用している理由は最適化の処理時間を短縮することですが、その観点ではPerplexityは最適なライブラリを活用し、最良の実装になっています。

【Perplexity】トレードルール実装コード

# バックテスト関数
def backtest(df, entry_time, exit_time, take_profit, stop_loss):
    trades = []
    for date, group in df.groupby(df['datetime'].dt.date):
        entry_point = group[group['datetime'].dt.time == entry_time]
        if entry_point.empty:
            continue
        
        entry_price = entry_point['open'].values[0]
        exit_point = group[group['datetime'].dt.time > exit_time].iloc[0]
        
        high_after_entry = group[group['datetime'] >= entry_point['datetime'].values[0]]['high'].max()
        low_after_entry = group[group['datetime'] >= entry_point['datetime'].values[0]]['low'].min()
        
        if high_after_entry >= entry_price + take_profit:
            result = 'win'
            profit = take_profit
        elif low_after_entry <= entry_price - stop_loss:
            result = 'loss'
            profit = -stop_loss
        else:
            result = 'timeout'
            profit = exit_point['close'].values[0] - entry_price
        
        trades.append({'date': date, 'result': result, 'profit': profit})
    
    return pd.DataFrame(trades)

【Claude】トレードルール実装コード

class FXBacktester:
    def __init__(self, data: pd.DataFrame):
        self.data = data
        
    def simulate_trade(self, 
                      entry_time: str,
                      exit_timeout: int,
                      take_profit: float = 0.0003,
                      stop_loss: float,
                      risk_reward_ratio: float) -> Tuple[float, List[Dict]]:
        
        trades = []
        entry_hour, entry_minute = map(int, entry_time.split(':'))
        
        # 各取引日についてループ
        current_date = None
        position = None
        
        for idx, row in self.data.iterrows():
            dt = row['datetime']
            
            # 新しい日の開始
            if current_date != dt.date():
                current_date = dt.date()
                position = None
            
            # エントリー時間でポジションがない場合、新規購入
            if dt.hour == entry_hour and dt.minute == entry_minute and position is None:
                position = {
                    'entry_time': dt,
                    'entry_price': row['close'],
                    'take_profit': row['close'] + take_profit,
                    'stop_loss': row['close'] - stop_loss
                }
            
            # ポジションがある場合、決済条件をチェック
            elif position is not None:
                # 利確、損切り、タイムアウトのチェック
                if row['high'] >= position['take_profit']:
                    trades.append({
                        'entry_time': position['entry_time'],
                        'exit_time': dt,
                        'entry_price': position['entry_price'],
                        'exit_price': position['take_profit'],
                        'profit': take_profit,
                        'result': 'win'
                    })
                    position = None
                    
                elif row['low'] <= position['stop_loss']:
                    trades.append({
                        'entry_time': position['entry_time'],
                        'exit_time': dt,
                        'entry_price': position['entry_price'],
                        'exit_price': position['stop_loss'],
                        'profit': -stop_loss,
                        'result': 'loss'
                    })
                    position = None
                    
                elif (dt - position['entry_time']).total_seconds() / 60 >= exit_timeout:
                    trades.append({
                        'entry_time': position['entry_time'],
                        'exit_time': dt,
                        'entry_price': position['entry_price'],
                        'exit_price': row['close'],
                        'profit': row['close'] - position['entry_price'],
                        'result': 'timeout'
                    })
                    position = None
        
        # 勝率を計算
        if not trades:
            return 0, []
        
        win_rate = sum(1 for t in trades if t['result'] == 'win') / len(trades)
        return win_rate, trades

【Copilot】トレードルール実装コード

class MyStrategy(Strategy):
    entry_time = None
    close_time = None
    stop_loss = RISK_REWARD_RATIO * TAKE_PROFIT

    def init(self):
        pass

    def next(self):
        if self.data.index[-1].hour == self.entry_time.hour and self.data.index[-1].minute == self.entry_time.minute:
            self.buy(tp=self.data.Close[-1] * (1 + TAKE_PROFIT), sl=self.data.Close[-1] * (1 - self.stop_loss))
        if len(self.trades) > 0 and self.data.index[-1] - self.trades[-1].entry_time >= pd.Timedelta(minutes=self.close_time):
            self.position.close()


総合評価として、現時点では一つのAIがすべての点で最良であるわけではなく、それぞれいい点、悪い点が混在する形とうことになります。
Perplexityで作成したコードをClaudeに説明・改善してもらう、または、その逆にClaudeの作成したコードをPerplexityで改善するといったことをするのがいいのかもしれません。
今回作成したコードを実際にテスト、評価する部分について続編でご紹介したいと思います。

実際に作成されたコードはこちらです。

最後に

この記事はお気に入りいただけましたでしょうか?
内容お役にたちましたらうれしく思います。
また、サポートなど応援いただけましたら幸いです

いいなと思ったら応援しよう!

なべなべ
記事の内容を有効に活用できた、記事を気に入っていただけたようでしたらチップでサポートいただけますと嬉しいです。 また、こんなことを知りたい、あんなことができないかなど記事にしたいことがございましたら、サポートの有無にかかわらずお知らせくださいませ。