backtest-kit 12.1.0 → 12.3.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 +24 -5
- package/build/index.mjs +24 -5
- package/package.json +1 -1
package/build/index.cjs
CHANGED
|
@@ -12383,6 +12383,10 @@ const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => functoolsKit.trycatch(async (sy
|
|
|
12383
12383
|
backtest,
|
|
12384
12384
|
timestamp,
|
|
12385
12385
|
};
|
|
12386
|
+
{
|
|
12387
|
+
await self.priceMetaService.next(symbol, currentPrice, { strategyName, exchangeName, frameName: data.frameName }, backtest);
|
|
12388
|
+
await self.timeMetaService.next(symbol, timestamp, { strategyName, exchangeName, frameName: data.frameName }, backtest);
|
|
12389
|
+
}
|
|
12386
12390
|
await schedulePingSubject.next(event);
|
|
12387
12391
|
await self.actionCoreService.pingScheduled(backtest, event, { strategyName, exchangeName, frameName: data.frameName });
|
|
12388
12392
|
}, {
|
|
@@ -12417,6 +12421,10 @@ const CREATE_COMMIT_IDLE_PING_FN = (self) => functoolsKit.trycatch(async (symbol
|
|
|
12417
12421
|
backtest,
|
|
12418
12422
|
timestamp,
|
|
12419
12423
|
};
|
|
12424
|
+
{
|
|
12425
|
+
await self.priceMetaService.next(symbol, currentPrice, { strategyName, exchangeName, frameName: frameName }, backtest);
|
|
12426
|
+
await self.timeMetaService.next(symbol, timestamp, { strategyName, exchangeName, frameName: frameName }, backtest);
|
|
12427
|
+
}
|
|
12420
12428
|
await idlePingSubject.next(event);
|
|
12421
12429
|
await self.actionCoreService.pingIdle(backtest, event, { strategyName, exchangeName, frameName });
|
|
12422
12430
|
}, {
|
|
@@ -12451,6 +12459,10 @@ const CREATE_COMMIT_ACTIVE_PING_FN = (self) => functoolsKit.trycatch(async (symb
|
|
|
12451
12459
|
backtest,
|
|
12452
12460
|
timestamp,
|
|
12453
12461
|
};
|
|
12462
|
+
{
|
|
12463
|
+
await self.priceMetaService.next(symbol, currentPrice, { strategyName, exchangeName, frameName: data.frameName }, backtest);
|
|
12464
|
+
await self.timeMetaService.next(symbol, timestamp, { strategyName, exchangeName, frameName: data.frameName }, backtest);
|
|
12465
|
+
}
|
|
12454
12466
|
await activePingSubject.next(event);
|
|
12455
12467
|
await self.actionCoreService.pingActive(backtest, event, { strategyName, exchangeName, frameName: data.frameName });
|
|
12456
12468
|
}, {
|
|
@@ -27679,9 +27691,12 @@ class HeatmapStorage {
|
|
|
27679
27691
|
}
|
|
27680
27692
|
// Per-trade Sharpe Ratio
|
|
27681
27693
|
let sharpeRatio = null;
|
|
27694
|
+
// canComputeRatios gate is explicit here (matching the standalone Backtest/Live
|
|
27695
|
+
// paths) even though stdDev is already null below MIN_SIGNALS_FOR_RATIOS — relying
|
|
27696
|
+
// on the stdDev===null implication would couple the gate to an unrelated branch.
|
|
27682
27697
|
// STDDEV_EPSILON guard — protects against float-artifact stdDev producing
|
|
27683
27698
|
// spuriously astronomical sharpe on identical-returns symbols.
|
|
27684
|
-
if (avgPnl !== null && stdDev !== null && stdDev > STDDEV_EPSILON) {
|
|
27699
|
+
if (canComputeRatios && avgPnl !== null && stdDev !== null && stdDev > STDDEV_EPSILON) {
|
|
27685
27700
|
sharpeRatio = avgPnl / stdDev;
|
|
27686
27701
|
}
|
|
27687
27702
|
// Equity-curve max drawdown via compounded equity ("as-if 100% allocation per trade").
|
|
@@ -27874,17 +27889,21 @@ class HeatmapStorage {
|
|
|
27874
27889
|
: sorted[mid];
|
|
27875
27890
|
}
|
|
27876
27891
|
// Expectancy — probabilities from observed win/loss counts (break-evens contribute 0).
|
|
27892
|
+
// Gated by MIN_SIGNALS_FOR_RATIOS (via canComputeRatios), same as the standalone
|
|
27893
|
+
// Backtest/Live paths and the portfolio-level pooled Expectancy below — on a tiny
|
|
27894
|
+
// sample the per-trade EV is too noisy to publish, and an ungated per-symbol value
|
|
27895
|
+
// would disagree with the symbol's own standalone report (which IS gated).
|
|
27877
27896
|
let expectancy = null;
|
|
27878
|
-
if (totalTrades > 0 && avgWin !== null && avgLoss !== null) {
|
|
27897
|
+
if (canComputeRatios && totalTrades > 0 && avgWin !== null && avgLoss !== null) {
|
|
27879
27898
|
const winProb = winCount / totalTrades;
|
|
27880
27899
|
const lossProb = lossCount / totalTrades;
|
|
27881
27900
|
expectancy = winProb * avgWin + lossProb * avgLoss;
|
|
27882
27901
|
}
|
|
27883
|
-
else if (totalTrades > 0 && avgWin !== null && avgLoss === null) {
|
|
27902
|
+
else if (canComputeRatios && totalTrades > 0 && avgWin !== null && avgLoss === null) {
|
|
27884
27903
|
// No losing trades — expectancy is just average win frequency × avgWin
|
|
27885
27904
|
expectancy = (winCount / totalTrades) * avgWin;
|
|
27886
27905
|
}
|
|
27887
|
-
else if (totalTrades > 0 && avgWin === null && avgLoss !== null) {
|
|
27906
|
+
else if (canComputeRatios && totalTrades > 0 && avgWin === null && avgLoss !== null) {
|
|
27888
27907
|
expectancy = (lossCount / totalTrades) * avgLoss;
|
|
27889
27908
|
}
|
|
27890
27909
|
// Average only over signals that have the value — do not dilute the mean with zeros.
|
|
@@ -28594,7 +28613,7 @@ class HeatmapStorage {
|
|
|
28594
28613
|
`*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
28614
|
`*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
28615
|
`*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.
|
|
28616
|
+
`*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
28617
|
`*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
28618
|
`*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
28619
|
`*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
|
@@ -12363,6 +12363,10 @@ const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => trycatch(async (symbol, strateg
|
|
|
12363
12363
|
backtest,
|
|
12364
12364
|
timestamp,
|
|
12365
12365
|
};
|
|
12366
|
+
{
|
|
12367
|
+
await self.priceMetaService.next(symbol, currentPrice, { strategyName, exchangeName, frameName: data.frameName }, backtest);
|
|
12368
|
+
await self.timeMetaService.next(symbol, timestamp, { strategyName, exchangeName, frameName: data.frameName }, backtest);
|
|
12369
|
+
}
|
|
12366
12370
|
await schedulePingSubject.next(event);
|
|
12367
12371
|
await self.actionCoreService.pingScheduled(backtest, event, { strategyName, exchangeName, frameName: data.frameName });
|
|
12368
12372
|
}, {
|
|
@@ -12397,6 +12401,10 @@ const CREATE_COMMIT_IDLE_PING_FN = (self) => trycatch(async (symbol, strategyNam
|
|
|
12397
12401
|
backtest,
|
|
12398
12402
|
timestamp,
|
|
12399
12403
|
};
|
|
12404
|
+
{
|
|
12405
|
+
await self.priceMetaService.next(symbol, currentPrice, { strategyName, exchangeName, frameName: frameName }, backtest);
|
|
12406
|
+
await self.timeMetaService.next(symbol, timestamp, { strategyName, exchangeName, frameName: frameName }, backtest);
|
|
12407
|
+
}
|
|
12400
12408
|
await idlePingSubject.next(event);
|
|
12401
12409
|
await self.actionCoreService.pingIdle(backtest, event, { strategyName, exchangeName, frameName });
|
|
12402
12410
|
}, {
|
|
@@ -12431,6 +12439,10 @@ const CREATE_COMMIT_ACTIVE_PING_FN = (self) => trycatch(async (symbol, strategyN
|
|
|
12431
12439
|
backtest,
|
|
12432
12440
|
timestamp,
|
|
12433
12441
|
};
|
|
12442
|
+
{
|
|
12443
|
+
await self.priceMetaService.next(symbol, currentPrice, { strategyName, exchangeName, frameName: data.frameName }, backtest);
|
|
12444
|
+
await self.timeMetaService.next(symbol, timestamp, { strategyName, exchangeName, frameName: data.frameName }, backtest);
|
|
12445
|
+
}
|
|
12434
12446
|
await activePingSubject.next(event);
|
|
12435
12447
|
await self.actionCoreService.pingActive(backtest, event, { strategyName, exchangeName, frameName: data.frameName });
|
|
12436
12448
|
}, {
|
|
@@ -27659,9 +27671,12 @@ class HeatmapStorage {
|
|
|
27659
27671
|
}
|
|
27660
27672
|
// Per-trade Sharpe Ratio
|
|
27661
27673
|
let sharpeRatio = null;
|
|
27674
|
+
// canComputeRatios gate is explicit here (matching the standalone Backtest/Live
|
|
27675
|
+
// paths) even though stdDev is already null below MIN_SIGNALS_FOR_RATIOS — relying
|
|
27676
|
+
// on the stdDev===null implication would couple the gate to an unrelated branch.
|
|
27662
27677
|
// STDDEV_EPSILON guard — protects against float-artifact stdDev producing
|
|
27663
27678
|
// spuriously astronomical sharpe on identical-returns symbols.
|
|
27664
|
-
if (avgPnl !== null && stdDev !== null && stdDev > STDDEV_EPSILON) {
|
|
27679
|
+
if (canComputeRatios && avgPnl !== null && stdDev !== null && stdDev > STDDEV_EPSILON) {
|
|
27665
27680
|
sharpeRatio = avgPnl / stdDev;
|
|
27666
27681
|
}
|
|
27667
27682
|
// Equity-curve max drawdown via compounded equity ("as-if 100% allocation per trade").
|
|
@@ -27854,17 +27869,21 @@ class HeatmapStorage {
|
|
|
27854
27869
|
: sorted[mid];
|
|
27855
27870
|
}
|
|
27856
27871
|
// Expectancy — probabilities from observed win/loss counts (break-evens contribute 0).
|
|
27872
|
+
// Gated by MIN_SIGNALS_FOR_RATIOS (via canComputeRatios), same as the standalone
|
|
27873
|
+
// Backtest/Live paths and the portfolio-level pooled Expectancy below — on a tiny
|
|
27874
|
+
// sample the per-trade EV is too noisy to publish, and an ungated per-symbol value
|
|
27875
|
+
// would disagree with the symbol's own standalone report (which IS gated).
|
|
27857
27876
|
let expectancy = null;
|
|
27858
|
-
if (totalTrades > 0 && avgWin !== null && avgLoss !== null) {
|
|
27877
|
+
if (canComputeRatios && totalTrades > 0 && avgWin !== null && avgLoss !== null) {
|
|
27859
27878
|
const winProb = winCount / totalTrades;
|
|
27860
27879
|
const lossProb = lossCount / totalTrades;
|
|
27861
27880
|
expectancy = winProb * avgWin + lossProb * avgLoss;
|
|
27862
27881
|
}
|
|
27863
|
-
else if (totalTrades > 0 && avgWin !== null && avgLoss === null) {
|
|
27882
|
+
else if (canComputeRatios && totalTrades > 0 && avgWin !== null && avgLoss === null) {
|
|
27864
27883
|
// No losing trades — expectancy is just average win frequency × avgWin
|
|
27865
27884
|
expectancy = (winCount / totalTrades) * avgWin;
|
|
27866
27885
|
}
|
|
27867
|
-
else if (totalTrades > 0 && avgWin === null && avgLoss !== null) {
|
|
27886
|
+
else if (canComputeRatios && totalTrades > 0 && avgWin === null && avgLoss !== null) {
|
|
27868
27887
|
expectancy = (lossCount / totalTrades) * avgLoss;
|
|
27869
27888
|
}
|
|
27870
27889
|
// Average only over signals that have the value — do not dilute the mean with zeros.
|
|
@@ -28574,7 +28593,7 @@ class HeatmapStorage {
|
|
|
28574
28593
|
`*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
28594
|
`*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
28595
|
`*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.
|
|
28596
|
+
`*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
28597
|
`*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
28598
|
`*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
28599
|
`*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.*`,
|