Backtest Datetime Visualization

by smith6jt-cop

skill

Converting backtest visualizations from bar indices/timesteps to actual datetime axes for clearer time context

Skill Details

Repository Files

1 file in this skill directory


name: backtest-datetime-visualization description: "Converting backtest visualizations from bar indices/timesteps to actual datetime axes for clearer time context" author: Claude Code date: 2025-12-13

backtest-datetime-visualization - Research Notes

Experiment Overview

Item Details
Date 2025-12-13
Goal Convert backtest result visualizations from using bar indices (0, 1, 2...) to actual datetime values for equity curves, drawdowns, and trade distributions
Environment Python 3.10, matplotlib, pandas, Jupyter notebooks
Status Success

Context

Backtest visualizations often use bar indices or timestep numbers on the x-axis, which makes it difficult to correlate results with actual market periods. Converting to datetime axes provides:

  • Clear understanding of when drawdowns occurred
  • Correlation with known market events
  • Proper time-proportional spacing

Verified Workflow

1. Equity Curve with DateTime Index

When equity_curve is a pandas Series with DatetimeIndex:

# equity_series is pd.Series with DatetimeIndex
ax.plot(equity_series.index, equity_series.values, 'b-', label='Strategy')
ax.set_xlabel('Date')
ax.tick_params(axis='x', rotation=45)  # Rotate for readability

2. Drawdown Plot with Timestamps

# Extract timestamps from equity series
equity = equity_series.values
timestamps = equity_series.index  # DatetimeIndex
running_max = np.maximum.accumulate(equity)
drawdown = (running_max - equity) / running_max * 100

# Use timestamps for x-axis
ax.fill_between(timestamps, 0, drawdown, color='red', alpha=0.5)
ax.set_xlabel('Date')
ax.tick_params(axis='x', rotation=45)
ax.invert_yaxis()  # Drawdown goes down

3. Trade P&L with Entry Times

For trade distributions, use entry_time from TradeRecord:

if len(result.trades) > 0:
    trade_pnls = [t.pnl for t in result.trades]
    trade_times = [t.entry_time for t in result.trades]

    # Use stem plot for datetime x-axis (more robust than bar)
    markerline, stemlines, baseline = ax.stem(trade_times, trade_pnls, basefmt='k-')

    # Color stems based on profit/loss
    for stem, pnl in zip(stemlines, trade_pnls):
        stem.set_color('green' if pnl > 0 else 'red')
        stem.set_alpha(0.7)

    ax.set_xlabel('Date')
    ax.tick_params(axis='x', rotation=45)

4. Bar Charts with DateTime (Alternative)

If you must use bar charts with datetime:

import matplotlib.dates as mdates

# Convert datetime to matplotlib date numbers
trade_dates = mdates.date2num(trade_times)
width = 0.5  # Width in days

ax.bar(trade_dates, trade_pnls, width=width, color=colors)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.AutoDateLocator())

Failed Attempts (Critical)

Attempt Why it Failed Lesson Learned
ax.bar(trade_times, trade_pnls) directly Width parameter issues with datetime objects Use stem plot or convert to matplotlib date numbers
range(len(drawdown)) for x-axis Loses all time context Always use equity_series.index
Not rotating x-axis labels Dates overlap and become unreadable Add ax.tick_params(axis='x', rotation=45)
Using plt.xticks(rotation=45) Affects all subplots Use ax.tick_params() for specific axis

Final Parameters

# Standard pattern for backtest visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Equity curve
ax = axes[0, 0]
ax.plot(equity_series.index, equity_series.values)
ax.set_xlabel('Date')
ax.tick_params(axis='x', rotation=45)

# Drawdown
ax = axes[0, 1]
ax.fill_between(equity_series.index, 0, drawdown)
ax.set_xlabel('Date')
ax.tick_params(axis='x', rotation=45)

# Trade P&L (use stem for datetime compatibility)
ax = axes[1, 0]
ax.stem(trade_times, trade_pnls, basefmt='k-')
ax.set_xlabel('Date')
ax.tick_params(axis='x', rotation=45)

plt.tight_layout()  # Prevent label overlap

Key Insights

  • plt.stem() handles datetime x-values better than plt.bar() without conversion
  • Always call plt.tight_layout() after rotating labels to prevent clipping
  • Backtest DataFrames should use DatetimeIndex, not integer index
  • TradeRecord objects should store entry_time as datetime, not bar index
  • For long time ranges, consider using mdates.MonthLocator() or YearLocator()
  • The observation window plots (100-step windows) should keep "Time Step" labels since they represent relative position, not calendar time

Data Requirements

Ensure your backtest engine stores proper timestamps:

@dataclass
class TradeRecord:
    symbol: str
    entry_time: datetime  # Not int!
    exit_time: Optional[datetime]
    entry_price: float
    exit_price: Optional[float]
    pnl: float

References

  • alpaca_trading/backtest/engine.py - TradeRecord with entry_time
  • notebooks/develop_branch_testing.ipynb - Visualization examples
  • matplotlib.dates documentation for advanced date formatting

Related Skills

Attack Tree Construction

Build comprehensive attack trees to visualize threat paths. Use when mapping attack scenarios, identifying defense gaps, or communicating security risks to stakeholders.

skill

Grafana Dashboards

Create and manage production Grafana dashboards for real-time visualization of system and application metrics. Use when building monitoring dashboards, visualizing metrics, or creating operational observability interfaces.

skill

Matplotlib

Foundational plotting library. Create line plots, scatter, bar, histograms, heatmaps, 3D, subplots, export PNG/PDF/SVG, for scientific visualization and publication figures.

skill

Scientific Visualization

Create publication figures with matplotlib/seaborn/plotly. Multi-panel layouts, error bars, significance markers, colorblind-safe, export PDF/EPS/TIFF, for journal-ready scientific plots.

skill

Seaborn

Statistical visualization. Scatter, box, violin, heatmaps, pair plots, regression, correlation matrices, KDE, faceted plots, for exploratory analysis and publication figures.

skill

Shap

Model interpretability and explainability using SHAP (SHapley Additive exPlanations). Use this skill when explaining machine learning model predictions, computing feature importance, generating SHAP plots (waterfall, beeswarm, bar, scatter, force, heatmap), debugging models, analyzing model bias or fairness, comparing models, or implementing explainable AI. Works with tree-based models (XGBoost, LightGBM, Random Forest), deep learning (TensorFlow, PyTorch), linear models, and any black-box model

skill

Pydeseq2

Differential gene expression analysis (Python DESeq2). Identify DE genes from bulk RNA-seq counts, Wald tests, FDR correction, volcano/MA plots, for RNA-seq analysis.

skill

Query Writing

For writing and executing SQL queries - from simple single-table queries to complex multi-table JOINs and aggregations

skill

Pydeseq2

Differential gene expression analysis (Python DESeq2). Identify DE genes from bulk RNA-seq counts, Wald tests, FDR correction, volcano/MA plots, for RNA-seq analysis.

skill

Scientific Visualization

Meta-skill for publication-ready figures. Use when creating journal submission figures requiring multi-panel layouts, significance annotations, error bars, colorblind-safe palettes, and specific journal formatting (Nature, Science, Cell). Orchestrates matplotlib/seaborn/plotly with publication styles. For quick exploration use seaborn or plotly directly.

skill

Skill Information

Category:Skill
Last Updated:12/13/2025