DEV Community

Propfirmkey
Propfirmkey

Posted on

Automating Trading Journal Analysis with Python & Pandas

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
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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)