Add export-to-browser utility

- export-report-html.py: Converts MT5 report to clean, styled HTML
- export-to-browser.sh: Wrapper script for easy usage
- Generates beautiful HTML report viewable in any browser
- Includes performance summary, trade stats, and risk metrics
This commit is contained in:
2026-03-21 19:42:16 -04:00
parent 14c4e7da2f
commit e58a4a425f
2 changed files with 419 additions and 0 deletions

381
export-report-html.py Executable file
View File

@@ -0,0 +1,381 @@
#!/usr/bin/env python3
"""Export MT5 HTML report to a clean, browser-viewable HTML file"""
import re
import sys
import os
from datetime import datetime
def parse_report(html_file):
"""Parse MT5 HTML report and extract data"""
try:
with open(html_file, 'rb') as f:
content = f.read().decode('utf-16-le', errors='ignore')
except Exception as e:
print(f"Error reading file: {e}")
return None
# Clean text for parsing
text = re.sub('<[^<]+?>', ' ', content)
text = text.replace('&nbsp;', ' ')
def extract_value(pattern, text, group=1):
match = re.search(pattern, text)
return match.group(group).strip() if match else "N/A"
def parse_num(s):
if not s or s == "N/A":
return 0
try:
s = str(s).replace(' ', '').replace(',', '.')
parts = s.split('.')
if len(parts) > 2:
s = ''.join(parts[:-1]) + '.' + parts[-1]
return float(s)
except:
return 0
data = {
'filename': os.path.basename(html_file),
'account': extract_value(r'Account:\s*(\d+)', text),
'name': extract_value(r'Name:\s*([^\d]+?)(?:\s+Account|\s*$)', text),
'company': extract_value(r'Company:\s*([^\d]+?)(?:\s+Date|\s*$)', text),
'net_profit': extract_value(r'Total Net Profit[^\d\-]*([\-\d\s,\.]+)', text),
'gross_profit': extract_value(r'Gross Profit[^\d]*([\d\s,\.]+)', text),
'gross_loss': extract_value(r'Gross Loss[^\d\-]*([\-\d\s,\.]+)', text),
'profit_factor': extract_value(r'Profit Factor[^\d]*([\d\.]+)', text),
'expected_payoff': extract_value(r'Expected Payoff[^\d]*([\d\s,\.]+)', text),
'total_trades': extract_value(r'Total Trades[^\d]*(\d+)', text),
'profit_trades': extract_value(r'Profit Trades \(%[^)]*\)[^\d]*(\d+)', text),
'loss_trades': extract_value(r'Loss Trades \(%[^)]*\)[^\d]*(\d+)', text),
'win_pct': extract_value(r'Profit Trades \(%[^)]*\)[^\d]*\d+[^\d]*\((\d+\.?\d*)%', text),
'largest_profit': extract_value(r'Largest profit trade[^\d]*([\d\s,\.]+)', text),
'largest_loss': extract_value(r'Largest loss trade[^\d\-]*([\-\d\s,\.]+)', text),
'max_drawdown': extract_value(r'Balance Drawdown Maximal[^\d]*([\d\s,\.]+)', text),
}
# Calculate values
net = parse_num(data['net_profit'])
data['starting_balance'] = 100000.00
data['current_balance'] = 100000.00 + net
data['return_pct'] = (net / 100000.00) * 100
return data
def generate_html(data, output_file):
"""Generate clean HTML report"""
html_content = f'''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MT5 Trading Report - Account {data['account']}</title>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}}
.container {{
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}}
.header {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}}
.header h1 {{
font-size: 28px;
margin-bottom: 10px;
}}
.header p {{
opacity: 0.9;
font-size: 16px;
}}
.content {{
padding: 30px;
}}
.section {{
margin-bottom: 30px;
}}
.section-title {{
font-size: 20px;
color: #333;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #667eea;
display: flex;
align-items: center;
gap: 10px;
}}
.info-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}}
.info-item {{
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #667eea;
}}
.info-label {{
font-size: 12px;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 5px;
}}
.info-value {{
font-size: 18px;
font-weight: 600;
color: #333;
}}
.profit {{
color: #28a745;
}}
.loss {{
color: #dc3545;
}}
.metric-box {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 25px;
border-radius: 12px;
text-align: center;
margin: 20px 0;
}}
.metric-value {{
font-size: 42px;
font-weight: bold;
margin-bottom: 5px;
}}
.metric-label {{
font-size: 14px;
opacity: 0.9;
}}
.stats-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-top: 20px;
}}
.stat-card {{
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
text-align: center;
transition: transform 0.2s;
}}
.stat-card:hover {{
transform: translateY(-5px);
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}}
.stat-value {{
font-size: 24px;
font-weight: bold;
color: #667eea;
margin-bottom: 5px;
}}
.stat-label {{
font-size: 12px;
color: #666;
}}
.footer {{
background: #f8f9fa;
padding: 20px;
text-align: center;
color: #666;
font-size: 12px;
}}
.badge {{
display: inline-block;
padding: 5px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-left: 10px;
}}
.badge-success {{
background: #d4edda;
color: #155724;
}}
.badge-warning {{
background: #fff3cd;
color: #856404;
}}
@media (max-width: 600px) {{
.content {{
padding: 20px;
}}
.metric-value {{
font-size: 32px;
}}
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📊 MT5 Trading Report</h1>
<p>Account {data['account']} | {data['name']} | Generated on {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
</div>
<div class="content">
<!-- Performance Summary -->
<div class="section">
<h2 class="section-title">💰 Performance Summary</h2>
<div class="metric-box">
<div class="metric-value">{'+' if data['return_pct'] > 0 else ''}{data['return_pct']:.2f}%</div>
<div class="metric-label">Total Return</div>
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Starting Balance</div>
<div class="info-value">${data['starting_balance']:,.2f}</div>
</div>
<div class="info-item">
<div class="info-label">Current Balance</div>
<div class="info-value">${data['current_balance']:,.2f}</div>
</div>
<div class="info-item">
<div class="info-label">Net Profit</div>
<div class="info-value {'profit' if data['return_pct'] > 0 else 'loss'}">${data['net_profit']}</div>
</div>
<div class="info-item">
<div class="info-label">Profit Factor</div>
<div class="info-value">{data['profit_factor']}</div>
</div>
<div class="info-item">
<div class="info-label">Gross Profit</div>
<div class="info-value profit">${data['gross_profit']}</div>
</div>
<div class="info-item">
<div class="info-label">Gross Loss</div>
<div class="info-value loss">${data['gross_loss']}</div>
</div>
</div>
</div>
<!-- Trade Statistics -->
<div class="section">
<h2 class="section-title">📈 Trade Statistics</h2>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">{data['total_trades']}</div>
<div class="stat-label">Total Trades</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #28a745;">{data['profit_trades']}</div>
<div class="stat-label">Winning Trades</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #dc3545;">{data['loss_trades']}</div>
<div class="stat-label">Losing Trades</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: {'#28a745' if float(data['win_pct'] or 0) > 60 else '#ffc107'}">{data['win_pct']}%</div>
<div class="stat-label">Win Rate</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #28a745;">${data['largest_profit']}</div>
<div class="stat-label">Largest Win</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #dc3545;">${data['largest_loss']}</div>
<div class="stat-label">Largest Loss</div>
</div>
</div>
</div>
<!-- Risk Metrics -->
<div class="section">
<h2 class="section-title">⚠️ Risk Metrics</h2>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Expected Payoff</div>
<div class="info-value">${data['expected_payoff']}</div>
</div>
<div class="info-item">
<div class="info-label">Max Drawdown</div>
<div class="info-value">${data['max_drawdown']}</div>
</div>
</div>
</div>
</div>
<div class="footer">
<p>Report generated by MT5 Report Exporter | Strategy: MultiSignal Confluence EA</p>
<p>Source: {data['filename']}</p>
</div>
</div>
</body>
</html>'''
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html_content)
return output_file
def main():
# Find latest report if no argument provided
if len(sys.argv) > 1:
report_file = sys.argv[1]
else:
report_dir = os.path.expanduser("~/mt5-docker/config/.wine/drive_c/users/abc/Desktop")
import glob
reports = glob.glob(f"{report_dir}/ReportHistory-*.html")
if not reports:
print("❌ No report files found!")
print(f"Searched: {report_dir}")
sys.exit(1)
report_file = max(reports, key=os.path.getmtime)
if not os.path.exists(report_file):
print(f"❌ File not found: {report_file}")
sys.exit(1)
print(f"📊 Parsing report: {os.path.basename(report_file)}")
data = parse_report(report_file)
if not data:
print("❌ Failed to parse report")
sys.exit(1)
# Generate output filename
timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
output_file = os.path.expanduser(f"~/mt5-report-{data['account']}-{timestamp}.html")
# Generate HTML
generate_html(data, output_file)
print(f"✅ Report exported to: {output_file}")
print("")
print(f"📊 Summary:")
print(f" Account: {data['account']}")
print(f" Name: {data['name']}")
print(f" Profit: ${data['net_profit']}")
print(f" Return: {data['return_pct']:.2f}%")
print(f" Win Rate: {data['win_pct']}%")
print("")
print("🌐 Open in browser:")
print(f" file://{output_file}")
return output_file
if __name__ == "__main__":
main()