Document your personal trading journey. Track daily equity curves, review winning and losing streaks, share trade screenshots, and get constructive feedback.
This script processes raw trade-by-trade exports, calculates the running balance to convert currency profits into percentage returns, and computes both ratios.
Because trading platforms export raw PnL rather than percentage returns, the script reconstructs the equity curve first. It also calculates the raw Sortino ratio on a per-trade basis, which is more accurate for high-volume M15/H4 scaling systems than attempting to force intraday data into a daily annualized formula.
import pandas as pd
import numpy as np
def evaluate_trade_history(csv_path, starting_balance, profit_column='Profit', minimum_acceptable_return=0.0):
"""
Calculates Sortino and Omega ratios from a raw trading export.
csv_path: Path to the exported CSV (e.g., from MT4/MT5 or cTrader).
starting_balance: The initial account balance before the first trade in the series.
profit_column: The name of the column containing the net PnL (after commissions/swaps).
minimum_acceptable_return (MAR): The threshold for success (usually 0.0 for raw returns).
"""
# Load the trading data
try:
df = pd.read_csv(csv_path)
except FileNotFoundError:
return "Error: CSV file not found."
if profit_column not in df.columns:
return f"Error: Column '{profit_column}' not found. Please verify your CSV headers."
# 1. Reconstruct the equity curve to find percentage returns per trade
# Shift by 1 so the balance reflects the capital available *before* the trade executed
df['Running_Balance'] = starting_balance + df[profit_column].cumsum().shift(1).fillna(0)
# Calculate fractional return per trade
df['Return'] = df[profit_column] / df['Running_Balance']
# 2. Sortino Ratio Calculation
# Isolate downside returns (trades that fell below the minimum acceptable return)
downside_returns = df.loc[df['Return'] < minimum_acceptable_return, 'Return']
# Calculate downside deviation (Root Mean Square of downside returns)
if len(downside_returns) > 0:
downside_dev = np.sqrt(np.mean((downside_returns - minimum_acceptable_return)**2))
else:
downside_dev = 0.0
mean_return = df['Return'].mean()
# Calculate per-trade Sortino.
# Note: To annualize this for a 2-6 week series, multiply by np.sqrt(expected_trades_per_year)
if downside_dev > 0:
sortino = (mean_return - minimum_acceptable_return) / downside_dev
else:
sortino = np.inf # Indicates no downside volatility
# 3. Omega Ratio Calculation
# Sum of all returns above the MAR
returns_above_mar = df.loc[df['Return'] > minimum_acceptable_return, 'Return'] - minimum_acceptable_return
sum_positive = returns_above_mar.sum()
# Sum of all absolute losses below the MAR
returns_below_mar = minimum_acceptable_return - df.loc[df['Return'] < minimum_acceptable_return, 'Return']
sum_negative = returns_below_mar.sum()
if sum_negative > 0:
omega = sum_positive / sum_negative
else:
omega = np.inf # Indicates no losses below the threshold
return {
"Total Executed Trades": len(df),
"Starting Balance": starting_balance,
"Ending Balance": round(df['Running_Balance'].iloc[-1] + df[profit_column].iloc[-1], 2),
"Sortino Ratio (Per Trade)": round(sortino, 4),
"Omega Ratio": round(omega, 4)
}
# --- Execution Example ---
# Save your export as 'history.csv' in the same directory.
# Adjust the 'profit_column' name to exactly match your CSV header.
#
# results = evaluate_trade_history(
# csv_path='history.csv',
# starting_balance=1000.00,
# profit_column='Profit'
# )
#
# for metric, value in results.items():
# print(f"{metric}: {value}")
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Net Profit Accuracy: Ensure the profit_column you target in the CSV represents the Net PnL. If your export splits gross profit, commissions, and swaps into separate columns, you will need to add a line to the script to combine them (df['Net_Profit'] = df['Profit'] + df['Commission'] + df['Swap']) before running the calculation.
Chronological Order: The script calculates the running balance sequentially. Ensure the CSV is sorted chronologically by execution or close time. If the export defaults to newest-to-oldest, add df = df.sort_values(by='Time_Column') immediately after loading the CSV.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Time Column Requirement: To accurately calculate the Compounded Annual Growth Rate (CAGR), the script now requires a timestamp. When exporting reports directly from MetaTrader 5 or cTrader, the default column is typically Time or Close Time. Ensure the column name in the script perfectly matches your export's header.
Annualizing Short Series: If you run this script on a single 2 to 6-week trading series rather than your full annual aggregate, the CAGR calculation will mathematically annualize those short-term returns. A 15% gain over 4 weeks will project into a massive triple-digit CAGR, which artificially inflates the MAR Ratio. The MAR Ratio is most reliable when applied to at least one full calendar year of combined execution blocks.
Absolute vs. Max Drawdown: The script splits these two metrics intentionally. Absolute Drawdown confirms whether you ever breached your baseline risk capital, while Max Drawdown tracks how much accumulated scaling profit you surrendered during an aggressive momentum run.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
To aggregate multiple 2-6 week trading blocks into a single annual dataset, we use Python's glob library to batch-read an entire folder of CSVs, and pandas.concat() to stitch them together.
Because you take a 4-week vacation between series, the script sorts the combined data chronologically. The resulting Compounded Annual Growth Rate (CAGR) automatically accounts for these flat periods, giving you a true annual representation of your time in and out of the market.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
This specific configuration assumes continuous compounding across the entire year. If your 4-week vacation routine includes withdrawing profits and manually resetting your account balance back to the strict 2-5% risk baseline before starting the next block, this script will under-report your true percentage gains. If you reset your capital, the script needs logic to interpret large manual withdrawals between the CSV boundaries.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.