The Backtest Engine
for Prediction Markets

Test trading strategies against historical Polymarket data with real orderbook simulation, slippage modeling, and pay-per-use pricing. One endpoint — full results.

v1.0.0 REST + JSON API Key Auth Pay-per-use

Authentication

All backtest requests are authenticated with an API key passed in the X-API-Key header.

🗝
API keys are prefixed with pcap_ and look like pcap_k9m3xR7vL2pQ8wN4jF6....
Create and manage keys from your dashboard. The plaintext key is shown only once at creation time.
header
X-API-Key: pcap_k9m3xR7vL2pQ8wN4jF6hT1bY...

Quickstart

One request is all you need. Here's a complete example in three languages.

bash
curl -X POST https://api.polycop.polybacktest.com/v1/backtest \
  -H "X-API-Key: pcap_k9m3xR7vL2pQ8wN4..." \
  -H "Content-Type: application/json" \
  -d '{
    "coin": "btc",
    "num_markets": 100,
    "market_type": "15m",
    "direction": "buy_down",
    "order_type": "limit",
    "limit_price": 0.85,
    "duration_seconds": 120,
    "min_share_price": 0.70,
    "max_share_price": 0.95,
    "take_profit_pct": 15,
    "stop_loss_pct": 50
  }'
python
import requests

response = requests.post(
    "https://api.polycop.polybacktest.com/v1/backtest",
    headers={"X-API-Key": "pcap_k9m3xR7vL2pQ8wN4..."},
    json={
        "coin": "btc",
        "num_markets": 100,
        "market_type": "15m",
        "direction": "buy_down",
        "order_type": "limit",
        "limit_price": 0.85,
        "duration_seconds": 120,
        "min_share_price": 0.70,
        "max_share_price": 0.95,
        "take_profit_pct": 15,
        "stop_loss_pct": 50,
    },
)

data = response.json()
summary = data["summary"]

print(f"PnL:      ${summary['pnl_absolute']:.2f} ({summary['pnl_pct']:.1f}%)")
print(f"Win Rate: {summary['win_rate_pct']:.1f}%")
print(f"Trades:   {summary['total_trades']}")
print(f"Markets:  {summary['markets_tested']} tested, {summary['markets_won']} won")
print(f"Drawdown: {summary['max_drawdown_pct']:.1f}% (${summary['drawdown']:.2f})")
print(f"Cost:     ${summary['cost_charged']}")
javascript
const res = await fetch("https://api.polycop.polybacktest.com/v1/backtest", {
  method: "POST",
  headers: {
    "X-API-Key": "pcap_k9m3xR7vL2pQ8wN4...",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    coin: "btc",
    num_markets: 100,
    market_type: "15m",
    direction: "buy_down",
    order_type: "limit",
    limit_price: 0.85,
    duration_seconds: 120,
    min_share_price: 0.70,
    max_share_price: 0.95,
    take_profit_pct: 15,
    stop_loss_pct: 50,
  }),
});

const { summary, trades, equity_curve } = await res.json();
console.log(`PnL: $${summary.pnl_absolute.toFixed(2)} (${summary.pnl_pct.toFixed(1)}%)`);
console.log(`Win Rate: ${summary.win_rate_pct.toFixed(1)}%`);
console.log(`Trades: ${summary.total_trades} across ${summary.markets_tested} markets`);

Endpoint

POST /v1/backtest

Run a trading strategy against historical Polymarket data. Simulates entries and exits using real orderbook snapshots with VWAP fill pricing, slippage checks, and configurable take-profit / stop-loss.

Cost is deducted from your wallet atomically on success. If your balance is insufficient, you'll get a 402 error and nothing is charged.

Request Parameters

Send as JSON body. All parameters have sensible defaults except limit_price and duration_seconds which are required when using limit orders.

Scope & Market Selection

ParameterTypeDescription
coinstringOPTIONAL"btc", "eth", or "sol". Default: "btc"
num_marketsintegerOPTIONALNumber of markets: 10, 100, or 1000. Default: 10
market_typestringOPTIONALMarket duration: "5m" "15m" "1hr" "4hr" "24hr". Default: "15m"
tagstringOPTIONALCustom label for this run (e.g. "aggressive-v2")

Entry Conditions

ParameterTypeDescription
directionstringOPTIONAL"buy_down" (bet No wins) or "buy_up" (bet Yes wins). Default: "buy_down"
trigger_startstringOPTIONALWindow start offset from market open (MM:SS). Default: "13:00"
trigger_endstringOPTIONALWindow end offset (MM:SS). Default: "15:00"
min_share_pricefloatOPTIONALMin token price filter (0–1). Default: 0.7
max_share_pricefloatOPTIONALMax token price filter (0–1). Default: 0.95
min_coin_changefloatOPTIONALMin coin price change from market open (USD). Default: 50.0
max_coin_changefloatOPTIONALMax coin price change (USD). Default: 9999.0
min_btc_changefloatOPTIONALLegacy alias for min_coin_change
max_btc_changefloatOPTIONALLegacy alias for max_coin_change

Order Configuration

ParameterTypeDescription
order_typestringOPTIONAL"market" or "limit". Default: "limit"
limit_pricefloatREQUIRED*Max entry price (0–1). Required when order_type="limit"
duration_secondsintegerREQUIRED*How long the limit order stays open (1–86400s). Required for limit orders
single_trade_buy_amountfloatOPTIONALUSD per trade round. Default: 5.0
number_of_roundsintegerOPTIONALMax trade rounds (1–100,000). Default: 10000
max_slippage_pctfloatOPTIONALReject entry if slippage exceeds this % (0–100)

Exit Conditions

ParameterTypeDescription
take_profit_pctfloatOPTIONALExit when profit reaches this % (e.g. 15 = +15%)
stop_loss_pctfloatOPTIONALExit when loss reaches this % (e.g. 50 = -50%)
take_profitfloatOPTIONALAbsolute take-profit price (0–1). Exit when token price ≥ this value. Cannot combine with take_profit_pct
stop_lossfloatOPTIONALAbsolute stop-loss price (0–1). Exit when token price ≤ this value. Cannot combine with stop_loss_pct
ℹ️
If neither take-profit nor stop-loss triggers during the market's lifetime, the position is resolved at the market outcome (1.0 if your prediction was correct, 0.0 otherwise).

Response

A successful response contains four top-level fields:

json — 200 OK
{
  "settings": {
    "tag": "aggressive-dip-buy",
    "trigger_start": "13:00",
    "trigger_end": "15:00",
    "direction": "buy_down",
    "order_type": "limit",
    "limit_price": 0.85,
    "duration_seconds": 120,
    "min_share_price": 0.7,
    "max_share_price": 0.95,
    "min_coin_change": 50.0,
    "max_coin_change": 9999.0,
    "single_trade_buy_amount": 10.0,
    "number_of_rounds": 10000,
    "take_profit_pct": 15,
    "stop_loss_pct": 50,
    "take_profit": null,
    "stop_loss": null,
    "max_slippage_pct": null,
    "coin": "btc",
    "num_markets": 100,
    "market_type": "15m"
  },

  "summary": {
    "total_trades": 186,
    "total_volume": 1860.00,
    "total_capital_used": 930.00,
    "win_rate_pct": 62.4,
    "pnl_absolute": 148.32,
    "pnl_pct": 15.94,
    "avg_buy_price": 0.812,
    "avg_sell_price": 0.871,
    "max_drawdown_pct": 8.3,
    "drawdown": -0.83,
    "fees_impact": 0.0,
    "markets_tested": 97,
    "markets_won": 58,
    "markets_lost": 39,
    "trigger_rate_abs": 93,
    "trigger_rate_pct": 95.88,
    "max_consecutive_losses": 4,
    "cost_charged": 0.05
  },

  "equity_curve": [
    { "timestamp": "2025-01-10T12:13:00Z", "portfolio_value": 10000.00 },
    { "timestamp": "2025-01-10T12:13:45Z", "portfolio_value": 10012.50 },
    // ... one point per trade event
  ],

  "trades": [
    {
      "timestamp": "2025-01-10T12:13:22Z",
      "market": "will-btc-go-above-95000",
      "side": "buy",
      "price": 0.82,
      "size": 12.19,
      "portfolio_value": 10000.00,
      "prediction": "down",
      "coin_price": 94852.31
    },
    {
      "timestamp": "2025-01-10T12:28:00Z",
      "market": "will-btc-go-above-95000",
      "side": "sell",
      "price": 1.0,
      "size": 12.19,
      "portfolio_value": 10002.19,
      "prediction": "down",
      "coin_price": 95017.44
    }
    // ... all trades (buy/sell pairs)
  ]
}

Summary Fields

FieldTypeDescription
total_tradesintTotal buy + sell trades executed
total_volumefloatTotal USD volume traded
total_capital_usedfloatTotal capital deployed (buys only)
win_rate_pctfloatPercentage of winning trades
pnl_absolutefloatNet profit/loss in USD
pnl_pctfloatNet profit/loss as percentage of capital
avg_buy_pricefloatAverage entry price across all buys
avg_sell_pricefloatAverage exit price across all sells
max_drawdown_pctfloatMaximum peak-to-trough portfolio decline (%)
drawdownfloatMaximum peak-to-trough portfolio decline in USD (negative value)
markets_testedintNumber of markets with data (may be < num_markets)
markets_wonintMarkets where strategy was profitable
markets_lostintMarkets where strategy lost money
trigger_rate_absintMarkets where at least one entry triggered
trigger_rate_pctfloatTrigger rate as percentage of markets tested
max_consecutive_lossesintLongest losing streak
cost_chargedfloatUSD deducted from your wallet for this run

Trade Fields

Each entry in the trades array represents a single buy or sell execution.

FieldTypeDescription
timestampstringISO-8601 timestamp of the snapshot at which the trade executed
marketstringMarket ID / slug the trade was placed in
sidestring"buy" (entry) or "sell" (exit)
pricefloatToken fill price (0–1). For buys with orderbook data, this is the VWAP fill price including slippage.
sizefloatTrade size in USD
portfolio_valuefloatSimulated portfolio value after the trade
predictionstring"down" or "up" — which side of the market was taken
coin_pricefloatUnderlying coin price (USD) at the moment the trade was triggered. For a buy, this is the coin price at entry; for the matching sell, the coin price at exit.

Error Codes

All errors return JSON with a detail field.

json — error response
{
  "detail": "Insufficient wallet balance. Required: $0.0500, Available: $0.0100. Please top up your wallet."
}
StatusMeaningWhat to do
200SuccessBacktest completed. Parse the response.
401UnauthorizedCheck your X-API-Key header. Key may be missing, invalid, or revoked.
402Payment RequiredWallet balance too low. Top up via the dashboard.
403ForbiddenAccount is disabled. Contact support.
422Validation ErrorInvalid params. Check market_type, limit_price, direction, etc.
429Rate LimitedToo many requests. Back off and retry.
500Server ErrorInternal failure. The detail field has specifics.

Strategy Tips

💡
Direction explained
buy_down — You're buying the "No" token, betting the market resolves downward. The entry price filter (min/max_share_price) applies to price_down.
buy_up — You're buying the "Yes" token. Price filter applies to price_up.
Trigger window
trigger_start / trigger_end define when the strategy looks for entries, as an offset from market open. For a 15-minute market, "13:00" to "15:00" means "look for entries in the last 2 minutes before close." Narrow windows reduce noise; wider windows catch more opportunities.
💰
Limit vs Market orders
Limit: Only enters if the VWAP fill price is ≤ limit_price. The order stays open for duration_seconds. Lower limit_price = better entries but fewer fills.
Market: Enters at whatever price the orderbook gives. No limit_price needed. Use max_slippage_pct to cap how much slippage you'll accept.
🛡
Risk management
Without TP/SL, positions are held until market resolution (binary outcome: 0 or 1). Adding TP/SL lets you exit mid-market based on price movement in the orderbook snapshots. Use the _pct variants for percentage-based exits relative to entry price, or the absolute variants (take_profit / stop_loss) for fixed price targets. You cannot combine both for the same exit type.

Example Strategies

json — conservative buy-down
// Tight price filter, low slippage, wide trigger window
{
  "direction": "buy_down",
  "order_type": "limit",
  "limit_price": 0.80,
  "duration_seconds": 60,
  "min_share_price": 0.70,
  "max_share_price": 0.85,
  "max_slippage_pct": 5,
  "take_profit_pct": 10,
  "stop_loss_pct": 30,
  "num_markets": 1000,
  "market_type": "15m"
}
json — aggressive dip buy
// Wide price range, high coin volatility filter, no stop loss
{
  "direction": "buy_down",
  "order_type": "market",
  "min_share_price": 0.55,
  "max_share_price": 0.95,
  "min_coin_change": 200,
  "single_trade_buy_amount": 25,
  "take_profit_pct": 25,
  "num_markets": 1000,
  "market_type": "1hr"
}
json — quick scalp on 5m markets
// Short markets, tight TP/SL, fast trigger window
{
  "direction": "buy_up",
  "order_type": "limit",
  "limit_price": 0.60,
  "duration_seconds": 30,
  "trigger_start": "03:00",
  "trigger_end": "04:30",
  "min_share_price": 0.45,
  "max_share_price": 0.65,
  "take_profit_pct": 8,
  "stop_loss_pct": 15,
  "num_markets": 100,
  "market_type": "5m"
}

Full Examples

Complete, copy-paste-ready examples showing the exact request and what you get back. Replace the API key with your own pcap_* key.

1. Minimal Request (defaults + market order)

The simplest possible call. Uses all defaults: 10 markets, 15m, buy_down, market order. No limit price needed.

curl — request
curl -X POST https://api.polycop.polybacktest.com/v1/backtest \
  -H "X-API-Key: pcap_k9m3xR7vL2pQ8wN4jF6hT1bY9cD5eA3" \
  -H "Content-Type: application/json" \
  -d '{
    "order_type": "market",
    "num_markets": 10
  }'
json — response
{
  "settings": {
    "tag": null,
    "trigger_start": "13:00",
    "trigger_end": "15:00",
    "min_share_price": 0.7,
    "max_share_price": 0.95,
    "min_coin_change": 50.0,
    "max_coin_change": 9999.0,
    "direction": "buy_down",
    "order_type": "market",
    "limit_price": null,
    "duration_seconds": null,
    "single_trade_buy_amount": 5.0,
    "number_of_rounds": 10000,
    "take_profit_pct": null,
    "stop_loss_pct": null,
    "take_profit": null,
    "stop_loss": null,
    "max_slippage_pct": null,
    "coin": "btc",
    "num_markets": 10,
    "market_type": "15m"
  },
  "summary": {
    "total_trades": 14,
    "total_volume": 70.00,
    "total_capital_used": 35.00,
    "win_rate_pct": 57.1,
    "pnl_absolute": 3.42,
    "pnl_pct": 9.77,
    "avg_buy_price": 0.826,
    "avg_sell_price": 0.875,
    "max_drawdown_pct": 5.1,
    "drawdown": -0.51,
    "fees_impact": 0.0,
    "markets_tested": 10,
    "markets_won": 4,
    "markets_lost": 3,
    "trigger_rate_abs": 7,
    "trigger_rate_pct": 70.0,
    "max_consecutive_losses": 2,
    "cost_charged": 0.015
  },
  "equity_curve": [
    { "timestamp": "2025-02-01T14:13:10Z", "portfolio_value": 5000.00 },
    { "timestamp": "2025-02-01T14:13:10Z", "portfolio_value": 4995.00 },
    // ...
  ],
  "trades": [
    { "timestamp": "2025-02-01T14:13:10Z", "market": "will-btc-hit-98500-2", "side": "buy", "price": 0.83, "size": 6.02, "portfolio_value": 5000.00, "prediction": "down", "coin_price": 98412.55 },
    { "timestamp": "2025-02-01T14:15:00Z", "market": "will-btc-hit-98500-2", "side": "sell", "price": 1.0, "size": 6.02, "portfolio_value": 5001.02, "prediction": "down", "coin_price": 98587.10 },
    // ... more trades
  ]
}

2. Limit Order with Take-Profit & Stop-Loss

A complete limit order setup on 100 markets. Only enters if fill price ≤ $0.85, order stays open for 2 minutes. Exits at +15% profit or -50% loss.

curl — request
curl -X POST https://api.polycop.polybacktest.com/v1/backtest \
  -H "X-API-Key: pcap_k9m3xR7vL2pQ8wN4jF6hT1bY9cD5eA3" \
  -H "Content-Type: application/json" \
  -d '{
    "coin": "btc",
    "num_markets": 100,
    "market_type": "15m",
    "direction": "buy_down",
    "order_type": "limit",
    "limit_price": 0.85,
    "duration_seconds": 120,
    "min_share_price": 0.70,
    "max_share_price": 0.95,
    "min_coin_change": 50,
    "max_coin_change": 9999,
    "single_trade_buy_amount": 10,
    "number_of_rounds": 10000,
    "take_profit_pct": 15,
    "stop_loss_pct": 50,
    "tag": "limit-with-tp-sl"
  }'
json — response
{
  "settings": {
    "tag": "limit-with-tp-sl",
    "trigger_start": "13:00",
    "trigger_end": "15:00",
    "min_share_price": 0.7,
    "max_share_price": 0.95,
    "min_coin_change": 50.0,
    "max_coin_change": 9999.0,
    "direction": "buy_down",
    "order_type": "limit",
    "limit_price": 0.85,
    "duration_seconds": 120,
    "single_trade_buy_amount": 10.0,
    "number_of_rounds": 10000,
    "take_profit_pct": 15.0,
    "stop_loss_pct": 50.0,
    "take_profit": null,
    "stop_loss": null,
    "max_slippage_pct": null,
    "coin": "btc",
    "num_markets": 100,
    "market_type": "15m"
  },
  "summary": {
    "total_trades": 186,
    "total_volume": 1860.00,
    "total_capital_used": 930.00,
    "win_rate_pct": 62.4,
    "pnl_absolute": 148.32,
    "pnl_pct": 15.94,
    "avg_buy_price": 0.812,
    "avg_sell_price": 0.871,
    "max_drawdown_pct": 8.3,
    "drawdown": -0.83,
    "fees_impact": 0.0,
    "markets_tested": 97,
    "markets_won": 58,
    "markets_lost": 39,
    "trigger_rate_abs": 93,
    "trigger_rate_pct": 95.88,
    "max_consecutive_losses": 4,
    "cost_charged": 0.05
  },
  "equity_curve": [
    { "timestamp": "2025-01-10T12:13:00Z", "portfolio_value": 10000.00 },
    { "timestamp": "2025-01-10T12:13:45Z", "portfolio_value": 10012.50 },
    { "timestamp": "2025-01-10T12:14:22Z", "portfolio_value": 10008.10 },
    // ... one point per trade event
  ],
  "trades": [
    { "timestamp": "2025-01-10T12:13:22Z", "market": "will-btc-go-above-95000", "side": "buy", "price": 0.82, "size": 12.19, "portfolio_value": 10000.00, "prediction": "down", "coin_price": 94852.31 },
    { "timestamp": "2025-01-10T12:14:01Z", "market": "will-btc-go-above-95000", "side": "sell", "price": 0.94, "size": 12.19, "portfolio_value": 10012.50, "prediction": "down", "coin_price": 94978.12 },
    // ... all buy/sell pairs
  ]
}

3. Full Python Script

A complete, runnable Python script that calls the API and prints a formatted summary.

python
import requests
import json

API_KEY = "pcap_k9m3xR7vL2pQ8wN4jF6hT1bY9cD5eA3"  # replace with your key
URL     = "https://api.polycop.polybacktest.com/v1/backtest"

payload = {
    "coin": "btc",
    "num_markets": 100,
    "market_type": "15m",
    "direction": "buy_down",

    # Limit order: only buy at 0.85 or less, keep order open 2 min
    "order_type": "limit",
    "limit_price": 0.85,
    "duration_seconds": 120,

    # Only enter if token price is between 0.70 and 0.95
    "min_share_price": 0.70,
    "max_share_price": 0.95,

    # Only enter if coin price moved at least $50 from market open
    "min_coin_change": 50,

    # $10 per trade, exit at +15% or -50%
    "single_trade_buy_amount": 10,
    "take_profit_pct": 15,
    "stop_loss_pct": 50,

    "tag": "my-test-run",
}

response = requests.post(URL, headers={"X-API-Key": API_KEY}, json=payload)

if response.status_code != 200:
    print(f"Error {response.status_code}: {response.json()['detail']}")
    exit(1)

data = response.json()
s = data["summary"]

print("=== Backtest Results ===")
print(f"Markets tested:  {s['markets_tested']}")
print(f"Total trades:    {s['total_trades']}")
print(f"Win rate:        {s['win_rate_pct']:.1f}%")
print(f"PnL:             ${s['pnl_absolute']:.2f} ({s['pnl_pct']:.1f}%)")
print(f"Max drawdown:    {s['max_drawdown_pct']:.1f}% (${s['drawdown']:.2f})")
print(f"Avg buy price:   {s['avg_buy_price']:.3f}")
print(f"Avg sell price:  {s['avg_sell_price']:.3f}")
print(f"Markets won:     {s['markets_won']} / {s['markets_tested']}")
print(f"Trigger rate:    {s['trigger_rate_pct']:.1f}%")
print(f"Longest streak:  {s['max_consecutive_losses']} consecutive losses")
print(f"Cost charged:    ${s['cost_charged']}")
print(f"\nFirst 5 trades:")
for t in data["trades"][:5]:
    print(f"  {t['side']:4s} {t['market']:40s} @ {t['price']:.3f}  (${t['size']:.2f})")

4. Full JavaScript (Node.js / Browser)

Works in Node 18+ (native fetch) or any modern browser console.

javascript
const API_KEY = "pcap_k9m3xR7vL2pQ8wN4jF6hT1bY9cD5eA3"; // replace with your key

const res = await fetch("https://api.polycop.polybacktest.com/v1/backtest", {
  method: "POST",
  headers: {
    "X-API-Key": API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    coin: "btc",
    num_markets: 100,
    market_type: "15m",
    direction: "buy_down",
    order_type: "limit",
    limit_price: 0.85,
    duration_seconds: 120,
    min_share_price: 0.70,
    max_share_price: 0.95,
    min_coin_change: 50,
    single_trade_buy_amount: 10,
    take_profit_pct: 15,
    stop_loss_pct: 50,
    tag: "js-test",
  }),
});

if (!res.ok) {
  const err = await res.json();
  console.error(`Error ${res.status}: ${err.detail}`);
} else {
  const { summary, trades, equity_curve } = await res.json();

  console.log("=== Backtest Results ===");
  console.log(`Markets tested:  ${summary.markets_tested}`);
  console.log(`Total trades:    ${summary.total_trades}`);
  console.log(`Win rate:        ${summary.win_rate_pct.toFixed(1)}%`);
  console.log(`PnL:             $${summary.pnl_absolute.toFixed(2)} (${summary.pnl_pct.toFixed(1)}%)`);
  console.log(`Max drawdown:    ${summary.max_drawdown_pct.toFixed(1)}% ($${summary.drawdown.toFixed(2)})`);
  console.log(`Cost charged:    $${summary.cost_charged}`);
  console.log(`Equity points:   ${equity_curve.length}`);
  console.log(`Trade count:     ${trades.length}`);
}

5. Market Order — Buy Up on 1hr Markets

Betting that the coin will hit the target. Market orders, no limit price needed. Uses slippage cap instead.

curl — request
curl -X POST https://api.polycop.polybacktest.com/v1/backtest \
  -H "X-API-Key: pcap_k9m3xR7vL2pQ8wN4jF6hT1bY9cD5eA3" \
  -H "Content-Type: application/json" \
  -d '{
    "coin": "btc",
    "num_markets": 1000,
    "market_type": "1hr",
    "direction": "buy_up",
    "order_type": "market",
    "min_share_price": 0.50,
    "max_share_price": 0.80,
    "min_coin_change": 100,
    "max_slippage_pct": 10,
    "single_trade_buy_amount": 20,
    "take_profit_pct": 20,
    "stop_loss_pct": 40,
    "trigger_start": "30:00",
    "trigger_end": "55:00",
    "tag": "buy-up-1hr"
  }'
json — response
{
  "settings": {
    "tag": "buy-up-1hr",
    "trigger_start": "30:00",
    "trigger_end": "55:00",
    "min_share_price": 0.5,
    "max_share_price": 0.8,
    "min_coin_change": 100.0,
    "max_coin_change": 9999.0,
    "direction": "buy_up",
    "order_type": "market",
    "limit_price": null,
    "duration_seconds": null,
    "single_trade_buy_amount": 20.0,
    "number_of_rounds": 10000,
    "take_profit_pct": 20.0,
    "stop_loss_pct": 40.0,
    "take_profit": null,
    "stop_loss": null,
    "max_slippage_pct": 10.0,
    "coin": "btc",
    "num_markets": 1000,
    "market_type": "1hr"
  },
  "summary": {
    "total_trades": 1240,
    "total_volume": 24800.00,
    "total_capital_used": 12400.00,
    "win_rate_pct": 54.8,
    "pnl_absolute": 892.16,
    "pnl_pct": 7.19,
    "avg_buy_price": 0.641,
    "avg_sell_price": 0.713,
    "max_drawdown_pct": 12.4,
    "drawdown": -2.48,
    "fees_impact": 0.0,
    "markets_tested": 948,
    "markets_won": 340,
    "markets_lost": 280,
    "trigger_rate_abs": 620,
    "trigger_rate_pct": 65.40,
    "max_consecutive_losses": 7,
    "cost_charged": 0.10
  },
  "equity_curve": [ /* ... */ ],
  "trades": [ /* ... */ ]
}

6. Handling Errors

What happens when things go wrong — and what the response looks like.

Missing limit_price on a limit order

curl — bad request
curl -X POST https://api.polycop.polybacktest.com/v1/backtest \
  -H "X-API-Key: pcap_k9m3xR7vL2pQ8wN4jF6hT1bY9cD5eA3" \
  -H "Content-Type: application/json" \
  -d '{
    "order_type": "limit"
  }'  // missing limit_price and duration_seconds!
json — 422 Validation Error
{
  "detail": [
    {
      "msg": "Value error, limit_price is required when order_type is 'limit'",
      "type": "value_error"
    }
  ]
}

Insufficient balance

json — 402 Payment Required
{
  "detail": "Insufficient wallet balance. Required: $0.0500, Available: $0.0100. Please top up your wallet."
}

Invalid API key

json — 401 Unauthorized
{
  "detail": "Invalid API key"
}

PolyCap API v1.0.0 — Built by PolyBackTest