Claude-skill-registry backtest-datetime-visualization

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

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/backtest-datetime-visualization" ~/.claude/skills/majiayu000-claude-skill-registry-backtest-datetime-visualization && rm -rf "$T"
manifest: skills/data/backtest-datetime-visualization/SKILL.md
source content

backtest-datetime-visualization - Research Notes

Experiment Overview

ItemDetails
Date2025-12-13
GoalConvert backtest result visualizations from using bar indices (0, 1, 2...) to actual datetime values for equity curves, drawdowns, and trade distributions
EnvironmentPython 3.10, matplotlib, pandas, Jupyter notebooks
StatusSuccess

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)

AttemptWhy it FailedLesson Learned
ax.bar(trade_times, trade_pnls)
directly
Width parameter issues with datetime objectsUse
stem
plot or convert to matplotlib date numbers
range(len(drawdown))
for x-axis
Loses all time contextAlways use
equity_series.index
Not rotating x-axis labelsDates overlap and become unreadableAdd
ax.tick_params(axis='x', rotation=45)
Using
plt.xticks(rotation=45)
Affects all subplotsUse
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