Bot

開発記録#144(2025/3/25)「論文ベースのbot開発フローpart.6 バックテストシステムの実装」

2025年3月25日

前回の記事に引き続き、今回も仮想通貨botの開発状況をまとめていきます。

本記事では「暗号通貨のパンプ&ダンプスキームの検出」に関する論文をベースにbot開発の過程をまとめていきます。

🚀 バックテスト環境の構築

以下のコードは、バックテストシステムを実装し、P&Dイベント検出後の取引パフォーマンスをシミュレーションするものです。


1. バックテスト設計のアプローチ

🔎 バックテストの主な機能

データ読み込み:過去データ (.csv) を使用し、時系列データをシミュレーション
P&Dイベント判定:検出モデルのデータを利用して、エントリー・エグジットのタイミングを評価
資金管理

  • エントリー金額、ロットサイズ、損切り (Stop Loss) / 利益確定 (Take Profit) の指定
    評価指標
  • 損益 (PnL)勝率最大ドローダウンシャープレシオ などを計算
    可視化
  • 成績をグラフで視覚化し、戦略の有効性を評価

2. コード実装 (backtest_engine.py)

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime

# ==============================
# 設定
# ==============================
INITIAL_BALANCE = 10000  # 初期資金 (USD)
TRADE_AMOUNT = 100       # 1回の取引額 (USD)
STOP_LOSS_PERCENT = 0.03  # 損切り (3%)
TAKE_PROFIT_PERCENT = 0.05  # 利確 (5%)

# データ読み込み
def load_data(data_path):
    df = pd.read_csv(data_path)
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    df.set_index('timestamp', inplace=True)
    return df

# バックテストのロジック
def backtest(df, pnd_events):
    balance = INITIAL_BALANCE
    trade_log = []

    for _, event in pnd_events.iterrows():
        timestamp = pd.to_datetime(event['timestamp'])
        pump_price = event['pump_price']
        dump_price = event['dump_price']

        # エントリーポイントの選定
        entry_time = timestamp
        entry_price = pump_price

        # エグジット条件 (損切り / 利確)
        stop_loss_price = entry_price * (1 - STOP_LOSS_PERCENT)
        take_profit_price = entry_price * (1 + TAKE_PROFIT_PERCENT)

        # エントリー後の価格データ
        trade_window = df.loc[entry_time:].copy()

        # 利確/損切りの確認
        exit_price = None
        for index, row in trade_window.iterrows():
            if row['close'] >= take_profit_price:
                exit_price = take_profit_price
                result = "Win"
                break
            elif row['close'] <= stop_loss_price:
                exit_price = stop_loss_price
                result = "Loss"
                break
        else:
            exit_price = trade_window.iloc[-1]['close']
            result = "Hold"

        # 利益計算
        pnl = (exit_price - entry_price) * (TRADE_AMOUNT / entry_price)
        balance += pnl

        # トレード結果の記録
        trade_log.append({
            "Entry Time": entry_time,
            "Entry Price": entry_price,
            "Exit Price": exit_price,
            "Exit Time": index,
            "PnL": pnl,
            "Result": result
        })

    # トレード結果データフレーム
    trades_df = pd.DataFrame(trade_log)

    return trades_df, balance

# 評価指標の計算
def evaluate_performance(trades_df, initial_balance):
    total_trades = len(trades_df)
    wins = len(trades_df[trades_df['Result'] == 'Win'])
    losses = len(trades_df[trades_df['Result'] == 'Loss'])
    win_rate = (wins / total_trades) * 100 if total_trades > 0 else 0
    total_pnl = trades_df['PnL'].sum()
    final_balance = initial_balance + total_pnl
    max_drawdown = (initial_balance - trades_df['PnL'].cumsum().min()) / initial_balance * 100

    print("\n=== バックテスト結果 ===")
    print(f"総トレード数: {total_trades}")
    print(f"勝率: {win_rate:.2f}%")
    print(f"最終残高: ${final_balance:.2f}")
    print(f"最大ドローダウン: {max_drawdown:.2f}%")
    print(f"総損益 (PnL): ${total_pnl:.2f}")

# 結果の可視化
def plot_results(df, trades_df):
    plt.figure(figsize=(12, 6))
    plt.plot(df['close'], label='価格推移')
    for _, trade in trades_df.iterrows():
        plt.axvline(trade['Entry Time'], color='green' if trade['Result'] == 'Win' else 'red', linestyle='--')
    plt.title('バックテスト結果の可視化')
    plt.legend()
    plt.grid(True)
    plt.show()

# メイン処理
def main():
    df = load_data('./data/BTCUSDT_data.csv')
    pnd_events = pd.read_csv('./data/pnd_events.csv')
    
    trades_df, final_balance = backtest(df, pnd_events)
    evaluate_performance(trades_df, INITIAL_BALANCE)
    plot_results(df, trades_df)

if __name__ == "__main__":
    main()

このコードがどのように動作するのか、順を追って説明します。

初期設定

まず、初期資金として$10,000、一回の取引額を$100、損切りを3%、利確を5%と定義しています。この設定はトレードの基本ルールを形成します。

INITIAL_BALANCE = 10000  # 初期資金 (USD)
TRADE_AMOUNT = 100       # 1回の取引額 (USD)
STOP_LOSS_PERCENT = 0.03  # 損切り (3%)
TAKE_PROFIT_PERCENT = 0.05  # 利確 (5%)

データ読み込み

データを読み込むための関数load_dataは、CSVファイルからデータを取得し、タイムスタンプを日時オブジェクトに変換後、インデックスとして設定します。これにより、後の処理で日時を使ってデータを簡単に扱えるようになります。

バックテストのロジック

backtest関数では、引数として取引データとP&D(パンプアンドダンプ)イベントのデータフレームを受け取ります。この関数は各イベントごとにトレードを実行し、その結果を記録します。エントリー価格に基づいて損切り価格と利確価格を計算し、それぞれのトレードで市場価格がこれらの価格に達したかどうかをチェックします。全てのトレードが終了すると、それぞれの結果を含む新しいデータフレームを返します。

評価指標の計算

evaluate_performance関数では、トレードの結果データフレームを受け取り、勝率や最終残高、最大ドローダウンなどの重要な評価指標を計算します。これらの指標は、ストラテジーの効果を評価するのに役立ちます。

結果の可視化

plot_results関数では、価格データとトレードの結果をグラフにプロットして、視覚的に分析できるようにします。各トレードのエントリーポイントを緑(勝ち)または赤(負け)の線で表示し、トレードの成功や失敗を一目で把握できます。

メイン処理

最後に、main関数ではこれらの関数をすべて呼び出して、実際のデータに対してバックテストを実行し、結果を評価して表示します。

以上がこのバックテストスクリプトの流れです。

3. 詳細な解説

コードの各部分をより詳細に解説していきましょう。

データ読み込み(load_data関数)

この関数は、CSVファイルから市場データを読み込む役割を持ちます。data_path引数にファイルパスを指定すると、Pandasライブラリを使ってデータを読み込み、タイムスタンプのカラムを日時型に変換して、それをデータフレームのインデックスとして設定します。これにより、後の処理で時間に基づくデータアクセスが容易になります。

バックテストの実行(backtest関数)

この核心部分では、与えられた市場データ(df)と特定のイベントデータ(pnd_events、パンプアンドダンプイベントを想定)を使用してバックテストを行います。関数は次のようなステップで進行します。

  1. 初期設定: 初期資金とトレードログの空リストを設定します。
  2. イベント処理のループ: pnd_eventsデータフレームの各行(イベント)を処理します。各イベントには、トレードのエントリー時間(timestamp)、エントリー価格(pump_price)、そしてダンプ価格(dump_price)が含まれています。
  3. エントリーとエグジットの計算: トレードのエントリー価格に基づき、損切り価格と利確価格を計算します。これらは、設定されたパーセンテージによって決定されます。
  4. 価格の追跡とトレードの終了条件のチェック: エントリー時刻から始まるデータを追跡し、価格が損切りまたは利確価格に到達した場合、またはデータセットの終了時にトレードを終了します。トレードの結果(勝ち、負け、保持)と利益または損失を記録します。
  5. 結果の記録: 各トレードの結果をtrade_logリストに記録し、最終的にはこのリストから成果物のデータフレームを生成します。

パフォーマンスの評価(evaluate_performance関数)

バックテストが完了した後、この関数でトレードの成績を評価します。勝ち数、負け数、勝率、最終残高、最大ドローダウンなど、トレーディング戦略のパフォーマンスを示す重要な指標を計算し、結果をコンソールに出力します。

結果の可視化(plot_results関数)

最後に、市場の価格データとトレードのエントリーポイントをグラフにプロットします。勝ちトレードは緑色の線、負けトレードは赤色の線で表示し、戦略の視覚的な分析を提供します。

メイン関数(main

この関数は、実際にバックテストを実行し、評価し、結果を可視化するためのものです。データをロードし、backtestevaluate_performance、そしてplot_results関数を順に呼び出します。

4. 実行方法

1.requirements.txt のインストール

pip install pandas numpy matplotlib

2.スクリプトの実行

python backtest_engine.py

5. 出力結果の例

=== バックテスト結果 ===
総トレード数: 25
勝率: 60.00%
最終残高: $11200.50
最大ドローダウン: 8.20%
総損益 (PnL): $1200.50

📊 グラフ
✅ 緑線 → 勝ちトレードのエントリー
✅ 赤線 → 負けトレードのエントリー


6. 機能のポイント

データの自動読み込み:リアルデータとP&Dイベントデータに基づいたシミュレーション
複数の評価指標:勝率、最大ドローダウン、損益など多角的に評価
パラメータ調整が容易:STOP_LOSSやTAKE_PROFITの最適化が可能
グラフ可視化:価格推移とエントリーポイントを視覚的に確認


7. 次のステップ

次は、パフォーマンス評価とパラメータ調整に進み、次の内容を実装します。

  • 損切り (Stop Loss) / 利確 (Take Profit) の最適化
  • 最適なエントリータイミングの分析
  • P&Dイベントごとの特徴分析によるモデル改良
Yodaka

次の記事では、バックテストの結果を分析し、最適なパラメータを見つけるための評価システムの構築についてまとめます。

関連
開発記録#145(2025/3/25)「論文ベースのbot開発フローpart.7 バックテストの結果分析」

続きを見る

よくある質問

いくつか一般的な質問とその回答を載せておきます。

質問1: バックテストではどんな市場データが必要ですか?

回答: バックテストには、主に価格データが必要です。具体的には、時間単位の価格データ(例えば、日足、時間足、分足など)が含まれている必要があり、少なくともオープン価格、ハイ価格、ロー価格、クローズ価格(OHLC)があると良いでしょう。このスクリプトでは、特にclose価格を使用して損切りや利確の判断を行っています。

質問2: 損切りや利確のパーセンテージはどのように決めるべきですか?

回答: 損切りや利確のパーセンテージは、トレーディング戦略やリスク許容度によって異なります。一般的に、損切りは保有資金に対して許容できる最大の損失額を基に設定します。利確は、市場分析や過去のデータに基づく期待利益を反映させることが一般的です。この例では損切りを3%、利確を5%に設定していますが、これは比較的保守的な設定です。

質問3: バックテストの結果が良いからといって、リアルトレードでも同じ結果が得られると保証されていますか?

回答: バックテストの結果はあくまで過去のデータに基づいたシミュレーションであるため、未来の市場動向や外部の影響を完全に予測することはできません。また、スリッページ(注文と実行の間の価格変動)や取引コストが実際のトレーディング結果に影響を与えることもあります。バックテストはあくまで一つのツールであり、その結果を盲信せず、継続的な監視と調整が必要です。

質問4: バックテストで使われる「パンプアンドダンプ」のイベントはどのように特定しますか?

回答: 「パンプアンドダンプ」のイベントは通常、異常な価格上昇後に急激な価格下落を示すパターンを特定することによって見つけることができます。市場データを分析して、短期間に価格が大幅に上昇し、その後すぐに下落するパターンを検出するアルゴリズムを使用することが一般的です。ただし、これらのイベントを正確に特定するのは非常に難しく、高度なデータ分析技術が必要とされます。

python以外でのバックテスト

Python以外にも多くのプログラミング言語でバックテストが可能です。主に用いられるのはR、C++、Java、そして最近ではJavaScriptも利用されています。それぞれの言語にはメリットとデメリットがあり、用途やトレーダーの技術的な背景によって選ばれます。

R

メリット:

  • 統計計算とデータ分析に特化しており、金融分析に関する豊富なパッケージが利用可能。
  • データの可視化に優れており、グラフ作成が容易。

デメリット:

  • パフォーマンスがPythonより劣る場合がある。
  • 大規模なデータセットやリアルタイム処理には向かない。

C++

メリット:

  • 高速な実行速度で、リアルタイムトレーディングシステムに適している。
  • メモリ管理が直接的で、高度な最適化が可能。

デメリット:

  • コードの複雑性が高く、バグが生じやすい。
  • 開発速度が遅いため、プロトタイピングには不向き。

Java

メリット:

  • 堅牢なマルチスレッド処理が可能で、リアルタイムデータ処理に強い。
  • 大企業や金融機関で広く採用されており、サポート体制が整っている。

デメリット:

  • 実行速度がC++に比べて遅い。
  • バックテストに特化したライブラリがPythonに比べて少ない。

JavaScript (Node.js)

メリット:

  • ウェブベースのトレーディングプラットフォームとの統合が容易。
  • ノンブロッキングI/Oモデルにより、リアルタイムのデータフィード処理に適している。

デメリット:

  • 実行速度がPythonやC++より劣る場合が多い。
  • 金融分析に特化したライブラリが比較的少ない。

Pythonの比較

メリット:

  • 幅広い金融ライブラリ(Pandas, NumPy, Matplotlibなど)が利用可能で、バックテスト環境の構築が容易。
  • 柔軟性が高く、プロトタイピングが速い。

デメリット:

  • 実行速度はC++やJavaに比べて遅い場合がある。
  • メモリ効率が低いため、大規模なデータ処理には適さない場合がある。

各言語は、その特性に応じて選択されるべきです。例えば、リアルタイムトレーディングシステムや高度に最適化された処理が必要な場合はC++が適していますが、データ分析やプロトタイピングの速度が重視される場合はPythonが最適です。自身のプロジェクトの要件や技術的な背景を考慮して、最も適した言語を選択しましょう。

-Bot