Skip to content
Back to skills

Backtest Capital Accounting

ASecurity

Critical fix for backtest capital accounting when equity curves are inflated or drawdown metrics are meaningless

  • 666 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 12, 2026
ai-agentspythongorailsapi

Works with

  • claude code
  • api

Security analysis

A100/100

Scanned June 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill backtest-capital-accounting --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Backtest Capital Accounting?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Backtest Capital Accounting
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-backtest-capital-accounting/badge)](https://www.skillsdirectory.com/skills/majiayu000-backtest-capital-accounting)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
name: backtest-capital-accounting
description: "Critical fix for backtest capital accounting when equity curves are inflated or drawdown metrics are meaningless"
author: Claude Code
date: 2025-12-16
---

# Backtest Capital Accounting - Research Notes

## Experiment Overview
| Item | Details |
|------|---------|
| **Date** | 2025-12-16 |
| **Goal** | Fix inflated equity curves and meaningless drawdown in backtests |
| **Environment** | Python 3.10, pandas, custom BacktestEngine |
| **Status** | Success |

## Context
Backtest engines often fail to properly track cash flows, leading to:
- Capital not deducted when positions open (inflated buying power)
- Sale proceeds not properly credited on close
- Equity calculation ignoring open position market values
- Meaningless max_drawdown metrics that can't trigger guardrails

The bug is subtle because the backtest "runs" without errors but produces unrealistic results.

## Verified Workflow

### 1. Correct `_open_position()` Implementation
```python
def _open_position(self, symbol, side, price, time, bar_idx, confidence):
    """Open position with proper capital accounting."""
    # Apply slippage first
    slippage = price * self.config.slippage_pct
    entry_price = price + (slippage if side == 1 else -slippage)

    # Calculate position size from AVAILABLE capital
    position_value = self.capital * self.config.position_size_pct
    shares = position_value / entry_price

    # CRITICAL: Deduct full position value + commission from capital
    total_cost = (shares * entry_price) + self.config.commission_per_trade
    self.capital -= total_cost  # <-- This line is often missing!

    # Store position
    self.positions[symbol] = Position(...)
```

### 2. Correct `_close_position()` Implementation
```python
def _close_position(self, symbol, price, time, bar_idx, reason):
    """Close position with proper capital accounting."""
    position = self.positions[symbol]

    # Apply slippage
    slippage = price * self.config.slippage_pct
    exit_price = price - (slippage if position.side == 1 else -slippage)

    commission = self.config.commission_per_trade

    # CRITICAL: Add sale proceeds (exit value - commission) to capital
    if position.side == 1:  # Long
        sale_proceeds = position.shares * exit_price - commission
    else:  # Short
        # Short P&L: entry - exit (profit when price falls)
        sale_proceeds = (2 * position.shares * position.entry_price) - \
                       (position.shares * exit_price) - commission

    self.capital += sale_proceeds  # <-- This line is often wrong!

    del self.positions[symbol]
```

### 3. Correct `_calculate_equity()` Implementation
```python
def _calculate_equity(self, current_price: float) -> float:
    """Calculate total equity = cash + market value of positions."""
    equity = self.capital  # Cash on hand

    for position in self.positions.values():
        if position.side == 1:  # Long
            market_value = position.shares * current_price
        else:  # Short
            # Short market value: 2*entry - current (profit when price falls)
            market_value = (2 * position.shares * position.entry_price) - \
                          (position.shares * current_price)
        equity += market_value

    return equity
```

## Failed Attempts (Critical)

| Attempt | Why it Failed | Lesson Learned |
|---------|---------------|----------------|
| Only tracking P&L on close | Capital shows full value even with open positions | Must deduct on entry, add on exit |
| Using `capital = initial + sum(pnl)` | Ignores unrealized gains/losses | Equity must include position market value |
| Not applying slippage to entry/exit | Overstated returns | Apply slippage before calculating shares/proceeds |
| Adding position value to capital on close | Double-counts - position value already in equity | Add sale PROCEEDS, not position value |
| Short position P&L = (entry - exit) * shares | Wrong sign when price rises | Use 2*entry - exit formula for short market value |

## Final Parameters
```python
# BacktestConfig with proper defaults
@dataclass
class BacktestConfig:
    initial_capital: float = 100_000.0
    position_size_pct: float = 0.10      # 10% per position
    commission_per_trade: float = 1.0    # $1 per trade
    slippage_pct: float = 0.0005         # 0.05% slippage
    stop_loss_pct: float = 0.02          # 2% stop
    take_profit_pct: float = 0.04        # 4% TP
```

## Key Insights
- **The capital accounting bug is silent** - backtests run but produce garbage metrics
- **Equity must equal cash + market value** - at all times, not just on close
- **Short positions need special handling** - profit when price falls, loss when rises
- **Commission affects both entry AND exit** - double the impact of what you might expect
- **Slippage compounds with position size** - large positions have more slippage impact
- **Test with round trips** - open then close same position, capital should reflect P&L exactly

## Validation Check
After fixing, verify with this test:
```python
# After a round trip (buy then sell same shares):
# capital_after = capital_before + realized_pnl - 2*commission - slippage_cost
# If capital_after > capital_before + realistic_pnl, accounting is broken
```

## References
- CLAUDE.md guardrails: "Realistic backtests: Do not alter cash-flow logic without full audit"
- Standard brokerage margin accounting rules
- Walk-forward validation best practices

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.