Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/theonetrade/backtest-kit/llms.txt

Use this file to discover all available pages before exploring further.

All entry and exit prices in Backtest Kit are derived from the VWAP (Volume Weighted Average Price) of the last five 1-minute candles. The formula is:
typicalPrice = (high + low + close) / 3
vwap         = Σ(typicalPrice × volume) / Σ(volume)
This prevents PNL distortion from individual tick spikes and closely mirrors what a real market order would fill at in liquid markets. When multiple DCA entries accumulate, the effective entry price is the cost-weighted harmonic mean of all entries — the only mathematically correct average for fixed-dollar position sizing. Slippage (0.1%) and fees (0.1%) are applied at both open and close, giving you realistic net PNL in every report.

The three position management functions

commitAverageBuy — DCA entry

await commitAverageBuy("BTCUSDT");
Adds a new averaging entry at the current VWAP price. By default, an entry is only accepted when the current price is below the last effective entry price (averaging down, not up). This prevents the common mistake of adding to a losing position that has already reversed. The check can be disabled with setConfig({ CC_ENABLE_DCA_EVERYWHERE: true }). Each DCA entry records { price, cost, timestamp } in signal._entry. The effective price after n entries is:
effectivePrice = Σ(cost_i) / Σ(cost_i / price_i)
This is the cost-weighted harmonic mean — the same formula used for calculating cost basis in any portfolio accounting system.

commitPartialProfit — partial take-profit

await commitPartialProfit("BTCUSDT", 30);  // close 30% of position
Closes percentToClose% of the current position at the current VWAP price, recording a "profit" type partial in signal._partial. The remaining cost basis is carried forward. A costBasisAtClose snapshot is stored so the weighted PNL calculation can replay partial history without re-scanning all entries on every tick.

commitPartialLoss — partial stop-out

await commitPartialLoss("BTCUSDT", 20);   // close 20% of position
Same mechanics as commitPartialProfit but records a "loss" type partial. Used to cut exposure before the main stop-loss is hit.

Harmonic mean for effective price after partials

After each partial close, the remaining cost basis is carried into the next harmonic mean calculation. The effective price after n DCA entries and k partial closes is computed by replaying all _partial snapshots in order:
For each partial[i]:
  effectivePrice_i = costBasisAtClose[i] / Σ(cost_j / price_j) for entries[0..entryCount[i]]

After last partial:
  blendedPrice = blend remaining + any new DCA entries added after that partial
This shift in effective price after each partial also changes whether the next commitAverageBuy call will be accepted — the acceptance check uses the current effective price, not the original priceOpen.

PNL formula — toProfitLossDto

Slippage and fees are applied symmetrically to both legs:
LONG:
  priceOpenWithCosts  = priceOpen  * (1 + slippage + fee)
  priceCloseWithCosts = priceClose * (1 - slippage - fee)
  pnl% = (priceCloseWithCosts - priceOpenWithCosts) / priceOpenWithCosts * 100

SHORT:
  priceOpenWithCosts  = priceOpen  * (1 - slippage + fee)
  priceCloseWithCosts = priceClose * (1 + slippage + fee)
  pnl% = (priceOpenWithCosts - priceCloseWithCosts) / priceOpenWithCosts * 100
Default values: CC_PERCENT_SLIPPAGE = 0.1, CC_PERCENT_FEE = 0.1 (both configurable via setConfig()). For positions with partials, the total PNL is the dollar-weighted average of each segment’s PNL:
totalPnl% = Σ( weight_i × pnl%_i )

where weight_i = partialDollarValue_i / totalInvested

Worked DCA example — 4 entries, 3 partials, closed at TP = +17.89%

This example traces the full lifecycle of a LONG position: entry at 1000, four DCA attempts (one rejected), three partial closes, and a final exit at 1200.
totalInvested = $400  (4 × $100, rejected attempt not counted)

Entries
  entry#1 @ 1000  → 0.10000 coins
    commitPartialProfit(30%) @ 1150          ← Partial#1
  entry#2 @ 950   → 0.10526 coins
  entry#3 @ 880   → 0.11364 coins
    commitPartialLoss(20%)   @ 860           ← Partial#2
  entry#4 @ 920   → 0.10870 coins
    commitPartialProfit(40%) @ 1050          ← Partial#3
  entry#5 @ 980   ✗ REJECTED (980 > ep3≈929.92)
Partial #1 — commitPartialProfit @ 1150, 30%
effectivePrice = hm(1000) = 1000
costBasis      = $100
partialValue   = 30% × $100 = $30   → weight = 30/400 = 0.075
pnl            = (1150 − 1000) / 1000 × 100 = +15.00%
costBasis      → $70
coins sold: 0.03000 × 1150 = $34.50
remaining: 0.07000
DCA after Partial#1 (entries #2 and #3 accepted)
entry#2 @ 950  (950 < ep1=1000 ✓)
entry#3 @ 880  (880 < ep1=1000 ✓)
coins: 0.07000 + 0.10526 + 0.11364 = 0.28890
Partial #2 — commitPartialLoss @ 860, 20%
costBasis = 70 + 100 + 100 = $270
ep2       = 270 / 0.28890 ≈ 934.58
partialValue = 20% × $270 = $54   → weight = 54/400 = 0.135
pnl          = (860 − 934.58) / 934.58 × 100 ≈ −7.98%
costBasis    → $216
coins sold: 0.05778 × 860 = $49.69
remaining: 0.23112
DCA after Partial#2 (entry #4 accepted)
entry#4 @ 920  (920 < ep2=934.58 ✓)
coins: 0.23112 + 0.10870 = 0.33982
Partial #3 — commitPartialProfit @ 1050, 40%
costBasis = 216 + 100 = $316
ep3       = 316 / 0.33982 ≈ 929.92
partialValue = 40% × $316 = $126.4   → weight = 126.4/400 = 0.316
pnl          = (1050 − 929.92) / 929.92 × 100 ≈ +12.91%
costBasis    → $189.6
coins sold: 0.13593 × 1050 = $142.72
remaining: 0.20389
Entry #5 rejected
entry#5 @ 980  (980 > ep3≈929.92 ✗ REJECTED — averaging up not allowed)
Final close @ 1200 (TP hit)
ep_final = ep3 ≈ 929.92
remainingValue = 400 − 30 − 54 − 126.4 = $189.6
weight         = 189.6 / 400 = 0.474
pnl            = (1200 − 929.92) / 929.92 × 100 ≈ +29.04%
coins sold: 0.20389 × 1200 = $244.67
Result
0.075 × (+15.00) = +1.125
0.135 × (−7.98)  = −1.077
0.316 × (+12.91) = +4.080
0.474 × (+29.04) = +13.765
─────────────────────────────
total pnl         ≈ +17.89%

Cross-check (coins):
34.50 + 49.69 + 142.72 + 244.67 = $471.58
(471.58 − 400) / 400 × 100      = +17.90%  ✓

Report metrics

Backtest Kit auto-generates Markdown reports with the following risk-adjusted metrics calculated from all closed signals:
MetricDescription
Sharpe RatioavgPnl / stdDev — risk-adjusted return per unit of volatility
Annualized SharpesharpeRatio × √tradesPerYear
Sortino RatioavgPnl / downsideDeviation — penalizes only losing trades
Calmar RatioexpectedYearlyReturns / maxDrawdown
Recovery FactortotalPnl / maxDrawdown
Expected Yearly ReturnsBased on average trade duration and PNL
Certainty Ratio`avgWin /avgLoss`
Win RateWinning trades / total closed trades
Standard deviation uses Bessel’s correction (divides by n − 1 rather than n), which gives an unbiased estimate of the population variance from a sample. This makes the reported Sharpe ratio slightly more conservative than naive n-based implementations — which is the correct behaviour for strategy evaluation.

Build docs developers (and LLMs) love