backtesting-sim

backtesting-sim is a skill for Claude Code from mahmoud20138/Tradecraft. It costs 118 tokens per session (11,356 once invoked), scanned A, original, MIT.

A toolkit for testing trading strategies against historical market data and simulating trades without using real money. It also covers comparing strategies and describing trading plans in plain language.

In plain words
What is it for?
Running backtests, paper-trading simulations, strategy comparisons, automated strategy building, and calculating measures such as Sharpe ratio, Sortino ratio, drawdown, and profit factor.
Why use it?
It helps estimate how a strategy might have behaved before risking capital and makes common performance and risk calculations repeatable.

Skill for Claude Code

Written for Claude Code: shipped in a Claude Code plugin.

Part of the tradecraft plugin — 58 skills shipped together

Good fit Running backtests, paper-trading simulations, strategy comparisons, automated strategy building, and calculating measures such as Sharpe ratio, Sortino ratio, drawdown, and profit factor.

Compare 6 skills from other repositories ↓
Install with agentmods
npx agentmods add skills/mahmoud20138/tradecraft/backtesting-sim
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Any agent
npx skills add mahmoud20138/Tradecraft --skill backtesting-sim
Clone the repo
git clone --depth 1 https://github.com/mahmoud20138/Tradecraft

Made for: Claude Code.

Or install tradecraft, the plugin that ships this one along with the rest of its 58 skills.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for backtesting-sim

README.md
[![agentmods](https://agentmods.dev/badge/skills/mahmoud20138/tradecraft/backtesting-sim.svg)](https://agentmods.dev/skills/mahmoud20138/tradecraft/backtesting-sim)
Your own site
<a href="https://agentmods.dev/skills/mahmoud20138/tradecraft/backtesting-sim"><img src="https://agentmods.dev/badge/skills/mahmoud20138/tradecraft/backtesting-sim.svg" alt="Measured on agentmods" height="20"></a>
Per session 118 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 11,356 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00118 $0.11356
Opus 5 $0.00059 $0.05678
Sonnet 5 $0.00024 $0.02271
Haiku 4.5 $0.00012 $0.01136

Measured 7d ago against content hash 56d141840f6c, method: parsed. Prices are Anthropic first-party input rates as of 2026-09-07, from the pricing page.

Security

Grade A, and why

backtesting-sim scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 7d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

plugins/tradecraft/skills/backtesting-sim/SKILL.md · 1,157 lines

How it starts

The opening of the file, as written. The whole thing — 1,157 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Skill: Backtesting Sim | Domain: trading | Category: quantitative | Level: intermediate Tags: trading, quant, backtesting, simulation, paper-trading, vectorized

Vectorized Backtester

Vectorized Backtester

import pandas as pd
import numpy as np
from typing import Callable, Optional

# ── Shared helpers ──────────────────────────────────────────────────────────

def _sharpe(r: np.ndarray, rfr: float = 0.04 / 252) -> float:
    std = r.std(ddof=1)
    return float((r.mean() - rfr) / std * np.sqrt(252)) if std > 0 else 0.0

def _sortino(r: np.ndarray, rfr: float = 0.04 / 252) -> float:
    down = r[r < rfr]
    std_dn = down.std(ddof=1) if len(down) > 1 else 0.0
    return float((r.mean() - rfr) / std_dn * np.sqrt(252)) if std_dn > 0 else 0.0

def _calmar(r: np.ndarray) -> float:
    eq      = np.cumprod(1 + r)
    peak    = np.maximum.accumulate(eq)
    max_dd  = abs(((eq - peak) / peak).min())
    ann_ret = eq[-1] ** (252 / len(r)) - 1
    return float(ann_ret / max_dd) if max_dd > 0 else 0.0

def _profit_factor(r: np.ndarray) -> float:
    gains  = r[r > 0].sum()
    losses = abs(r[r < 0].sum())
    return float(gains / losses) if losses > 0 else float("inf")


class VectorizedBacktester:
    """
    Production-quality vectorised backtester with full performance metrics.

    Signals must be pre-shifted (no look-ahead bias).
    Supports transaction costs, position sizing, and walk-forward validation.

    Example
    -------
    >>> df["signal"] = np.sign(df["close"].pct_change(5))  # 5-bar momentum
    >>> result = VectorizedBacktester.backtest(df, spread_bps=2.0)
    >>> print(result["sharpe"], result["max_drawdown_pct"])
    1.34  -12.5
    """

    @staticmethod
    def backtest(
        df: pd.DataFrame,
        signal_col: str = "signal",
        spread_bps: float = 2.0,
        slippage_bps: float = 1.0,
        initial_capital: float = 10_000.0,
        position_size: float = 1.0,
        risk_free_annual: float = 0.04,
    ) -> dict:
        """
        Vectorised backtest engine.

        Parameters
        ----------
        df              : OHLCV DataFrame with a signal column
        signal_col      : column name for signal (1=long, -1=short, 0=flat)
        spread_bps      : round-trip spread cost in basis points
        slippage_bps    : round-trip slippage estimate in basis points
        initial_capital : starting capital
        position_size   : fraction of capital at risk (1.0 = fully invested)
        risk_free_annual: used for Sharpe / Sortino calculations

        Returns
        -------
        Comprehensive dict including Sharpe, Sortino, Calmar, max DD,
        win rate, profit factor, equity curve, and full annotated DataFrame.

        Notes
        -----
        ALL signals are shifted by 1 bar to prevent look-ahead.
        Costs are charged on position changes (not every bar).
        """
        if signal_col not in df.columns:
            raise KeyError(f"Column '{signal_col}' not found in DataFrame")
        if len(df) < 10:
            raise ValueError("Need at least 10 bars to backtest")

        out = df.copy()
        cost_rt = (spread_bps + slippage_bps) / 10_000.0   # Round-trip cost fraction

        out["returns"]      = out["close"].pct_change()
        out["position"]     = out[signal_col].shift(1).fillna(0) * position_size
        out["trade"]        = out["position"].diff().abs().fillna(0)
        out["gross_return"] = out["position"] * out["returns"]
        out["cost"]         = out["trade"] * cost_rt
        out["net_return"]   = out["gross_return"] - out["cost"]

        # Equity curve (multiplicative)
        out["equity"]       = initial_capital * (1 + out["net_return"]).cumprod()
        out["peak"]         = out["equity"].cummax()
        out["drawdown"]     = (out["equity"] - out["peak"]) / out["peak"]

        # Trade-level stats
        out["trade_entry"]  = (out["position"] != 0) & (out["position"].shift(1) == 0)
        out["trade_exit"]   = (out["position"] == 0) & (out["position"].shift(1) != 0)
        n_trades            = int(out["trade_entry"].sum())

        net     = out["net_return"].dropna().values
        rfr_d   = risk_free_annual / 252

        # Compute comprehensive metrics from net returns
        total_return = float((out["equity"].iloc[-1] / initial_capital - 1) * 100)
        max_dd       = float(out["drawdown"].min() * 100)
        sharpe       = _sharpe(net, rfr_d)
        sortino      = _sortino(net, rfr_d)
        calmar       = _calmar(net)
        pf           = _profit_factor(net)

        # Win rate on closed trades (more accurate than bar-level)
        exit_returns = out.loc[out["trade_exit"], "net_return"]
        win_rate     = float((exit_returns > 0).mean() * 100) if len(exit_returns) > 0 else 0.0

        # Consecutive loss streak
        signs = np.sign(net)
        streak = 0
        max_streak = 0
        for s in signs:
            if s < 0:
                streak += 1
                max_streak = max(max_streak, streak)
            else:
                streak = 0

        return {
            "total_return_pct":   round(total_return, 2),
            "ann_return_pct":     round(float((out["equity"].iloc[-1] / initial_capital)
                                              ** (252 / max(len(net), 1)) - 1) * 100, 2),
            "sharpe":             round(sharpe, 3),
            "sortino":            round(sortino, 3),
            "calmar":             round(calmar, 3),
            "max_drawdown_pct":   round(max_dd, 2),
            "profit_factor":      round(pf, 3),
            "n_trades":           n_trades,
            "win_rate":           round(win_rate, 1),
            "max_consec_losses":  int(max_streak),
            "total_costs_pct":    round(float(out["cost"].sum() * 100), 2),
            "equity_curve":       out["equity"],
            "drawdown_series":    out["drawdown"],
            "df":                 out,
        }

    @staticmethod
    def walk_forward_backtest(
        df: pd.DataFrame,
        signal_fn: Callable,
        optimize_fn: Callable,
        train_bars: int = 500,
        test_bars: int = 100,
        min_folds: int = 3,
        **kwargs,
    ) -> dict:
        """
        Anchored walk-forward validation.

        Parameters
        ----------
        signal_fn   : callable(df, params) → signal Series
        optimize_fn : callable(train_df) → params dict
        train_bars  : in-sample training window
        test_bars   : out-of-sample test window per fold
        min_folds   : minimum folds required for a valid result

        Returns
        -------
        dict with per-fold and aggregate OOS statistics.
        """
        results:    list[dict] = []
        all_equity: list[pd.Series] = []

        for start in range(0, len(df) - train_bars - test_bars, test_bars):
            train = df.iloc[start: start + train_bars]
            test  = df.iloc[start + train_bars: start + train_bars + test_bars].copy()

            try:
                params          = optimize_fn(train)
                test["signal"]  = signal_fn(test, params)
                bt              = VectorizedBacktester.backtest(test, **kwargs)
                results.append({
                    "fold":    len(results),
                    "sharpe":  bt["sharpe"],
                    "sortino": bt["sortino"],
                    "return":  bt["total_return_pct"],
                    "max_dd":  bt["max_drawdown_pct"],
                    "params":  params,
                })
                all_equity.append(bt["equity_curve"])
            except Exception as e:
                results.append({"fold": len(results), "error": str(e)})

        valid = [r for r in results if "sharpe" in r]
        if len(valid) < min_folds:
            return {
                "method":  "walk_forward",
                "error":   f"Only {len(valid)} valid folds (need {min_folds})",
                "n_folds": len(results),
            }

        sharpes  = [r["sharpe"]  for r in valid]
        sortinos = [r["sortino"] for r in valid]
        returns  = [r["return"]  for r in valid]

        # Concatenate OOS equity curves for a continuous equity line
        oos_equity = pd.concat(all_equity).sort_index() if all_equity else pd.Series(dtype=float)

        return {
            "method":            "walk_forward",
            "n_folds":           len(results),
            "n_valid_folds":     len(valid),
            "avg_sharpe":        round(float(np.mean(sharpes)), 3),
            "median_sharpe":     round(float(np.median(sharpes)), 3),
            "std_sharpe":        round(float(np.std(sharpes, ddof=1)), 3),
            "pct_folds_positive": round(float(np.mean([r > 0 for r in returns])) * 100, 1),
            "avg_return":        round(float(np.mean(returns)), 2),
            "avg_sortino":       round(float(np.mean(sortinos)), 3),
            "fold_results":      valid,
            "oos_equity":        oos_equity,
            "WARNING":           "Past performance ≠ future results. OOS validation required.",
        }

    @staticmethod
    def compare_strategies(
        df: pd.DataFrame,
        strategies: dict[str, pd.Series],
        spread_bps: float = 2.0,
        slippage_bps: float = 1.0,
        initial_capital: float = 10_000.0,
    ) -> pd.DataFrame:
        """
        Run multiple strategies on the same data and compare side-by-side.

        Parameters
        ----------
        strategies : {"name": signal_series, ...}

        Returns
        -------
        DataFrame ranked by Sharpe ratio.
        """
        results: list[dict] = []
        for name, signal_series in strategies.items():
            df_copy = df.copy()
            df_copy["signal"] = signal_series
            try:
                bt = VectorizedBacktester.backtest(
                    df_copy,
                    spread_bps=spread_bps,
                    slippage_bps=slippage_bps,
                    initial_capital=initial_capital,
                )
                results.append({
                    "strategy":     name,
                    "return":       bt["total_return_pct"],
                    "sharpe":       bt["sharpe"],
                    "sortino":      bt["sortino"],
                    "calmar":       bt["calmar"],
                    "max_dd":       bt["max_drawdown_pct"],
                    "n_trades":     bt["n_trades"],
                    "win_rate":     bt["win_rate"],
                    "profit_factor": bt["profit_factor"],
                })
            except Exception as e:
                results.append({"strategy": name, "error": str(e)})

        return pd.DataFrame(results).sort_values("sharpe", ascending=False)

Read the full file on GitHub · 1,157 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 7d ago First seen · 1,157 lines · 118 tokens per session scan A 56d141840f6c

Subscribe to this mod's changes

backtesting-sim is a skill published in the GitHub repository mahmoud20138/Tradecraft (15 stars, last pushed 4mo ago), licensed MIT. It adds 118 tokens to every session and 11,356 once invoked, about $0.0006 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.

Related

Other skills, from other repositories

strategy-generate

Create, modify, and optimize quantitative trading strategies, then backtest and evaluate them.

HKUDS/Vibe-Trading · 21 tokens

portfolio-certification

Orchestrate an out-of-sample certification of a NexusTrade trading strategy or live book — the master discipline behind the Public Portfolio Challenge. Use whenever you must decide PASS/FAIL on whether a portfolio holds up out of sample before deploying real money, replaying the Episode 10 runbooks, or running a…

austin-starks/Public-Portfolio-Challenge · 127 tokens

lockbox-holdout

The single-touch lockbox — a final anti-overfitting holdout run once, after design freeze, on a window held out from every fold, sweep, and search. Use when finalizing a bakeoff winner before deploy, setting up the A/B/C baselines as OOS bars, or running the S1.5 gate-coherence auto-relax. Covers why looking at the…

austin-starks/Public-Portfolio-Challenge · 115 tokens

pnl-explain

Explains profit and loss composition and attribution for paper accounts using engine-calculated breakdowns.

YicunAI/Pnlclaw-community · 22 tokens

tushare

A Python interface for Tushare, a financial data service that provides market and company information for stocks, funds, futures, and digital assets. It returns queried data as pandas tables.

HKUDS/Vibe-Trading · 79 tokens

social-media-intelligence

Social media intelligence: financial signal extraction from Twitter/X, Telegram, Discord, and Reddit for sentiment-driven trading strategies.

HKUDS/Vibe-Trading · 28 tokens