MT5のストラテジーテスターレポート結果を変換するやつ

はじめに


ストラテジーテスターの結果って出力できる形式が限られていて不便ですよね。
折角結果が出せるのでcsvとかにしてAIとかに食わせたりしたいじゃないですか。

結論


以下のPythonコードを利用すれば解決。
サンプル数少なくあまりテストしてないので何かあれば教えてください。

以下でインストールして出力したhtmlファイルを指定するだけ。

pip install beautifulsoup4 pandas
python3 convert_mt5_report.py ReportTester-xxxxxxxx.html```
import re
from bs4 import BeautifulSoup
import pandas as pd
import sys

def extract_trades_from_html(html_content):
    soup = BeautifulSoup(html_content, 'html.parser')
    trades = []
    
    # テーブルの行を取得
    rows = soup.find_all('tr', {'bgcolor': ['#F7F7F7', '#FFFFFF']})
    
    for row in rows:
        cols = row.find_all('td')
        if len(cols) >= 13:  # 必要なカラム数を確認
            try:
                # 数値データの安全な変換
                def safe_float(value):
                    try:
                        return float(value.strip()) if value.strip() else 0.0
                    except (ValueError, AttributeError):
                        return 0.0

                trade = {
                    'datetime': cols[0].text.strip(),
                    'ticket': cols[1].text.strip(),
                    'symbol': cols[2].text.strip(),
                    'type': cols[3].text.strip(),
                    'direction': cols[4].text.strip(),
                    'volume': safe_float(cols[5].text),
                    'price': safe_float(cols[6].text),
                    'order': cols[7].text.strip(),
                    'commission': safe_float(cols[8].text),
                    'swap': safe_float(cols[9].text),
                    'profit': safe_float(cols[10].text),
                    'balance': safe_float(cols[11].text),
                    'comment': cols[12].text.strip()
                }
                trades.append(trade)
            except Exception as e:
                print(f"行の処理中にエラー: {str(e)}")
                print(f"問題のある行: {[col.text.strip() for col in cols]}")
                continue
    
    if not trades:
        print("警告: 抽出されたトレードがありません")
    else:
        print(f"抽出されたトレード数: {len(trades)}")
    
    return trades

def extract_parameters_from_html(html_content):
    soup = BeautifulSoup(html_content, 'html.parser')
    params = {}
    
    # パラメータ行を取得
    param_rows = soup.find_all('tr', {'align': 'right'})
    
    for row in param_rows:
        cols = row.find_all('td')
        if len(cols) >= 2:
            param_text = cols[-1].text.strip()
            if param_text.startswith('='):
                continue
            if param_text.startswith('b>'):
                continue
            params[param_text.split('=')[0].strip()] = param_text.split('=')[1].strip() if '=' in param_text else param_text

    return params

def convert_to_csv(input_file, output_trades_file, output_params_file):
    # HTMLファイルをバイナリモードで読み込む
    try:
        with open(input_file, 'rb') as f:
            raw_content = f.read()
            
        # デバッグ情報を表示
        print(f"ファイルサイズ: {len(raw_content)} bytes")
        print(f"先頭20バイト: {raw_content[:20].hex()}")
        
        # BOMチェックとデコード
        if raw_content.startswith(b'\xff\xfe'):
            try:
                # UTF-16LE with BOM
                html_content = raw_content.decode('utf-16')
                print("デコード成功: UTF-16 (with BOM)")
            except UnicodeDecodeError:
                try:
                    # UTF-16LE specific
                    html_content = raw_content.decode('utf-16le')
                    print("デコード成功: UTF-16LE")
                except UnicodeDecodeError:
                    print("UTF-16LEデコード失敗")
                    raise
        else:
            # その他のエンコーディングを試す
            encodings = [
                'utf-16le',
                'utf-16be',
                'utf-16',
                'utf-8-sig',
                'utf-8',
                'shift_jis',
                'cp932',
                'euc-jp',
                'latin1'  # 最後の手段として追加
            ]
            
            for encoding in encodings:
                try:
                    html_content = raw_content.decode(encoding)
                    print(f"デコード成功: {encoding}")
                    break
                except UnicodeDecodeError:
                    print(f"{encoding}でのデコード失敗")
                    html_content = None
                    continue
            
            if html_content is None:
                raise ValueError(f"すべてのエンコーディングのデコードに失敗しました")
        
        # デコードされたコンテンツの最初の部分を表示(デバッグ用)
        print("\nデコードされたコンテンツの先頭:")
        print(html_content[:200])
        
        # トレード情報を抽出
        trades = extract_trades_from_html(html_content)
        trades_df = pd.DataFrame(trades)
        
        # パラメータ情報を抽出
        params = extract_parameters_from_html(html_content)
        params_df = pd.DataFrame(list(params.items()), columns=['Parameter', 'Value'])
        
        # CSVファイルとして保存
        trades_df.to_csv(output_trades_file, index=False, encoding='utf-8-sig')
        params_df.to_csv(output_params_file, index=False, encoding='utf-8-sig')
        
        return trades_df, params_df
        
    except Exception as e:
        print(f"エラーの詳細: {str(e)}")
        raise

def analyze_trades(trades_df):
    """トレード分析を行う関数"""
    if trades_df.empty:
        print("警告: トレードデータが空です")
        return pd.DataFrame([['データなし', 'N/A']], columns=['指標', '値'])
    
    try:
        total_trades = len(trades_df)
        profitable_trades = len(trades_df[trades_df['profit'] > 0])
        losing_trades = len(trades_df[trades_df['profit'] < 0])
        win_rate = profitable_trades / total_trades * 100 if total_trades > 0 else 0
        
        total_profit = trades_df['profit'].sum()
        max_profit = trades_df['profit'].max()
        max_loss = trades_df['profit'].min()
        avg_profit = trades_df[trades_df['profit'] > 0]['profit'].mean()
        avg_loss = trades_df[trades_df['profit'] < 0]['profit'].mean()
        
        analysis = {
            '総トレード数': total_trades,
            '勝ちトレード数': profitable_trades,
            '負けトレード数': losing_trades,
            '勝率': f'{win_rate:.2f}%',
            '総利益': f'{total_profit:.2f}',
            '最大利益': f'{max_profit:.2f}',
            '最大損失': f'{max_loss:.2f}',
            '平均利益': f'{avg_profit:.2f}' if not pd.isna(avg_profit) else 'N/A',
            '平均損失': f'{avg_loss:.2f}' if not pd.isna(avg_loss) else 'N/A',
            'プロフィットファクター': f'{abs(avg_profit / avg_loss):.2f}' if not pd.isna(avg_loss) and avg_loss != 0 else 'N/A'
        }
        
        return pd.DataFrame(list(analysis.items()), columns=['指標', '値'])
        
    except Exception as e:
        print(f"分析中にエラー: {str(e)}")
        return pd.DataFrame([['エラー', str(e)]], columns=['指標', '値'])

def main():
    if len(sys.argv) < 2:
        print("使用方法: python convert_mt5_report.py <input_html_file>")
        return
    
    input_file = sys.argv[1]
    output_trades_file = input_file.replace('.html', '_trades.csv')
    output_params_file = input_file.replace('.html', '_params.csv')
    output_analysis_file = input_file.replace('.html', '_analysis.csv')
    
    try:
        trades_df, params_df = convert_to_csv(input_file, output_trades_file, output_params_file)
        analysis_df = analyze_trades(trades_df)
        analysis_df.to_csv(output_analysis_file, index=False, encoding='utf-8-sig')
        
        print(f"\n変換完了:")
        print(f"トレード情報: {output_trades_file}")
        print(f"パラメータ情報: {output_params_file}")
        print(f"分析結果: {output_analysis_file}")
        
        print("\n分析結果:")
        print(analysis_df.to_string(index=False))
        
    except Exception as e:
        print(f"処理中にエラーが発生しました: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    main() 

以上

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