src/helpers/getEffectivePriceOpen.ts — cost-basis replay algorithm (DO NOT MODIFY)src/helpers/toProfitLossDto.ts — weighted PNL with partial close replay (DO NOT MODIFY)test/spec/dca.test.mjs — 35 unit tests for DCA+partial logictest/e2e/dca.test.mjs — 9 e2e tests: partial profit/loss interleaved with DCAtest/migration/migrate7.test.mjs — 2 migration tests: trailing stop breakeven, partialLosstest/e2e/average.test.mjs — reference pattern for e2e backtest teststest/README.md — comprehensive test writing guide (read before writing e2e tests)Running costBasis through all partials sequentially:
costBasis = 0
for each partial[i]:
newEntries = entryCountAtClose[i] - entryCountAtClose[i-1] (0 for i=0)
costBasis += newEntries * 100
dollarValue = (percent[i] / 100) * costBasis ← correct running basis
costBasis *= (1 - percent[i] / 100) ← reduce after each close
weight[i] = dollarValue[i] / totalInvested
snap[0] = hm(entries[0..cnt[0]]) when no prior partialssnap[i≥1] = must use getEff(entries[0..cnt[i]], partials[0..i-1]) — NOT plain harmonic mean
(remainingCostBasis + newDCA*100) / (remainingCostBasis/snap[i-1] + Σ100/newPrice)addExchangeSchema({ exchangeName: "binance-X", getCandles, formatPrice, formatQuantity })
addStrategySchema({ strategyName: "test-X", interval: "1m", getSignal, callbacks: { onActivePing, onClose, ... } })
addFrameSchema({ frameName: "Nm-X", interval: "1m", startDate, endDate })
const awaitSubject = new Subject();
listenDoneBacktest(() => awaitSubject.next());
const unsubscribeError = listenError((error) => { errorCaught = error; awaitSubject.next(); });
Backtest.background("BTCUSDT", { strategyName, exchangeName, frameName });
await awaitSubject.toPromise();
unsubscribeError();
getSignal call (signalGenerated flag); getCandles returns from allCandles arraycandle.low <= priceOpen; SHORT when candle.high >= priceOpengetAveragePrice(symbol) needs min 5 candlesCC_MAX_STOPLOSS_DISTANCE_PERCENT: 20 — SL can be at most 20% from entry; violations silently break testsCC_AVG_PRICE_CANDLES_COUNT: 5 — VWAP window; first 4 candles skipped as buffer in pending processingminuteEstimatedTime must fit within frame; if candles run out before time expires → error thrownonPartialProfit / onPartialLoss fire based on VWAP (averagePrice), not candle.closerevenuePercent in onPartialProfit = % progress toward TP (0–100), NOT P&L %revenuePercent in onPartialLoss = % progress toward SL (0–100)commitPartialProfit(symbol, percentToClose) — close X% at profit; requires currentPrice > effectivePriceOpen for LONGcommitPartialLoss(symbol, percentToClose) — close X% at loss; requires currentPrice < effectivePriceOpen for LONGcommitAverageBuy(symbol) — DCA entry (rejected if price unfavorable direction)commitTrailingStop(symbol, percentShift, currentPrice) — shift SL by percent from original distanceonActivePing, onPartialProfit, onPartialLoss) with awaitBacktest.getPendingSignal(symbol, context) — returns current signal state including priceOpen, priceStopLoss_entry → returns signal.priceOpen immediately (line 23)test/index.mjs — import list controls which test files run