Every serious trader keeps a journal. But most never analyze their data systematically. Let's build an automated trading journal analyzer that extracts actionable insights from your trade history.
Input Format
Most platforms export CSV trade logs. We'll parse a standard format:
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
def generate_sample_trades(n=200):
np.random.seed(42)
symbols = ["ES", "NQ", "MES", "CL", "GC"]
sides = ["LONG", "SHORT"]
trades = []
for i in range(n):
entry_time = datetime(2025, 1, 1) + timedelta(
days=np.random.randint(0, 90),
hours=np.random.randint(9, 16),
minutes=np.random.randint(0, 60)
)
symbol = np.random.choice(symbols, p=[0.35, 0.25, 0.20, 0.12, 0.08])
side = np.random.choice(sides)
pnl = np.random.normal(25, 300)
duration = np.random.exponential(45)
trades.append({
"trade_id": i + 1,
"entry_time": entry_time,
"exit_time": entry_time + timedelta(minutes=duration),
"symbol": symbol,
"side": side,
"quantity": np.random.choice([1, 2, 3, 5]),
"pnl": round(pnl, 2),
"commission": round(np.random.uniform(2, 8), 2),
})
return pd.DataFrame(trades)
df = generate_sample_trades()
df["net_pnl"] = df["pnl"] - df["commission"]
df["entry_hour"] = df["entry_time"].dt.hour
df["day_of_week"] = df["entry_time"].dt.day_name()
df["duration_min"] = (df["exit_time"] - df["entry_time"]).dt.total_seconds() / 60
Core Analysis Functions
class TradeAnalyzer:
def __init__(self, df: pd.DataFrame):
self.df = df.copy()
self.df["is_winner"] = self.df["net_pnl"] > 0
def summary_stats(self) -> dict:
d = self.df
winners = d[d["is_winner"]]
losers = d[~d["is_winner"]]
return {
"total_trades": len(d),
"win_rate": f"{len(winners) / len(d) * 100:.1f}%",
"total_pnl": f"${d['net_pnl'].sum():,.2f}",
"avg_winner": f"${winners['net_pnl'].mean():,.2f}" if len(winners) else "N/A",
"avg_loser": f"${losers['net_pnl'].mean():,.2f}" if len(losers) else "N/A",
"profit_factor": f"{abs(winners['net_pnl'].sum() / losers['net_pnl'].sum()):.2f}" if losers['net_pnl'].sum() != 0 else "inf",
"largest_winner": f"${d['net_pnl'].max():,.2f}",
"largest_loser": f"${d['net_pnl'].min():,.2f}",
"avg_duration_min": f"{d['duration_min'].mean():.1f}",
"sharpe_approx": f"{d['net_pnl'].mean() / d['net_pnl'].std() * np.sqrt(252):.2f}",
}
def by_symbol(self) -> pd.DataFrame:
return self.df.groupby("symbol").agg(
trades=("net_pnl", "count"),
total_pnl=("net_pnl", "sum"),
win_rate=("is_winner", "mean"),
avg_pnl=("net_pnl", "mean"),
).round(2).sort_values("total_pnl", ascending=False)
def by_hour(self) -> pd.DataFrame:
return self.df.groupby("entry_hour").agg(
trades=("net_pnl", "count"),
total_pnl=("net_pnl", "sum"),
win_rate=("is_winner", "mean"),
).round(2)
def by_day(self) -> pd.DataFrame:
day_order = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
result = self.df.groupby("day_of_week").agg(
trades=("net_pnl", "count"),
total_pnl=("net_pnl", "sum"),
win_rate=("is_winner", "mean"),
).round(2)
return result.reindex(day_order)
def streak_analysis(self) -> dict:
streaks = []
current = 0
for win in self.df["is_winner"]:
if win:
current = max(0, current) + 1
else:
current = min(0, current) - 1
streaks.append(current)
return {
"max_win_streak": max(streaks),
"max_loss_streak": abs(min(streaks)),
"current_streak": streaks[-1] if streaks else 0,
}
analyzer = TradeAnalyzer(df)
stats = analyzer.summary_stats()
for k, v in stats.items():
print(f" {k}: {v}")
print("\nPerformance by Symbol:")
print(analyzer.by_symbol().to_string())
Generating an Automated Report
def generate_report(analyzer: TradeAnalyzer) -> str:
stats = analyzer.summary_stats()
streaks = analyzer.streak_analysis()
by_sym = analyzer.by_symbol()
best_symbol = by_sym.index[0]
worst_symbol = by_sym.index[-1]
report = f'''
# Trading Journal Report
Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}
## Overview
- Total Trades: {stats['total_trades']}
- Win Rate: {stats['win_rate']}
- Total P&L: {stats['total_pnl']}
- Profit Factor: {stats['profit_factor']}
## Key Insights
- Best instrument: {best_symbol}
- Worst instrument: {worst_symbol}
- Max win streak: {streaks['max_win_streak']}
- Max loss streak: {streaks['max_loss_streak']}
'''
return report
print(generate_report(analyzer))
Key Takeaways
The best traders treat their journal data like a dataset. Patterns emerge that you'll never spot manually β your worst hour, your best instrument, whether you overtrade on Mondays.
For traders using funded accounts, tracking these metrics is even more critical since you're operating within strict drawdown limits. If you're comparing which firm's rules fit your trading style, PropFirmKey has a comprehensive comparison tool that covers firms like Alpha Futures and their specific rule sets.
Happy analyzing!
Top comments (0)