Skip to Content

Backtesting

Use strategy() mode to simulate a trading strategy over historical data. The server executes entries and exits, then returns a performance report.

How It Works

  • Declare your script with strategy() instead of indicator()
  • Use strategy.entry() and strategy.close() (or strategy.exit()) to simulate trades
  • Use --format json to get the full performance report
  • Parse the report with jq: .data.report_json | fromjson

Strategy Settings

Common parameters for the strategy() declaration and their defaults:

ParameterDefaultDescription
initial_capital1000000Starting capital
commission_typestrategy.commission.percentCommission calculation method
commission_value0Commission rate / amount (0 = no commission)
slippage0Slippage in ticks per fill
default_qty_typestrategy.fixedHow position size is specified: strategy.fixed (contracts), strategy.percent_of_equity, strategy.cash
default_qty_value1Default position size
pyramiding0Max simultaneous entries in the same direction (0 = one at a time)
risk_free_rate2Annual risk-free rate (%) for Sharpe / Sortino

Example with custom settings:

pine
strategy("My Strategy",
    initial_capital    = 50000,
    commission_type    = strategy.commission.percent,
    commission_value   = 0.1,
    default_qty_type   = strategy.percent_of_equity,
    default_qty_value  = 10)

EMA Crossover Strategy

Buy when EMA8 crosses above EMA21; sell when it crosses below.

bash
longbridge quant run NVDA.US \
  --start 2025-01-01 --end 2026-04-28 \
  --format json \
  --script '
strategy("EMA Cross", overlay=true)
fast = ta.ema(close, 8)
slow = ta.ema(close, 21)
if ta.crossover(fast, slow)
    strategy.entry("Long", strategy.long)
if ta.crossunder(fast, slow)
    strategy.close("Long")
' | jq '.data.report_json | fromjson | .performanceAll'
json
{
  "netProfit": 4231.00,
  "netProfitPercent": 42.31,
  "grossProfit": 7850.00,
  "grossLoss": 3619.00,
  "profitFactor": 2.17,
  "buyHoldReturnPercent": 31.20,
  "maxDrawdownPercent": -28.15,
  "sharpeRatio": 0.87,
  "sortinoRatio": 1.24,
  "totalClosedTrades": 18,
  "percentProfitable": 50.0,
  "avgWinningTradePercent": 8.72,
  "avgLosingTradePercent": -4.02,
  "commissionPaid": 180.00
}

RSI Mean-Reversion Strategy

Buy when RSI drops below 30 (oversold); exit when RSI recovers above 55.

bash
longbridge quant run AAPL.US \
  --start 2025-01-01 --end 2026-04-28 \
  --format json \
  --script '
strategy("RSI Reversion", overlay=false)
r = ta.rsi(close, 14)
if ta.crossunder(r, 30)
    strategy.entry("Long", strategy.long)
if ta.crossover(r, 55)
    strategy.close("Long")
' | jq '.data.report_json | fromjson | .performanceAll'

Report Reference

Parse the full report object:

bash
longbridge quant run NVDA.US ... --format json --script '...' \
  | jq '.data.report_json | fromjson'

Top-Level Structure

FieldDescription
performanceAllPerformance metrics across all trades
performanceLongPerformance metrics for long trades only
performanceShortPerformance metrics for short trades only
closedTradesArray of completed trade records
openTradesArray of unrealized positions at backtest end
equityCurvePer-bar account equity
drawdownCurvePer-bar drawdown from equity peak
buyHoldCurvePer-bar buy-and-hold benchmark equity
configStrategy configuration snapshot

Performance Metrics

performanceAll, performanceLong, and performanceShort share the same shape. Fields marked all only are always 0 / null in performanceLong and performanceShort — they reflect the combined equity curve and cannot be split by direction.

Profit & Loss

FieldDescription
netProfitNet profit in account currency
netProfitPercentNet profit % of initial capital
grossProfitTotal profit from winning trades
grossProfitPercentGross profit %
grossLossTotal loss from losing trades (positive number)
grossLossPercentGross loss %
profitFactorGross profit ÷ gross loss
buyHoldReturnBuy-and-hold return in account currency (all only)
buyHoldReturnPercentBuy-and-hold return % (all only)

Drawdown & Runup (all only)

FieldDescription
maxDrawdownLargest equity drawdown in account currency
maxDrawdownPercentMax drawdown %
maxRunupLargest equity runup in account currency
maxRunupPercentMax runup %

Risk-Adjusted Returns (all only)

FieldDescription
sharpeRatioAnnualized Sharpe ratio
sortinoRatioAnnualized Sortino ratio

Trade Statistics

FieldDescription
totalClosedTradesCompleted trades
totalOpenTradesUnrealized positions at end of backtest
numWinningTradesTrades with profit > 0
numLosingTradesTrades with profit < 0
numEvenTradesBreak-even trades (all only)
percentProfitableWin rate (0–100)

Average Trade

FieldDescription
avgTradeAverage P&L per trade
avgTradePercentAverage P&L % per trade
avgWinningTradeAverage profit of winning trades
avgWinningTradePercentAverage winning trade profit %
avgLosingTradeAverage loss of losing trades
avgLosingTradePercentAverage losing trade loss %
ratioAvgWinLossAvg winning trade ÷ avg losing trade
largestWinningTradeSingle largest profit
largestWinningTradePercentSingle largest profit %
largestLosingTradeSingle largest loss
largestLosingTradePercentSingle largest loss %

Holding Period

FieldDescription
avgBarsInTradesAverage bars held per trade
avgBarsInWinningTradesAverage bars held for winning trades
avgBarsInLosingTradesAverage bars held for losing trades

Other

FieldDescription
commissionPaidTotal commissions paid
maxContractsHeldPeak simultaneous contracts held
marginCallsNumber of margin calls triggered

Trade History

Each entry in closedTrades is a completed round-trip:

FieldDescription
tradeNumTrade number (0-based)
entrySide"Long" or "Short"
entryIdEntry order ID
entryPriceEntry fill price
entryTimeEntry timestamp (Unix ms)
exitIdExit order ID
exitPriceExit fill price
exitTimeExit timestamp (Unix ms)
quantityContracts / shares traded
profitRealized P&L after commission
profitPercentRealized P&L % relative to entry value
cumulativeProfitRunning total P&L including this trade
cumulativeProfitPercentRunning total P&L % vs. initial capital
maxRunup / maxRunupPercentBest unrealized gain during the trade
maxDrawdown / maxDrawdownPercentWorst unrealized loss during the trade
commissionTotal commission for this trade (entry + exit)
bash
# Print a trade-by-trade summary
longbridge quant run NVDA.US --start 2025-01-01 --end 2026-04-28 \
  --format json --script '...' \
  | jq -r '.data.report_json | fromjson | .closedTrades[]
    | "#\(.tradeNum) \(.entrySide)  entry=\(.entryPrice)  exit=\(.exitPrice)  P&L=\(.profitPercent)%"'

openTrades uses the same shape minus exit fields, and profit reflects current unrealized P&L.

Equity Curves

Three parallel arrays, one value per bar (index 0 = first bar):

FieldDescription
equityCurveAccount equity at bar close
drawdownCurveDrawdown from equity peak (always ≥ 0)
buyHoldCurveHypothetical buy-and-hold equity (benchmark)
bash
# Final equity
jq '.data.report_json | fromjson | .equityCurve[-1]'

# Worst drawdown value
jq '.data.report_json | fromjson | .drawdownCurve | max'

Strategy Config

config captures the declared strategy() settings:

FieldDescription
initialCapitalStarting account equity
commissionTypePerContract, PerTrade, or PercentOfValue
commissionValueCommission amount
slippageOrder slippage in ticks
pyramidingMax simultaneous entries in the same direction
riskFreeRateAnnual risk-free rate used for Sharpe/Sortino (%)

Table Output (Quick Review)

Without --format json, the table shows each plotted series — useful for visually checking signal timing before running a full backtest:

bash
longbridge quant run NVDA.US \
  --start 2025-01-01 --end 2026-04-28 \
  --script '
strategy("EMA Cross", overlay=true)
fast = ta.ema(close, 8)
slow = ta.ema(close, 21)
plot(fast, "EMA8")
plot(slow, "EMA21")
if ta.crossover(fast, slow)
    strategy.entry("Long", strategy.long)
if ta.crossunder(fast, slow)
    strategy.close("Long")
'