Re: Quarterly goals beyond money: process metrics only
Posted: Mon Oct 19, 2026 5:50 pm
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.
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.
Code: Select all
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}")