backtest-kit 12.1.0 → 12.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -27679,9 +27679,12 @@ class HeatmapStorage {
27679
27679
  }
27680
27680
  // Per-trade Sharpe Ratio
27681
27681
  let sharpeRatio = null;
27682
+ // canComputeRatios gate is explicit here (matching the standalone Backtest/Live
27683
+ // paths) even though stdDev is already null below MIN_SIGNALS_FOR_RATIOS — relying
27684
+ // on the stdDev===null implication would couple the gate to an unrelated branch.
27682
27685
  // STDDEV_EPSILON guard — protects against float-artifact stdDev producing
27683
27686
  // spuriously astronomical sharpe on identical-returns symbols.
27684
- if (avgPnl !== null && stdDev !== null && stdDev > STDDEV_EPSILON) {
27687
+ if (canComputeRatios && avgPnl !== null && stdDev !== null && stdDev > STDDEV_EPSILON) {
27685
27688
  sharpeRatio = avgPnl / stdDev;
27686
27689
  }
27687
27690
  // Equity-curve max drawdown via compounded equity ("as-if 100% allocation per trade").
@@ -27874,17 +27877,21 @@ class HeatmapStorage {
27874
27877
  : sorted[mid];
27875
27878
  }
27876
27879
  // Expectancy — probabilities from observed win/loss counts (break-evens contribute 0).
27880
+ // Gated by MIN_SIGNALS_FOR_RATIOS (via canComputeRatios), same as the standalone
27881
+ // Backtest/Live paths and the portfolio-level pooled Expectancy below — on a tiny
27882
+ // sample the per-trade EV is too noisy to publish, and an ungated per-symbol value
27883
+ // would disagree with the symbol's own standalone report (which IS gated).
27877
27884
  let expectancy = null;
27878
- if (totalTrades > 0 && avgWin !== null && avgLoss !== null) {
27885
+ if (canComputeRatios && totalTrades > 0 && avgWin !== null && avgLoss !== null) {
27879
27886
  const winProb = winCount / totalTrades;
27880
27887
  const lossProb = lossCount / totalTrades;
27881
27888
  expectancy = winProb * avgWin + lossProb * avgLoss;
27882
27889
  }
27883
- else if (totalTrades > 0 && avgWin !== null && avgLoss === null) {
27890
+ else if (canComputeRatios && totalTrades > 0 && avgWin !== null && avgLoss === null) {
27884
27891
  // No losing trades — expectancy is just average win frequency × avgWin
27885
27892
  expectancy = (winCount / totalTrades) * avgWin;
27886
27893
  }
27887
- else if (totalTrades > 0 && avgWin === null && avgLoss !== null) {
27894
+ else if (canComputeRatios && totalTrades > 0 && avgWin === null && avgLoss !== null) {
27888
27895
  expectancy = (lossCount / totalTrades) * avgLoss;
27889
27896
  }
27890
27897
  // Average only over signals that have the value — do not dilute the mean with zeros.
@@ -28594,7 +28601,7 @@ class HeatmapStorage {
28594
28601
  `*Sortino Ratio (column): per-symbol Avg PNL / downside deviation, where downside deviation = √( Σ min(0, per-trade PNL)² / total trade count ) (canonical Sortino: MAR = 0, divide by N_total). UNITS: dimensionless. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS}, OR no losing trades, OR downside deviation ≤ 1e-9 (float-artifact guard).*`,
28595
28602
  `*Calmar Ratio (column): per-symbol Expected Yearly Returns / Max Drawdown, clamped to ±${MAX_CALMAR_RATIO}. Denominator is the mark-to-market max drawdown of that symbol's compounded equity curve. Null when Expected Yearly Returns is null (requires ≥ ${MIN_SIGNALS_FOR_ANNUALIZATION} signals and a calendar span ≥ ${MIN_CALENDAR_SPAN_DAYS} days for that symbol) OR Max Drawdown ≤ 0.*`,
28596
28603
  `*Recovery Factor (column): per-symbol (final equity − 1) × 100 / Max Drawdown, clamped to ±${MAX_CALMAR_RATIO}. The numerator is the compounded total return of that symbol's equity curve. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS}, the equity curve blew up (reached ≤ 0), or Max Drawdown ≤ 0.*`,
28597
- `*Expectancy (column): per-symbol expected value per trade. Three cases depending on what kinds of trades exist for that symbol: (a) BOTH winning and losing trades present → (winning-trade count / Total Trades) × Avg Win + (losing-trade count / Total Trades) × Avg Loss; (b) only winning trades present → (winning-trade count / Total Trades) × Avg Win (zero contribution from non-existent losses); (c) only losing trades present → (losing-trade count / Total Trades) × Avg Loss. Break-even trades contribute 0 (excluded from both probabilities). UNITS: percent per trade. NOT gated by MIN_SIGNALS computed whenever Total Trades 1 and at least one decisive trade exists. (Note: the portfolio-level Expectancy further up in this report uses a single combined formula and IS gated by MIN_SIGNALS_FOR_RATIOS over the pooled count; per-symbol Expectancy is intentionally looser to populate the row early.)*`,
28604
+ `*Expectancy (column): per-symbol expected value per trade. Three cases depending on what kinds of trades exist for that symbol: (a) BOTH winning and losing trades present → (winning-trade count / Total Trades) × Avg Win + (losing-trade count / Total Trades) × Avg Loss; (b) only winning trades present → (winning-trade count / Total Trades) × Avg Win (zero contribution from non-existent losses); (c) only losing trades present → (losing-trade count / Total Trades) × Avg Loss. Break-even trades contribute 0 (excluded from both probabilities). UNITS: percent per trade. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS} the same sample-size gate as the standalone Backtest/Live report and the portfolio-level pooled Expectancy further up, so the per-symbol value never disagrees with the symbol's own standalone report.*`,
28598
28605
  `*Max Drawdown (column): per-symbol mark-to-market max drawdown — the symbol's compounded equity curve applies each closed signal's worst intra-trade excursion (its trough-PNL snapshot, ≤ 0) before booking the realised close, so deep round-trip dips count rather than only realised close-to-close drops. UNITS: percent. NOT realised-only.*`,
28599
28606
  `*Avg Peak PNL / Avg Max Drawdown PNL (columns): per-symbol arithmetic means of each closed signal's peak-PNL / trough-PNL snapshot — the best / worst mark-to-market PNL recorded while the position was open. Signals that never recorded the snapshot are excluded — no zero dilution. UNITS: percent. NOT gated by MIN_SIGNALS — each is null only if no signal for that symbol carries the corresponding snapshot.*`,
28600
28607
  `*Peak Profit PNL / Max Drawdown PNL (columns): per-symbol MAX of the peak-PNL snapshot / MIN of the trough-PNL snapshot across the symbol's stored closed signals. UNITS: percent. The single best best-case and worst worst-case excursions for that symbol — tail behaviour the averages hide. NOT gated by MIN_SIGNALS — each is null only if no signal for that symbol carries the corresponding snapshot.*`,
package/build/index.mjs CHANGED
@@ -27659,9 +27659,12 @@ class HeatmapStorage {
27659
27659
  }
27660
27660
  // Per-trade Sharpe Ratio
27661
27661
  let sharpeRatio = null;
27662
+ // canComputeRatios gate is explicit here (matching the standalone Backtest/Live
27663
+ // paths) even though stdDev is already null below MIN_SIGNALS_FOR_RATIOS — relying
27664
+ // on the stdDev===null implication would couple the gate to an unrelated branch.
27662
27665
  // STDDEV_EPSILON guard — protects against float-artifact stdDev producing
27663
27666
  // spuriously astronomical sharpe on identical-returns symbols.
27664
- if (avgPnl !== null && stdDev !== null && stdDev > STDDEV_EPSILON) {
27667
+ if (canComputeRatios && avgPnl !== null && stdDev !== null && stdDev > STDDEV_EPSILON) {
27665
27668
  sharpeRatio = avgPnl / stdDev;
27666
27669
  }
27667
27670
  // Equity-curve max drawdown via compounded equity ("as-if 100% allocation per trade").
@@ -27854,17 +27857,21 @@ class HeatmapStorage {
27854
27857
  : sorted[mid];
27855
27858
  }
27856
27859
  // Expectancy — probabilities from observed win/loss counts (break-evens contribute 0).
27860
+ // Gated by MIN_SIGNALS_FOR_RATIOS (via canComputeRatios), same as the standalone
27861
+ // Backtest/Live paths and the portfolio-level pooled Expectancy below — on a tiny
27862
+ // sample the per-trade EV is too noisy to publish, and an ungated per-symbol value
27863
+ // would disagree with the symbol's own standalone report (which IS gated).
27857
27864
  let expectancy = null;
27858
- if (totalTrades > 0 && avgWin !== null && avgLoss !== null) {
27865
+ if (canComputeRatios && totalTrades > 0 && avgWin !== null && avgLoss !== null) {
27859
27866
  const winProb = winCount / totalTrades;
27860
27867
  const lossProb = lossCount / totalTrades;
27861
27868
  expectancy = winProb * avgWin + lossProb * avgLoss;
27862
27869
  }
27863
- else if (totalTrades > 0 && avgWin !== null && avgLoss === null) {
27870
+ else if (canComputeRatios && totalTrades > 0 && avgWin !== null && avgLoss === null) {
27864
27871
  // No losing trades — expectancy is just average win frequency × avgWin
27865
27872
  expectancy = (winCount / totalTrades) * avgWin;
27866
27873
  }
27867
- else if (totalTrades > 0 && avgWin === null && avgLoss !== null) {
27874
+ else if (canComputeRatios && totalTrades > 0 && avgWin === null && avgLoss !== null) {
27868
27875
  expectancy = (lossCount / totalTrades) * avgLoss;
27869
27876
  }
27870
27877
  // Average only over signals that have the value — do not dilute the mean with zeros.
@@ -28574,7 +28581,7 @@ class HeatmapStorage {
28574
28581
  `*Sortino Ratio (column): per-symbol Avg PNL / downside deviation, where downside deviation = √( Σ min(0, per-trade PNL)² / total trade count ) (canonical Sortino: MAR = 0, divide by N_total). UNITS: dimensionless. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS}, OR no losing trades, OR downside deviation ≤ 1e-9 (float-artifact guard).*`,
28575
28582
  `*Calmar Ratio (column): per-symbol Expected Yearly Returns / Max Drawdown, clamped to ±${MAX_CALMAR_RATIO}. Denominator is the mark-to-market max drawdown of that symbol's compounded equity curve. Null when Expected Yearly Returns is null (requires ≥ ${MIN_SIGNALS_FOR_ANNUALIZATION} signals and a calendar span ≥ ${MIN_CALENDAR_SPAN_DAYS} days for that symbol) OR Max Drawdown ≤ 0.*`,
28576
28583
  `*Recovery Factor (column): per-symbol (final equity − 1) × 100 / Max Drawdown, clamped to ±${MAX_CALMAR_RATIO}. The numerator is the compounded total return of that symbol's equity curve. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS}, the equity curve blew up (reached ≤ 0), or Max Drawdown ≤ 0.*`,
28577
- `*Expectancy (column): per-symbol expected value per trade. Three cases depending on what kinds of trades exist for that symbol: (a) BOTH winning and losing trades present → (winning-trade count / Total Trades) × Avg Win + (losing-trade count / Total Trades) × Avg Loss; (b) only winning trades present → (winning-trade count / Total Trades) × Avg Win (zero contribution from non-existent losses); (c) only losing trades present → (losing-trade count / Total Trades) × Avg Loss. Break-even trades contribute 0 (excluded from both probabilities). UNITS: percent per trade. NOT gated by MIN_SIGNALS computed whenever Total Trades 1 and at least one decisive trade exists. (Note: the portfolio-level Expectancy further up in this report uses a single combined formula and IS gated by MIN_SIGNALS_FOR_RATIOS over the pooled count; per-symbol Expectancy is intentionally looser to populate the row early.)*`,
28584
+ `*Expectancy (column): per-symbol expected value per trade. Three cases depending on what kinds of trades exist for that symbol: (a) BOTH winning and losing trades present → (winning-trade count / Total Trades) × Avg Win + (losing-trade count / Total Trades) × Avg Loss; (b) only winning trades present → (winning-trade count / Total Trades) × Avg Win (zero contribution from non-existent losses); (c) only losing trades present → (losing-trade count / Total Trades) × Avg Loss. Break-even trades contribute 0 (excluded from both probabilities). UNITS: percent per trade. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS} the same sample-size gate as the standalone Backtest/Live report and the portfolio-level pooled Expectancy further up, so the per-symbol value never disagrees with the symbol's own standalone report.*`,
28578
28585
  `*Max Drawdown (column): per-symbol mark-to-market max drawdown — the symbol's compounded equity curve applies each closed signal's worst intra-trade excursion (its trough-PNL snapshot, ≤ 0) before booking the realised close, so deep round-trip dips count rather than only realised close-to-close drops. UNITS: percent. NOT realised-only.*`,
28579
28586
  `*Avg Peak PNL / Avg Max Drawdown PNL (columns): per-symbol arithmetic means of each closed signal's peak-PNL / trough-PNL snapshot — the best / worst mark-to-market PNL recorded while the position was open. Signals that never recorded the snapshot are excluded — no zero dilution. UNITS: percent. NOT gated by MIN_SIGNALS — each is null only if no signal for that symbol carries the corresponding snapshot.*`,
28580
28587
  `*Peak Profit PNL / Max Drawdown PNL (columns): per-symbol MAX of the peak-PNL snapshot / MIN of the trough-PNL snapshot across the symbol's stored closed signals. UNITS: percent. The single best best-case and worst worst-case excursions for that symbol — tail behaviour the averages hide. NOT gated by MIN_SIGNALS — each is null only if no signal for that symbol carries the corresponding snapshot.*`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "12.1.0",
3
+ "version": "12.2.0",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",