backtest-kit 11.9.0 → 12.1.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 +549 -112
- package/build/index.mjs +549 -113
- package/package.json +1 -1
- package/types.d.ts +64 -1
package/build/index.cjs
CHANGED
|
@@ -21061,6 +21061,24 @@ class WalkerCommandService {
|
|
|
21061
21061
|
}
|
|
21062
21062
|
}
|
|
21063
21063
|
|
|
21064
|
+
/**
|
|
21065
|
+
* Derives the number of decimal places to show for a price based on the
|
|
21066
|
+
* magnitude of its integer part. Larger prices need fewer decimals; sub-dollar
|
|
21067
|
+
* prices keep full precision.
|
|
21068
|
+
* @param value - The price to derive the decimal scale for
|
|
21069
|
+
* @returns The number of digits after the decimal point
|
|
21070
|
+
*/
|
|
21071
|
+
const getPriceScale = (value) => {
|
|
21072
|
+
const abs = Math.abs(value);
|
|
21073
|
+
if (abs >= 1) {
|
|
21074
|
+
// 1..9 -> 4, 10..99 -> 3, 100..999 -> 2, 1000+ -> 2 (floor), capped at 2
|
|
21075
|
+
const digits = Math.floor(Math.log10(abs)) + 1;
|
|
21076
|
+
return Math.max(2, 6 - digits);
|
|
21077
|
+
}
|
|
21078
|
+
// Sub-dollar prices need more precision.
|
|
21079
|
+
return 8;
|
|
21080
|
+
};
|
|
21081
|
+
|
|
21064
21082
|
/**
|
|
21065
21083
|
* Converts markdown content to plain text with minimal formatting
|
|
21066
21084
|
* @param content - Markdown string to convert
|
|
@@ -21169,43 +21187,43 @@ const backtest_columns = [
|
|
|
21169
21187
|
{
|
|
21170
21188
|
key: "openPrice",
|
|
21171
21189
|
label: "Open Price",
|
|
21172
|
-
format: (data) => `${data.signal.priceOpen.toFixed(
|
|
21190
|
+
format: (data) => `${data.signal.priceOpen.toFixed(getPriceScale(data.signal.priceOpen))} USD`,
|
|
21173
21191
|
isVisible: () => true,
|
|
21174
21192
|
},
|
|
21175
21193
|
{
|
|
21176
21194
|
key: "closePrice",
|
|
21177
21195
|
label: "Close Price",
|
|
21178
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
21196
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
21179
21197
|
isVisible: () => true,
|
|
21180
21198
|
},
|
|
21181
21199
|
{
|
|
21182
21200
|
key: "takeProfit",
|
|
21183
21201
|
label: "Take Profit",
|
|
21184
|
-
format: (data) => `${data.signal.priceTakeProfit.toFixed(
|
|
21202
|
+
format: (data) => `${data.signal.priceTakeProfit.toFixed(getPriceScale(data.signal.priceTakeProfit))} USD`,
|
|
21185
21203
|
isVisible: () => true,
|
|
21186
21204
|
},
|
|
21187
21205
|
{
|
|
21188
21206
|
key: "stopLoss",
|
|
21189
21207
|
label: "Stop Loss",
|
|
21190
|
-
format: (data) => `${data.signal.priceStopLoss.toFixed(
|
|
21208
|
+
format: (data) => `${data.signal.priceStopLoss.toFixed(getPriceScale(data.signal.priceStopLoss))} USD`,
|
|
21191
21209
|
isVisible: () => true,
|
|
21192
21210
|
},
|
|
21193
21211
|
{
|
|
21194
21212
|
key: "originalPriceTakeProfit",
|
|
21195
21213
|
label: "Original TP",
|
|
21196
|
-
format: (data) => `${data.signal.originalPriceTakeProfit.toFixed(
|
|
21214
|
+
format: (data) => `${data.signal.originalPriceTakeProfit.toFixed(getPriceScale(data.signal.originalPriceTakeProfit))} USD`,
|
|
21197
21215
|
isVisible: () => true,
|
|
21198
21216
|
},
|
|
21199
21217
|
{
|
|
21200
21218
|
key: "originalPriceStopLoss",
|
|
21201
21219
|
label: "Original SL",
|
|
21202
|
-
format: (data) => `${data.signal.originalPriceStopLoss.toFixed(
|
|
21220
|
+
format: (data) => `${data.signal.originalPriceStopLoss.toFixed(getPriceScale(data.signal.originalPriceStopLoss))} USD`,
|
|
21203
21221
|
isVisible: () => true,
|
|
21204
21222
|
},
|
|
21205
21223
|
{
|
|
21206
21224
|
key: "originalPriceOpen",
|
|
21207
21225
|
label: "Original Entry",
|
|
21208
|
-
format: (data) => `${data.signal.originalPriceOpen.toFixed(
|
|
21226
|
+
format: (data) => `${data.signal.originalPriceOpen.toFixed(getPriceScale(data.signal.originalPriceOpen))} USD`,
|
|
21209
21227
|
isVisible: () => true,
|
|
21210
21228
|
},
|
|
21211
21229
|
{
|
|
@@ -21508,6 +21526,71 @@ const heat_columns = [
|
|
|
21508
21526
|
: "N/A",
|
|
21509
21527
|
isVisible: () => true,
|
|
21510
21528
|
},
|
|
21529
|
+
{
|
|
21530
|
+
key: "trend",
|
|
21531
|
+
label: "Trend",
|
|
21532
|
+
format: (data) => data.trend ?? "N/A",
|
|
21533
|
+
isVisible: () => true,
|
|
21534
|
+
},
|
|
21535
|
+
{
|
|
21536
|
+
key: "trendStrength",
|
|
21537
|
+
label: "Trend %/d",
|
|
21538
|
+
format: (data) => data.trendStrength !== null ? functoolsKit.str(data.trendStrength, "%") : "N/A",
|
|
21539
|
+
isVisible: () => true,
|
|
21540
|
+
},
|
|
21541
|
+
{
|
|
21542
|
+
key: "trendConfidence",
|
|
21543
|
+
label: "Trend R²",
|
|
21544
|
+
format: (data) => data.trendConfidence !== null ? data.trendConfidence.toFixed(3) : "N/A",
|
|
21545
|
+
isVisible: () => true,
|
|
21546
|
+
},
|
|
21547
|
+
{
|
|
21548
|
+
key: "buyerPressure",
|
|
21549
|
+
label: "Buyer Pres",
|
|
21550
|
+
format: (data) => data.buyerPressure !== null
|
|
21551
|
+
? (data.buyerPressure * 100).toFixed(1) + "%"
|
|
21552
|
+
: "N/A",
|
|
21553
|
+
isVisible: () => true,
|
|
21554
|
+
},
|
|
21555
|
+
{
|
|
21556
|
+
key: "sellerPressure",
|
|
21557
|
+
label: "Seller Pres",
|
|
21558
|
+
format: (data) => data.sellerPressure !== null
|
|
21559
|
+
? (data.sellerPressure * 100).toFixed(1) + "%"
|
|
21560
|
+
: "N/A",
|
|
21561
|
+
isVisible: () => true,
|
|
21562
|
+
},
|
|
21563
|
+
{
|
|
21564
|
+
key: "buyerStrength",
|
|
21565
|
+
label: "Buyer Str",
|
|
21566
|
+
format: (data) => data.buyerStrength !== null
|
|
21567
|
+
? (data.buyerStrength * 100).toFixed(1) + "%"
|
|
21568
|
+
: "N/A",
|
|
21569
|
+
isVisible: () => true,
|
|
21570
|
+
},
|
|
21571
|
+
{
|
|
21572
|
+
key: "sellerStrength",
|
|
21573
|
+
label: "Seller Str",
|
|
21574
|
+
format: (data) => data.sellerStrength !== null
|
|
21575
|
+
? (data.sellerStrength * 100).toFixed(1) + "%"
|
|
21576
|
+
: "N/A",
|
|
21577
|
+
isVisible: () => true,
|
|
21578
|
+
},
|
|
21579
|
+
{
|
|
21580
|
+
key: "pressureImbalance",
|
|
21581
|
+
label: "Pres Imb",
|
|
21582
|
+
format: (data) => data.pressureImbalance !== null
|
|
21583
|
+
? (data.pressureImbalance > 0 ? "+" : "") +
|
|
21584
|
+
data.pressureImbalance.toFixed(3)
|
|
21585
|
+
: "N/A",
|
|
21586
|
+
isVisible: () => true,
|
|
21587
|
+
},
|
|
21588
|
+
{
|
|
21589
|
+
key: "medianStepSize",
|
|
21590
|
+
label: "Median Step",
|
|
21591
|
+
format: (data) => data.medianStepSize !== null ? functoolsKit.str(data.medianStepSize, "%") : "N/A",
|
|
21592
|
+
isVisible: () => true,
|
|
21593
|
+
},
|
|
21511
21594
|
{
|
|
21512
21595
|
key: "sortinoRatio",
|
|
21513
21596
|
label: "Sortino",
|
|
@@ -21604,34 +21687,34 @@ const live_columns = [
|
|
|
21604
21687
|
{
|
|
21605
21688
|
key: "currentPrice",
|
|
21606
21689
|
label: "Current Price",
|
|
21607
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
21690
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
21608
21691
|
isVisible: () => true,
|
|
21609
21692
|
},
|
|
21610
21693
|
{
|
|
21611
21694
|
key: "openPrice",
|
|
21612
21695
|
label: "Open Price",
|
|
21613
|
-
format: (data) => data.priceOpen !== undefined ? `${data.priceOpen.toFixed(
|
|
21696
|
+
format: (data) => data.priceOpen !== undefined ? `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD` : "N/A",
|
|
21614
21697
|
isVisible: () => true,
|
|
21615
21698
|
},
|
|
21616
21699
|
{
|
|
21617
21700
|
key: "takeProfit",
|
|
21618
21701
|
label: "Take Profit",
|
|
21619
21702
|
format: (data) => data.priceTakeProfit !== undefined
|
|
21620
|
-
? `${data.priceTakeProfit.toFixed(
|
|
21703
|
+
? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`
|
|
21621
21704
|
: "N/A",
|
|
21622
21705
|
isVisible: () => true,
|
|
21623
21706
|
},
|
|
21624
21707
|
{
|
|
21625
21708
|
key: "stopLoss",
|
|
21626
21709
|
label: "Stop Loss",
|
|
21627
|
-
format: (data) => data.priceStopLoss !== undefined ? `${data.priceStopLoss.toFixed(
|
|
21710
|
+
format: (data) => data.priceStopLoss !== undefined ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A",
|
|
21628
21711
|
isVisible: () => true,
|
|
21629
21712
|
},
|
|
21630
21713
|
{
|
|
21631
21714
|
key: "originalPriceTakeProfit",
|
|
21632
21715
|
label: "Original TP",
|
|
21633
21716
|
format: (data) => data.originalPriceTakeProfit !== undefined
|
|
21634
|
-
? `${data.originalPriceTakeProfit.toFixed(
|
|
21717
|
+
? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD`
|
|
21635
21718
|
: "N/A",
|
|
21636
21719
|
isVisible: () => true,
|
|
21637
21720
|
},
|
|
@@ -21639,7 +21722,7 @@ const live_columns = [
|
|
|
21639
21722
|
key: "originalPriceStopLoss",
|
|
21640
21723
|
label: "Original SL",
|
|
21641
21724
|
format: (data) => data.originalPriceStopLoss !== undefined
|
|
21642
|
-
? `${data.originalPriceStopLoss.toFixed(
|
|
21725
|
+
? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD`
|
|
21643
21726
|
: "N/A",
|
|
21644
21727
|
isVisible: () => true,
|
|
21645
21728
|
},
|
|
@@ -21647,7 +21730,7 @@ const live_columns = [
|
|
|
21647
21730
|
key: "originalPriceOpen",
|
|
21648
21731
|
label: "Original Entry",
|
|
21649
21732
|
format: (data) => data.originalPriceOpen !== undefined
|
|
21650
|
-
? `${data.originalPriceOpen.toFixed(
|
|
21733
|
+
? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD`
|
|
21651
21734
|
: "N/A",
|
|
21652
21735
|
isVisible: () => true,
|
|
21653
21736
|
},
|
|
@@ -21818,43 +21901,43 @@ const partial_columns = [
|
|
|
21818
21901
|
{
|
|
21819
21902
|
key: "currentPrice",
|
|
21820
21903
|
label: "Current Price",
|
|
21821
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
21904
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
21822
21905
|
isVisible: () => true,
|
|
21823
21906
|
},
|
|
21824
21907
|
{
|
|
21825
21908
|
key: "priceOpen",
|
|
21826
21909
|
label: "Entry Price",
|
|
21827
|
-
format: (data) => (data.priceOpen ? `${data.priceOpen.toFixed(
|
|
21910
|
+
format: (data) => (data.priceOpen ? `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD` : "N/A"),
|
|
21828
21911
|
isVisible: () => true,
|
|
21829
21912
|
},
|
|
21830
21913
|
{
|
|
21831
21914
|
key: "priceTakeProfit",
|
|
21832
21915
|
label: "Take Profit",
|
|
21833
|
-
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(
|
|
21916
|
+
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD` : "N/A"),
|
|
21834
21917
|
isVisible: () => true,
|
|
21835
21918
|
},
|
|
21836
21919
|
{
|
|
21837
21920
|
key: "priceStopLoss",
|
|
21838
21921
|
label: "Stop Loss",
|
|
21839
|
-
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(
|
|
21922
|
+
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A"),
|
|
21840
21923
|
isVisible: () => true,
|
|
21841
21924
|
},
|
|
21842
21925
|
{
|
|
21843
21926
|
key: "originalPriceTakeProfit",
|
|
21844
21927
|
label: "Original TP",
|
|
21845
|
-
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(
|
|
21928
|
+
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD` : "N/A"),
|
|
21846
21929
|
isVisible: () => true,
|
|
21847
21930
|
},
|
|
21848
21931
|
{
|
|
21849
21932
|
key: "originalPriceStopLoss",
|
|
21850
21933
|
label: "Original SL",
|
|
21851
|
-
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(
|
|
21934
|
+
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD` : "N/A"),
|
|
21852
21935
|
isVisible: () => true,
|
|
21853
21936
|
},
|
|
21854
21937
|
{
|
|
21855
21938
|
key: "originalPriceOpen",
|
|
21856
21939
|
label: "Original Entry",
|
|
21857
|
-
format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(
|
|
21940
|
+
format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD` : "N/A"),
|
|
21858
21941
|
isVisible: () => true,
|
|
21859
21942
|
},
|
|
21860
21943
|
{
|
|
@@ -21978,43 +22061,43 @@ const breakeven_columns = [
|
|
|
21978
22061
|
{
|
|
21979
22062
|
key: "priceOpen",
|
|
21980
22063
|
label: "Entry Price",
|
|
21981
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
22064
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
21982
22065
|
isVisible: () => true,
|
|
21983
22066
|
},
|
|
21984
22067
|
{
|
|
21985
22068
|
key: "currentPrice",
|
|
21986
22069
|
label: "Breakeven Price",
|
|
21987
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
22070
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
21988
22071
|
isVisible: () => true,
|
|
21989
22072
|
},
|
|
21990
22073
|
{
|
|
21991
22074
|
key: "priceTakeProfit",
|
|
21992
22075
|
label: "Take Profit",
|
|
21993
|
-
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(
|
|
22076
|
+
format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD` : "N/A"),
|
|
21994
22077
|
isVisible: () => true,
|
|
21995
22078
|
},
|
|
21996
22079
|
{
|
|
21997
22080
|
key: "priceStopLoss",
|
|
21998
22081
|
label: "Stop Loss",
|
|
21999
|
-
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(
|
|
22082
|
+
format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A"),
|
|
22000
22083
|
isVisible: () => true,
|
|
22001
22084
|
},
|
|
22002
22085
|
{
|
|
22003
22086
|
key: "originalPriceTakeProfit",
|
|
22004
22087
|
label: "Original TP",
|
|
22005
|
-
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(
|
|
22088
|
+
format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD` : "N/A"),
|
|
22006
22089
|
isVisible: () => true,
|
|
22007
22090
|
},
|
|
22008
22091
|
{
|
|
22009
22092
|
key: "originalPriceStopLoss",
|
|
22010
22093
|
label: "Original SL",
|
|
22011
|
-
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(
|
|
22094
|
+
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD` : "N/A"),
|
|
22012
22095
|
isVisible: () => true,
|
|
22013
22096
|
},
|
|
22014
22097
|
{
|
|
22015
22098
|
key: "originalPriceOpen",
|
|
22016
22099
|
label: "Original Entry",
|
|
22017
|
-
format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(
|
|
22100
|
+
format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD` : "N/A"),
|
|
22018
22101
|
isVisible: () => true,
|
|
22019
22102
|
},
|
|
22020
22103
|
{
|
|
@@ -22271,7 +22354,7 @@ const risk_columns = [
|
|
|
22271
22354
|
key: "openPrice",
|
|
22272
22355
|
label: "Open Price",
|
|
22273
22356
|
format: (data) => data.currentSignal.priceOpen !== undefined
|
|
22274
|
-
? `${data.currentSignal.priceOpen.toFixed(
|
|
22357
|
+
? `${data.currentSignal.priceOpen.toFixed(getPriceScale(data.currentSignal.priceOpen))} USD`
|
|
22275
22358
|
: "N/A",
|
|
22276
22359
|
isVisible: () => true,
|
|
22277
22360
|
},
|
|
@@ -22279,7 +22362,7 @@ const risk_columns = [
|
|
|
22279
22362
|
key: "takeProfit",
|
|
22280
22363
|
label: "Take Profit",
|
|
22281
22364
|
format: (data) => data.currentSignal.priceTakeProfit !== undefined
|
|
22282
|
-
? `${data.currentSignal.priceTakeProfit.toFixed(
|
|
22365
|
+
? `${data.currentSignal.priceTakeProfit.toFixed(getPriceScale(data.currentSignal.priceTakeProfit))} USD`
|
|
22283
22366
|
: "N/A",
|
|
22284
22367
|
isVisible: () => true,
|
|
22285
22368
|
},
|
|
@@ -22287,7 +22370,7 @@ const risk_columns = [
|
|
|
22287
22370
|
key: "stopLoss",
|
|
22288
22371
|
label: "Stop Loss",
|
|
22289
22372
|
format: (data) => data.currentSignal.priceStopLoss !== undefined
|
|
22290
|
-
? `${data.currentSignal.priceStopLoss.toFixed(
|
|
22373
|
+
? `${data.currentSignal.priceStopLoss.toFixed(getPriceScale(data.currentSignal.priceStopLoss))} USD`
|
|
22291
22374
|
: "N/A",
|
|
22292
22375
|
isVisible: () => true,
|
|
22293
22376
|
},
|
|
@@ -22295,7 +22378,7 @@ const risk_columns = [
|
|
|
22295
22378
|
key: "originalPriceTakeProfit",
|
|
22296
22379
|
label: "Original TP",
|
|
22297
22380
|
format: (data) => data.currentSignal.originalPriceTakeProfit !== undefined
|
|
22298
|
-
? `${data.currentSignal.originalPriceTakeProfit.toFixed(
|
|
22381
|
+
? `${data.currentSignal.originalPriceTakeProfit.toFixed(getPriceScale(data.currentSignal.originalPriceTakeProfit))} USD`
|
|
22299
22382
|
: "N/A",
|
|
22300
22383
|
isVisible: () => true,
|
|
22301
22384
|
},
|
|
@@ -22303,7 +22386,7 @@ const risk_columns = [
|
|
|
22303
22386
|
key: "originalPriceStopLoss",
|
|
22304
22387
|
label: "Original SL",
|
|
22305
22388
|
format: (data) => data.currentSignal.originalPriceStopLoss !== undefined
|
|
22306
|
-
? `${data.currentSignal.originalPriceStopLoss.toFixed(
|
|
22389
|
+
? `${data.currentSignal.originalPriceStopLoss.toFixed(getPriceScale(data.currentSignal.originalPriceStopLoss))} USD`
|
|
22307
22390
|
: "N/A",
|
|
22308
22391
|
isVisible: () => true,
|
|
22309
22392
|
},
|
|
@@ -22311,7 +22394,7 @@ const risk_columns = [
|
|
|
22311
22394
|
key: "originalPriceOpen",
|
|
22312
22395
|
label: "Original Entry",
|
|
22313
22396
|
format: (data) => data.currentSignal.originalPriceOpen !== undefined
|
|
22314
|
-
? `${data.currentSignal.originalPriceOpen.toFixed(
|
|
22397
|
+
? `${data.currentSignal.originalPriceOpen.toFixed(getPriceScale(data.currentSignal.originalPriceOpen))} USD`
|
|
22315
22398
|
: "N/A",
|
|
22316
22399
|
isVisible: () => true,
|
|
22317
22400
|
},
|
|
@@ -22342,7 +22425,7 @@ const risk_columns = [
|
|
|
22342
22425
|
{
|
|
22343
22426
|
key: "currentPrice",
|
|
22344
22427
|
label: "Current Price",
|
|
22345
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
22428
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
22346
22429
|
isVisible: () => true,
|
|
22347
22430
|
},
|
|
22348
22431
|
{
|
|
@@ -22468,32 +22551,32 @@ const schedule_columns = [
|
|
|
22468
22551
|
{
|
|
22469
22552
|
key: "currentPrice",
|
|
22470
22553
|
label: "Current Price",
|
|
22471
|
-
format: (data) => data.currentPrice ? `${data.currentPrice.toFixed(
|
|
22554
|
+
format: (data) => data.currentPrice ? `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD` : "N/A",
|
|
22472
22555
|
isVisible: () => true,
|
|
22473
22556
|
},
|
|
22474
22557
|
{
|
|
22475
22558
|
key: "priceOpen",
|
|
22476
22559
|
label: "Entry Price",
|
|
22477
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
22560
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
22478
22561
|
isVisible: () => true,
|
|
22479
22562
|
},
|
|
22480
22563
|
{
|
|
22481
22564
|
key: "takeProfit",
|
|
22482
22565
|
label: "Take Profit",
|
|
22483
|
-
format: (data) => `${data.priceTakeProfit.toFixed(
|
|
22566
|
+
format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
|
|
22484
22567
|
isVisible: () => true,
|
|
22485
22568
|
},
|
|
22486
22569
|
{
|
|
22487
22570
|
key: "stopLoss",
|
|
22488
22571
|
label: "Stop Loss",
|
|
22489
|
-
format: (data) => `${data.priceStopLoss.toFixed(
|
|
22572
|
+
format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
|
|
22490
22573
|
isVisible: () => true,
|
|
22491
22574
|
},
|
|
22492
22575
|
{
|
|
22493
22576
|
key: "originalPriceTakeProfit",
|
|
22494
22577
|
label: "Original TP",
|
|
22495
22578
|
format: (data) => data.originalPriceTakeProfit !== undefined
|
|
22496
|
-
? `${data.originalPriceTakeProfit.toFixed(
|
|
22579
|
+
? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD`
|
|
22497
22580
|
: "N/A",
|
|
22498
22581
|
isVisible: () => true,
|
|
22499
22582
|
},
|
|
@@ -22501,7 +22584,7 @@ const schedule_columns = [
|
|
|
22501
22584
|
key: "originalPriceStopLoss",
|
|
22502
22585
|
label: "Original SL",
|
|
22503
22586
|
format: (data) => data.originalPriceStopLoss !== undefined
|
|
22504
|
-
? `${data.originalPriceStopLoss.toFixed(
|
|
22587
|
+
? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD`
|
|
22505
22588
|
: "N/A",
|
|
22506
22589
|
isVisible: () => true,
|
|
22507
22590
|
},
|
|
@@ -22509,7 +22592,7 @@ const schedule_columns = [
|
|
|
22509
22592
|
key: "originalPriceOpen",
|
|
22510
22593
|
label: "Original Entry",
|
|
22511
22594
|
format: (data) => data.originalPriceOpen !== undefined
|
|
22512
|
-
? `${data.originalPriceOpen.toFixed(
|
|
22595
|
+
? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD`
|
|
22513
22596
|
: "N/A",
|
|
22514
22597
|
isVisible: () => true,
|
|
22515
22598
|
},
|
|
@@ -22634,7 +22717,7 @@ const strategy_columns = [
|
|
|
22634
22717
|
{
|
|
22635
22718
|
key: "currentPrice",
|
|
22636
22719
|
label: "Price",
|
|
22637
|
-
format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(
|
|
22720
|
+
format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD` : "N/A"),
|
|
22638
22721
|
isVisible: () => true,
|
|
22639
22722
|
},
|
|
22640
22723
|
{
|
|
@@ -22754,25 +22837,25 @@ const sync_columns = [
|
|
|
22754
22837
|
{
|
|
22755
22838
|
key: "currentPrice",
|
|
22756
22839
|
label: "Current Price",
|
|
22757
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
22840
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
22758
22841
|
isVisible: () => true,
|
|
22759
22842
|
},
|
|
22760
22843
|
{
|
|
22761
22844
|
key: "priceOpen",
|
|
22762
22845
|
label: "Entry Price",
|
|
22763
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
22846
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
22764
22847
|
isVisible: () => true,
|
|
22765
22848
|
},
|
|
22766
22849
|
{
|
|
22767
22850
|
key: "priceTakeProfit",
|
|
22768
22851
|
label: "Take Profit",
|
|
22769
|
-
format: (data) => `${data.priceTakeProfit.toFixed(
|
|
22852
|
+
format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
|
|
22770
22853
|
isVisible: () => true,
|
|
22771
22854
|
},
|
|
22772
22855
|
{
|
|
22773
22856
|
key: "priceStopLoss",
|
|
22774
22857
|
label: "Stop Loss",
|
|
22775
|
-
format: (data) => `${data.priceStopLoss.toFixed(
|
|
22858
|
+
format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
|
|
22776
22859
|
isVisible: () => true,
|
|
22777
22860
|
},
|
|
22778
22861
|
{
|
|
@@ -22865,25 +22948,25 @@ const highest_profit_columns = [
|
|
|
22865
22948
|
{
|
|
22866
22949
|
key: "currentPrice",
|
|
22867
22950
|
label: "Peak Price",
|
|
22868
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
22951
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
22869
22952
|
isVisible: () => true,
|
|
22870
22953
|
},
|
|
22871
22954
|
{
|
|
22872
22955
|
key: "priceOpen",
|
|
22873
22956
|
label: "Entry Price",
|
|
22874
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
22957
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
22875
22958
|
isVisible: () => true,
|
|
22876
22959
|
},
|
|
22877
22960
|
{
|
|
22878
22961
|
key: "priceTakeProfit",
|
|
22879
22962
|
label: "Take Profit",
|
|
22880
|
-
format: (data) => `${data.priceTakeProfit.toFixed(
|
|
22963
|
+
format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
|
|
22881
22964
|
isVisible: () => true,
|
|
22882
22965
|
},
|
|
22883
22966
|
{
|
|
22884
22967
|
key: "priceStopLoss",
|
|
22885
22968
|
label: "Stop Loss",
|
|
22886
|
-
format: (data) => `${data.priceStopLoss.toFixed(
|
|
22969
|
+
format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
|
|
22887
22970
|
isVisible: () => true,
|
|
22888
22971
|
},
|
|
22889
22972
|
{
|
|
@@ -22952,25 +23035,25 @@ const max_drawdown_columns = [
|
|
|
22952
23035
|
{
|
|
22953
23036
|
key: "currentPrice",
|
|
22954
23037
|
label: "DD Price",
|
|
22955
|
-
format: (data) => `${data.currentPrice.toFixed(
|
|
23038
|
+
format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
|
|
22956
23039
|
isVisible: () => true,
|
|
22957
23040
|
},
|
|
22958
23041
|
{
|
|
22959
23042
|
key: "priceOpen",
|
|
22960
23043
|
label: "Entry Price",
|
|
22961
|
-
format: (data) => `${data.priceOpen.toFixed(
|
|
23044
|
+
format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
|
|
22962
23045
|
isVisible: () => true,
|
|
22963
23046
|
},
|
|
22964
23047
|
{
|
|
22965
23048
|
key: "priceTakeProfit",
|
|
22966
23049
|
label: "Take Profit",
|
|
22967
|
-
format: (data) => `${data.priceTakeProfit.toFixed(
|
|
23050
|
+
format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
|
|
22968
23051
|
isVisible: () => true,
|
|
22969
23052
|
},
|
|
22970
23053
|
{
|
|
22971
23054
|
key: "priceStopLoss",
|
|
22972
23055
|
label: "Stop Loss",
|
|
22973
|
-
format: (data) => `${data.priceStopLoss.toFixed(
|
|
23056
|
+
format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
|
|
22974
23057
|
isVisible: () => true,
|
|
22975
23058
|
},
|
|
22976
23059
|
{
|
|
@@ -23852,6 +23935,165 @@ MarkdownFileBase = functoolsKit.makeExtendable(MarkdownFileBase);
|
|
|
23852
23935
|
ReportBase = functoolsKit.makeExtendable(ReportBase);
|
|
23853
23936
|
const ReportWriter = new ReportWriterAdapter();
|
|
23854
23937
|
|
|
23938
|
+
/** Minimum samples to surface any price-profile metric. Below this the
|
|
23939
|
+
* per-trade step-distribution and the regression are statistically noisy. */
|
|
23940
|
+
const MIN_SIGNALS = 10;
|
|
23941
|
+
/** R² gate for declaring any trend at all. Below this the regression is
|
|
23942
|
+
* too weak to claim a direction — treat the series as sideways even if
|
|
23943
|
+
* the slope is large. 0.30 is the conventional weak-to-moderate-fit
|
|
23944
|
+
* boundary in econometrics (Cohen's f² ≈ 0.43). */
|
|
23945
|
+
const R2_TREND_GATE = 0.30;
|
|
23946
|
+
/** Slope-magnitude threshold relative to medianStepSize for declaring the
|
|
23947
|
+
* trend strong enough to call bullish/bearish. Below this the regression
|
|
23948
|
+
* fits but the actual drift is weaker than the typical daily step, so we
|
|
23949
|
+
* downgrade to "neutral" (a real but uninteresting tilt). */
|
|
23950
|
+
const SLOPE_VS_STEP_GATE = 0.25;
|
|
23951
|
+
const isFiniteNumber = (v) => typeof v === "number" && Number.isFinite(v);
|
|
23952
|
+
const median = (values) => {
|
|
23953
|
+
const sorted = values.slice().sort((a, b) => a - b);
|
|
23954
|
+
const mid = sorted.length >> 1;
|
|
23955
|
+
return sorted.length % 2 === 0
|
|
23956
|
+
? (sorted[mid - 1] + sorted[mid]) / 2
|
|
23957
|
+
: sorted[mid];
|
|
23958
|
+
};
|
|
23959
|
+
const emptyProfile = () => ({
|
|
23960
|
+
medianStepSize: null,
|
|
23961
|
+
buyerPressure: null,
|
|
23962
|
+
sellerPressure: null,
|
|
23963
|
+
buyerStrength: null,
|
|
23964
|
+
sellerStrength: null,
|
|
23965
|
+
pressureImbalance: null,
|
|
23966
|
+
trend: null,
|
|
23967
|
+
trendStrength: null,
|
|
23968
|
+
trendConfidence: null,
|
|
23969
|
+
});
|
|
23970
|
+
/**
|
|
23971
|
+
* Computes the price-profile bundle for a chronological series of trade
|
|
23972
|
+
* closes. The input is expected to be sorted by `closeAt` ascending; the
|
|
23973
|
+
* function does not sort defensively (the markdown services already sort).
|
|
23974
|
+
*
|
|
23975
|
+
* @param series - One point per closed trade: timestamp (ms) and close price.
|
|
23976
|
+
* @returns A bundle of nine metrics, each `null` when the input is too small
|
|
23977
|
+
* or numerically unsafe.
|
|
23978
|
+
*/
|
|
23979
|
+
const getPriceProfile = (series) => {
|
|
23980
|
+
const valid = series.filter((p) => isFiniteNumber(p.closeAt) &&
|
|
23981
|
+
p.closeAt > 0 &&
|
|
23982
|
+
isFiniteNumber(p.close) &&
|
|
23983
|
+
p.close > 0);
|
|
23984
|
+
if (valid.length < MIN_SIGNALS)
|
|
23985
|
+
return emptyProfile();
|
|
23986
|
+
const n = valid.length;
|
|
23987
|
+
const closes = valid.map((p) => p.close);
|
|
23988
|
+
const times = valid.map((p) => p.closeAt);
|
|
23989
|
+
const absSteps = [];
|
|
23990
|
+
let upMoves = 0;
|
|
23991
|
+
let downMoves = 0;
|
|
23992
|
+
let upMagnitude = 0;
|
|
23993
|
+
let downMagnitude = 0;
|
|
23994
|
+
for (let i = 1; i < n; i++) {
|
|
23995
|
+
const prev = closes[i - 1];
|
|
23996
|
+
const cur = closes[i];
|
|
23997
|
+
const ret = (cur - prev) / prev;
|
|
23998
|
+
const abs = Math.abs(ret);
|
|
23999
|
+
absSteps.push(abs);
|
|
24000
|
+
if (ret > 0) {
|
|
24001
|
+
upMoves++;
|
|
24002
|
+
upMagnitude += abs;
|
|
24003
|
+
}
|
|
24004
|
+
else if (ret < 0) {
|
|
24005
|
+
downMoves++;
|
|
24006
|
+
downMagnitude += abs;
|
|
24007
|
+
}
|
|
24008
|
+
}
|
|
24009
|
+
if (absSteps.length === 0)
|
|
24010
|
+
return emptyProfile();
|
|
24011
|
+
const medianStepSize = median(absSteps) * 100; // percent
|
|
24012
|
+
// --- Pressure / strength ---
|
|
24013
|
+
const decisiveMoves = upMoves + downMoves;
|
|
24014
|
+
const totalMagnitude = upMagnitude + downMagnitude;
|
|
24015
|
+
const buyerPressure = decisiveMoves > 0 ? upMoves / decisiveMoves : null;
|
|
24016
|
+
const sellerPressure = decisiveMoves > 0 ? downMoves / decisiveMoves : null;
|
|
24017
|
+
const buyerStrength = totalMagnitude > 0 ? upMagnitude / totalMagnitude : null;
|
|
24018
|
+
const sellerStrength = totalMagnitude > 0 ? downMagnitude / totalMagnitude : null;
|
|
24019
|
+
const pressureImbalance = buyerStrength !== null && sellerStrength !== null
|
|
24020
|
+
? buyerStrength - sellerStrength
|
|
24021
|
+
: null;
|
|
24022
|
+
// --- Trend: linear regression of log(close) vs days ---
|
|
24023
|
+
// log-price slope is scale-invariant: 1%/day means the same whether the
|
|
24024
|
+
// asset trades at $0.01 or $10000. Use `closeAt[0]` as time origin so x_i
|
|
24025
|
+
// starts at zero — keeps numerical conditioning sane on long horizons.
|
|
24026
|
+
const t0 = times[0];
|
|
24027
|
+
const xs = new Array(n);
|
|
24028
|
+
const ys = new Array(n);
|
|
24029
|
+
for (let i = 0; i < n; i++) {
|
|
24030
|
+
xs[i] = (times[i] - t0) / (1000 * 60 * 60 * 24); // days
|
|
24031
|
+
ys[i] = Math.log(closes[i]);
|
|
24032
|
+
}
|
|
24033
|
+
// Calendar span must be non-degenerate for the slope to mean anything.
|
|
24034
|
+
const xRange = xs[n - 1] - xs[0];
|
|
24035
|
+
let trend = null;
|
|
24036
|
+
let trendStrength = null;
|
|
24037
|
+
let trendConfidence = null;
|
|
24038
|
+
if (xRange > 0) {
|
|
24039
|
+
let sumX = 0;
|
|
24040
|
+
let sumY = 0;
|
|
24041
|
+
for (let i = 0; i < n; i++) {
|
|
24042
|
+
sumX += xs[i];
|
|
24043
|
+
sumY += ys[i];
|
|
24044
|
+
}
|
|
24045
|
+
const meanX = sumX / n;
|
|
24046
|
+
const meanY = sumY / n;
|
|
24047
|
+
let ssXX = 0;
|
|
24048
|
+
let ssXY = 0;
|
|
24049
|
+
let ssYY = 0;
|
|
24050
|
+
for (let i = 0; i < n; i++) {
|
|
24051
|
+
const dx = xs[i] - meanX;
|
|
24052
|
+
const dy = ys[i] - meanY;
|
|
24053
|
+
ssXX += dx * dx;
|
|
24054
|
+
ssXY += dx * dy;
|
|
24055
|
+
ssYY += dy * dy;
|
|
24056
|
+
}
|
|
24057
|
+
if (ssXX > 0) {
|
|
24058
|
+
const slopeLog = ssXY / ssXX; // log-return per day
|
|
24059
|
+
const slopePct = slopeLog * 100; // %/day (small-slope approx)
|
|
24060
|
+
// R² = 1 - SS_res / SS_tot. With a single explanatory variable this
|
|
24061
|
+
// equals (ssXY)² / (ssXX * ssYY) when ssYY > 0.
|
|
24062
|
+
const r2 = ssYY > 0 ? (ssXY * ssXY) / (ssXX * ssYY) : 0;
|
|
24063
|
+
trendStrength = slopePct;
|
|
24064
|
+
trendConfidence = Math.max(0, Math.min(1, r2));
|
|
24065
|
+
if (trendConfidence < R2_TREND_GATE) {
|
|
24066
|
+
trend = "sideways";
|
|
24067
|
+
}
|
|
24068
|
+
else {
|
|
24069
|
+
const slopeMagnitude = Math.abs(slopePct);
|
|
24070
|
+
const stepScale = medianStepSize * SLOPE_VS_STEP_GATE;
|
|
24071
|
+
if (slopeMagnitude < stepScale) {
|
|
24072
|
+
trend = "neutral";
|
|
24073
|
+
}
|
|
24074
|
+
else if (slopePct > 0) {
|
|
24075
|
+
trend = "bullish";
|
|
24076
|
+
}
|
|
24077
|
+
else {
|
|
24078
|
+
trend = "bearish";
|
|
24079
|
+
}
|
|
24080
|
+
}
|
|
24081
|
+
}
|
|
24082
|
+
}
|
|
24083
|
+
const safe = (v) => v === null || !Number.isFinite(v) ? null : v;
|
|
24084
|
+
return {
|
|
24085
|
+
medianStepSize: safe(medianStepSize),
|
|
24086
|
+
buyerPressure: safe(buyerPressure),
|
|
24087
|
+
sellerPressure: safe(sellerPressure),
|
|
24088
|
+
buyerStrength: safe(buyerStrength),
|
|
24089
|
+
sellerStrength: safe(sellerStrength),
|
|
24090
|
+
pressureImbalance: safe(pressureImbalance),
|
|
24091
|
+
trend,
|
|
24092
|
+
trendStrength: safe(trendStrength),
|
|
24093
|
+
trendConfidence: safe(trendConfidence),
|
|
24094
|
+
};
|
|
24095
|
+
};
|
|
24096
|
+
|
|
23855
24097
|
/**
|
|
23856
24098
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
23857
24099
|
* Key format: "symbol:strategyName:exchangeName:frameName:backtest" or "symbol:strategyName:exchangeName:live"
|
|
@@ -23984,6 +24226,15 @@ let ReportStorage$a = class ReportStorage {
|
|
|
23984
24226
|
avgConsecutiveLossPnl: null,
|
|
23985
24227
|
avgWinDuration: null,
|
|
23986
24228
|
avgLossDuration: null,
|
|
24229
|
+
medianStepSize: null,
|
|
24230
|
+
buyerPressure: null,
|
|
24231
|
+
sellerPressure: null,
|
|
24232
|
+
buyerStrength: null,
|
|
24233
|
+
sellerStrength: null,
|
|
24234
|
+
pressureImbalance: null,
|
|
24235
|
+
trend: null,
|
|
24236
|
+
trendStrength: null,
|
|
24237
|
+
trendConfidence: null,
|
|
23987
24238
|
};
|
|
23988
24239
|
}
|
|
23989
24240
|
// Valid signal set — those with usable pendingAt AND closeTimestamp. Single source
|
|
@@ -24031,16 +24282,18 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24031
24282
|
const tradesPerYear = canAnnualize ? rawTradesPerYear : 0;
|
|
24032
24283
|
// Per-trade Sharpe Ratio (risk-free rate = 0). Sample stddev (N-1) for unbiased estimate.
|
|
24033
24284
|
// Per-trade ratios are gated by MIN_SIGNALS_FOR_RATIOS — below that, variance estimates
|
|
24034
|
-
// are too noisy to publish (high chance of spurious ±Sharpe).
|
|
24285
|
+
// are too noisy to publish (high chance of spurious ±Sharpe). When the gate fails the
|
|
24286
|
+
// standard deviation itself is reported as null (NOT 0) so the report doesn't suggest
|
|
24287
|
+
// a flat distribution for a small but variable sample.
|
|
24035
24288
|
const returns = validSignals.map((s) => s.pnl.pnlPercentage);
|
|
24036
24289
|
const canComputeRatios = totalSignals >= MIN_SIGNALS_FOR_RATIOS$2;
|
|
24037
24290
|
const stdDev = canComputeRatios
|
|
24038
24291
|
? Math.sqrt(returns.reduce((sum, r) => sum + Math.pow(r - avgPnl, 2), 0) / (totalSignals - 1))
|
|
24039
|
-
:
|
|
24292
|
+
: null;
|
|
24040
24293
|
// Use STDDEV_EPSILON gate (not stdDev > 0) — identical-returns series produce
|
|
24041
24294
|
// float-artifact stdDev (~1e-17) that's mathematically > 0 but spuriously
|
|
24042
24295
|
// inflates sharpe to astronomical magnitudes (avgPnl / epsilon).
|
|
24043
|
-
const sharpeRatio = canComputeRatios && stdDev > STDDEV_EPSILON$2
|
|
24296
|
+
const sharpeRatio = canComputeRatios && stdDev !== null && stdDev > STDDEV_EPSILON$2
|
|
24044
24297
|
? avgPnl / stdDev
|
|
24045
24298
|
: null;
|
|
24046
24299
|
// Annualize only when gate passes; otherwise null.
|
|
@@ -24280,6 +24533,13 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24280
24533
|
const recoveryFactor = !canComputeRatios || blown || equityMaxDrawdown <= 0
|
|
24281
24534
|
? null
|
|
24282
24535
|
: Math.max(-MAX_CALMAR_RATIO$2, Math.min(MAX_CALMAR_RATIO$2, ((equityFinal - 1) * 100) / equityMaxDrawdown));
|
|
24536
|
+
// Price profile — buyer/seller pressure, trend classification. Walks the
|
|
24537
|
+
// chronological close series (`orderedSignals` is already sorted by
|
|
24538
|
+
// closeTimestamp). N-gated internally by the helper (MIN_SIGNALS = 10).
|
|
24539
|
+
const priceProfile = getPriceProfile(orderedSignals.map((s) => ({
|
|
24540
|
+
closeAt: s.closeTimestamp,
|
|
24541
|
+
close: s.currentPrice,
|
|
24542
|
+
})));
|
|
24283
24543
|
return {
|
|
24284
24544
|
signalList: this._signalList,
|
|
24285
24545
|
totalSignals,
|
|
@@ -24305,6 +24565,15 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24305
24565
|
avgConsecutiveLossPnl: isUnsafe$4(avgConsecutiveLossPnl) ? null : avgConsecutiveLossPnl,
|
|
24306
24566
|
avgWinDuration: isUnsafe$4(avgWinDuration) ? null : avgWinDuration,
|
|
24307
24567
|
avgLossDuration: isUnsafe$4(avgLossDuration) ? null : avgLossDuration,
|
|
24568
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
24569
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
24570
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
24571
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
24572
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
24573
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
24574
|
+
trend: priceProfile.trend,
|
|
24575
|
+
trendStrength: priceProfile.trendStrength,
|
|
24576
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
24308
24577
|
};
|
|
24309
24578
|
}
|
|
24310
24579
|
/**
|
|
@@ -24361,20 +24630,48 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24361
24630
|
`**Avg Loss Duration:** ${stats.avgLossDuration === null ? "N/A" : `${stats.avgLossDuration.toFixed(1)} min`}`,
|
|
24362
24631
|
`**Avg Consecutive Win PNL:** ${stats.avgConsecutiveWinPnl === null ? "N/A" : `${stats.avgConsecutiveWinPnl > 0 ? "+" : ""}${stats.avgConsecutiveWinPnl.toFixed(3)}% (higher is better)`}`,
|
|
24363
24632
|
`**Avg Consecutive Loss PNL:** ${stats.avgConsecutiveLossPnl === null ? "N/A" : `${stats.avgConsecutiveLossPnl.toFixed(3)}% (closer to 0 is better)`}`,
|
|
24633
|
+
`**Trend:** ${stats.trend === null ? "N/A" : stats.trend}`,
|
|
24634
|
+
`**Trend Strength:** ${stats.trendStrength === null ? "N/A" : `${stats.trendStrength > 0 ? "+" : ""}${stats.trendStrength.toFixed(3)}%/day`}`,
|
|
24635
|
+
`**Trend Confidence (R²):** ${stats.trendConfidence === null ? "N/A" : stats.trendConfidence.toFixed(3)}`,
|
|
24636
|
+
`**Buyer Pressure:** ${stats.buyerPressure === null ? "N/A" : `${(stats.buyerPressure * 100).toFixed(1)}%`}`,
|
|
24637
|
+
`**Seller Pressure:** ${stats.sellerPressure === null ? "N/A" : `${(stats.sellerPressure * 100).toFixed(1)}%`}`,
|
|
24638
|
+
`**Buyer Strength:** ${stats.buyerStrength === null ? "N/A" : `${(stats.buyerStrength * 100).toFixed(1)}%`}`,
|
|
24639
|
+
`**Seller Strength:** ${stats.sellerStrength === null ? "N/A" : `${(stats.sellerStrength * 100).toFixed(1)}%`}`,
|
|
24640
|
+
`**Pressure Imbalance:** ${stats.pressureImbalance === null ? "N/A" : `${stats.pressureImbalance > 0 ? "+" : ""}${stats.pressureImbalance.toFixed(3)}`}`,
|
|
24641
|
+
`**Median Step Size:** ${stats.medianStepSize === null ? "N/A" : `${stats.medianStepSize.toFixed(3)}%`}`,
|
|
24364
24642
|
"",
|
|
24365
|
-
`*Win Rate:
|
|
24366
|
-
`*
|
|
24367
|
-
`*
|
|
24368
|
-
`*
|
|
24369
|
-
`*
|
|
24370
|
-
`*
|
|
24371
|
-
`*
|
|
24372
|
-
`*
|
|
24373
|
-
`*
|
|
24374
|
-
`*
|
|
24375
|
-
`*
|
|
24376
|
-
`*
|
|
24377
|
-
`*
|
|
24643
|
+
`*Win Rate: percent of closed signals that ended with per-trade PNL > 0, computed as winning-trade count / (winning-trade count + losing-trade count) × 100 — break-even trades (per-trade PNL == 0) are excluded from both numerator and denominator. UNITS: percent in [0, 100]. Statistical reliability: noisy below 30 signals (a single streak can shift it 10–20 points); stable above 200 signals.*`,
|
|
24644
|
+
`*Average PNL: arithmetic mean of per-trade PNL across every closed signal, computed as Σ per-trade PNL / closed signal count. UNITS: percent per trade. Sign mirrors strategy edge — positive = profitable on average per trade. Sensitive to one whale trade; cross-check with Median PNL to detect skew.*`,
|
|
24645
|
+
`*Total PNL: arithmetic sum of per-trade PNL across every closed signal. UNITS: percent. This is the additive total, NOT the compounded equity return — for the geometrically-compounded variant see Recovery Factor's numerator and Expected Yearly Returns. Useful as a quick scoreboard but ignores volatility drag.*`,
|
|
24646
|
+
`*Standard Deviation Per Trade: sample standard deviation (Bessel-corrected, N−1 denominator) of per-trade PNL. UNITS: percent. Measures volatility of returns. Denominator for per-trade Sharpe Ratio. Below STDDEV_EPSILON = 1e-9 it is treated as zero (identical-returns guard). Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2} (variance too noisy on small samples).*`,
|
|
24647
|
+
`*Sharpe Ratio: per-trade Sharpe = Average PNL / Standard Deviation Per Trade (risk-free rate = 0). UNITS: dimensionless ratio. Higher = better risk-adjusted return per trade. Rule of thumb: below 1.0 poor, 1.0–2.0 acceptable, above 2.0 strong. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2} OR Standard Deviation ≤ 1e-9 (identical-returns / float-artifact guard).*`,
|
|
24648
|
+
`*Annualized Sharpe Ratio: per-trade Sharpe × √(trades per year), where trades per year = closed signal count × 365 / calendar span in days. UNITS: dimensionless. Null when the closed signal count < ${MIN_SIGNALS_FOR_ANNUALIZATION$2}, OR calendar span < ${MIN_CALENDAR_SPAN_DAYS$2} days, OR raw frequency > ${MAX_TRADES_PER_YEAR$2} (clustered sample, annualisation unreliable). Assumes returns are iid — autocorrelated strategies are overstated.*`,
|
|
24649
|
+
`*Certainty Ratio: mean per-trade PNL over winning trades, divided by the absolute value of the mean per-trade PNL over losing trades. UNITS: dimensionless ratio. Below 1.0 means the typical loss exceeds the typical win; above 1.5 is generally good. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}, OR there are no losing trades, OR the absolute mean losing PNL < 1e-9 (float-artifact loss guard).*`,
|
|
24650
|
+
`*Expected Yearly Returns: geometric annualisation of the equity curve: (final equity ^ (trades per year / closed signal count) − 1) × 100, where final equity is the compounded product of (1 + per-trade PNL / 100) walked over all closed signals in chronological close order. UNITS: percent per year. Accounts for volatility drag (unlike a simple Σ × 365 / calendar-span projection). Null under the same closed-signal-count / calendar-span / frequency gates as Annualized Sharpe Ratio, AND null when |raw value| > ${MAX_EXPECTED_YEARLY_RETURNS$2}% (capped numbers mislead). −100% when the equity curve hits ≤ 0 (account blown).*`,
|
|
24651
|
+
`*Avg Peak PNL: arithmetic mean of each closed signal's peak-PNL snapshot — the best mark-to-market PNL recorded while the position was open. Signals that never recorded such a snapshot are excluded from both numerator and denominator (no zero dilution). UNITS: percent. Describes the typical best-case unrealised excursion during the position's lifetime, not the realised close. NOT gated by MIN_SIGNALS — computed whenever at least one signal carries the snapshot; null only if no signal carries it.*`,
|
|
24652
|
+
`*Avg Max Drawdown PNL: arithmetic mean of each closed signal's trough-PNL snapshot — the worst mark-to-market PNL recorded while the position was open. Signals that never recorded such a snapshot are excluded (no zero dilution). UNITS: percent (negative for losing excursions). Describes the typical worst-case unrealised PNL during the position's lifetime; closer to 0 is better. NOT gated by MIN_SIGNALS — computed whenever at least one signal carries the snapshot.*`,
|
|
24653
|
+
`*Sortino Ratio: Average PNL / downside deviation, where downside deviation = √( Σ min(0, per-trade PNL)² / closed signal count ) (canonical Sortino 1991: MAR = 0, divide by N_total). UNITS: dimensionless. Penalises only downside volatility. Rule of thumb: below 1.0 poor, 1.0–2.0 acceptable, above 2.0 strong. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}, OR there are no losing trades (downside is undefined — flawless ≠ infinitely good), OR downside deviation ≤ 1e-9 (float-artifact guard).*`,
|
|
24654
|
+
`*Calmar Ratio: Expected Yearly Returns divided by the equity-curve mark-to-market max drawdown, clamped to ±${MAX_CALMAR_RATIO$2}. The denominator is the max drawdown of the compounded equity curve: each trade's worst intra-trade excursion (the trough-PNL snapshot, ≤ 0) is applied before booking its realised close, so a position that dipped to −18% and recovered to +2% contributes a real drawdown rather than zero. UNITS: dimensionless. Rule of thumb: below 0.5 poor, 0.5–1.0 acceptable, above 1.0 strong. Null when Expected Yearly Returns is null OR the equity max drawdown ≤ 0.*`,
|
|
24655
|
+
`*Recovery Factor: (final equity − 1) × 100 divided by the equity-curve mark-to-market max drawdown, clamped to ±${MAX_CALMAR_RATIO$2}. The numerator is the compounded total return — NOT the arithmetic Total PNL shown above; the denominator is computed on the compounded curve, so mixing those units would inflate the ratio on long winning streaks. The denominator is the same mark-to-market max drawdown used by Calmar. UNITS: dimensionless. Below 1.0 = profit doesn't cover drawdown; above 3.0 = good. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}, the equity curve blew up (account ≤ 0), or the equity max drawdown ≤ 0.*`,
|
|
24656
|
+
`*Expectancy: per-trade expected value = (winning-trade count / closed signal count) × mean winning PNL + (losing-trade count / closed signal count) × mean losing PNL. Break-even trades contribute 0 (excluded from both probabilities). UNITS: percent per trade. Positive = profitable on average per trade. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}.*`,
|
|
24657
|
+
`*Median PNL: median of per-trade PNL across all closed signals (middle value after sorting; for even N the mean of the two middles). UNITS: percent. Robust to outliers — comparing it with Average PNL reveals distribution skew. A large gap (e.g. avg +1.5%, median +0.2%) means one or two trades carry the arithmetic mean. NOT gated by MIN_SIGNALS — computed whenever the closed signal count ≥ 1.*`,
|
|
24658
|
+
`*Avg Duration: arithmetic mean of (close-time − pending-time) / 60_000 over all closed signals. UNITS: minutes (synchronised with the strategy's estimated-minutes setting). Describes the typical position hold time. NOT gated by MIN_SIGNALS — computed whenever the closed signal count ≥ 1.*`,
|
|
24659
|
+
`*Avg Win Duration: arithmetic mean of (close-time − pending-time) / 60_000 restricted to winning trades (per-trade PNL > 0). UNITS: minutes. Null only when there are no winning trades (NOT gated by MIN_SIGNALS — computed at any winning-trade count ≥ 1). Pair with Avg Loss Duration to detect the classic asymmetry "let winners run, cut losers short" (Win Duration > Loss Duration is healthy) versus the inverse red flag "cut winners short, let losers run".*`,
|
|
24660
|
+
`*Avg Loss Duration: arithmetic mean of (close-time − pending-time) / 60_000 restricted to losing trades (per-trade PNL < 0). UNITS: minutes. Null only when there are no losing trades (NOT gated by MIN_SIGNALS).*`,
|
|
24661
|
+
`*Avg Consecutive Win PNL: a "win streak" is a run of consecutive trades with per-trade PNL > 0 bounded by either a losing or break-even trade; this metric sums per-trade PNL within each streak and then averages those sums. Trades are walked in chronological close order. UNITS: percent per streak. Higher = winning streaks are typically bigger. Null only when there is not a single complete win streak in the stored history (NOT gated by MIN_SIGNALS).*`,
|
|
24662
|
+
`*Avg Consecutive Loss PNL: a "loss streak" is a run of consecutive trades with per-trade PNL < 0 bounded by either a winning or break-even trade; this metric sums per-trade PNL within each streak and then averages those sums. UNITS: percent per streak (negative). Closer to 0 = losing streaks are typically smaller. Null only when there is not a single complete loss streak (NOT gated by MIN_SIGNALS).*`,
|
|
24663
|
+
`*Trend: classification computed FROM CLOSING PRICES OF CLOSED SIGNALS (one point per closed trade = the price at which it closed, with its close-timestamp — no candles, no order book, no tick stream). Bivariate gate: "sideways" when R² < 0.30 (regression too weak to call any direction). Otherwise "neutral" when |Trend Strength| < 0.25 × Median Step Size (slope detectable but smaller than the typical daily step). Otherwise "bullish" if Trend Strength > 0, "bearish" if Trend Strength < 0. Two-axis gate — never a single-axis if on a magic-constant slope magnitude. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}, which means Median Step Size, Trend Strength and Trend Confidence all gate as one bundle: if any is null the others are null too, so the classification never sees a partially-computed input. Boundary case: when every closed price is identical (a flat synthetic series at N ≥ ${MIN_SIGNALS_FOR_RATIOS$2}), Median Step Size is 0 and the slope-vs-step gate collapses — the regression then returns slope = R² = 0, R² < 0.30 fires first, and the classification is "sideways" as expected.*`,
|
|
24664
|
+
`*Trend Strength: ordinary-least-squares slope of \`log(close) ~ days\` regressed across CLOSING PRICES OF CLOSED SIGNALS (one point per closed trade in chronological order — no candles). UNITS: percent per day (small-slope approximation: log-return ≈ percent-return). NOT ATR-normalised, NOT ADX, NOT a rolling-window indicator at signal entry — a static fit over the entire stored history at report time. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2} or the calendar span is degenerate.*`,
|
|
24665
|
+
`*Trend Confidence (R²): coefficient of determination of the same log-price regression that produces Trend Strength, computed on log(close) — NOT on absolute close. UNITS: dimensionless in [0, 1]. High R² = log-price moves linearly with time (clean trend); low R² = the series is noisy regardless of slope sign. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2} or the calendar span is degenerate.*`,
|
|
24666
|
+
`*Buyer Pressure: frequency-based, computed FROM CLOSING PRICES OF CLOSED SIGNALS. (count of i where close[i] > close[i−1]) / (count of decisive moves), where "decisive" excludes flats (close[i] == close[i−1]). UNITS: dimensionless fraction in [0, 1]. NOT order-flow aggressor volume — backtest data has no order book; "buyer" labels only the SIGN of the close-to-close return. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}.*`,
|
|
24667
|
+
`*Seller Pressure: frequency-based, computed FROM CLOSING PRICES OF CLOSED SIGNALS. (count of i where close[i] < close[i−1]) / (count of decisive moves). By construction Seller Pressure = 1 − Buyer Pressure (the two sum to 1). UNITS: dimensionless fraction in [0, 1]. NOT order-flow aggressor volume — labels only the SIGN of the close-to-close return. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}.*`,
|
|
24668
|
+
`*Buyer Strength: magnitude-based, computed FROM CLOSING PRICES OF CLOSED SIGNALS. Σ |close[i] − close[i−1]| / close[i−1] over up-moves (close[i] > close[i−1]), divided by the same sum over ALL decisive moves. UNITS: dimensionless fraction in [0, 1]. Buyer Pressure (count of up-moves) and Buyer Strength (sum of up-magnitudes) use the SAME close-to-close return series; only count vs magnitude differs. A divergence between them (e.g. Pressure 0.70 with Strength 0.45) means "many small up-moves, fewer but larger down-moves" — a regime asymmetry frequency alone hides. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}.*`,
|
|
24669
|
+
`*Seller Strength: magnitude-based, computed FROM CLOSING PRICES OF CLOSED SIGNALS. Σ |close[i] − close[i−1]| / close[i−1] over down-moves (close[i] < close[i−1]), divided by the same sum over ALL decisive moves. By construction Seller Strength = 1 − Buyer Strength (the two sum to 1). UNITS: dimensionless fraction in [0, 1]. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}.*`,
|
|
24670
|
+
`*Pressure Imbalance: DIFFERENCE, not ratio: Buyer Strength − Seller Strength, computed FROM CLOSING PRICES OF CLOSED SIGNALS. Equivalent to (2 × Buyer Strength − 1). UNITS: dimensionless in [−1, +1]. Sign = direction of magnitude bias (positive = bullish bias on magnitude); absolute value = how lopsided. Single signed scalar that compresses the strength pair into one number. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}.*`,
|
|
24671
|
+
`*Median Step Size: median over i ∈ [1..N−1] of |close[i] − close[i−1]| / close[i−1], computed FROM CLOSING PRICES OF CLOSED SIGNALS — "step" = consecutive closes of CLOSED TRADES (NOT ticks, NOT bars of any timeframe — the report has no candle access). UNITS: percent, normalised by price → directly comparable across symbols at any price level. Median (not mean) → robust to one whale trade. NOT classical candle-based volatility (ATR / σ of returns); it measures the step distribution AT THE RATE TRADES CLOSE. Null when the closed signal count < ${MIN_SIGNALS_FOR_RATIOS$2}.*`,
|
|
24672
|
+
`*General reliability note: per-trade ratios (Sharpe, Sortino, Certainty, Recovery, Expectancy) are gated to N/A below ${MIN_SIGNALS_FOR_RATIOS$2} closed signals because the underlying variance estimates are too noisy. Annualised metrics (Annualized Sharpe, Expected Yearly Returns, Calmar) additionally require a calendar span ≥ ${MIN_CALENDAR_SPAN_DAYS$2} days and a raw trade frequency ≤ ${MAX_TRADES_PER_YEAR$2} per year. 100+ signals are needed for statistical reliability of the ratios; annualised metrics assume the observed frequency and market regime persist year-round.*`,
|
|
24673
|
+
`*IMPORTANT: Total PNL and the equity-curve metrics (Expected Yearly Returns, Calmar, Recovery, and the equity max drawdown that feeds them) all assume **100% capital allocation per position** (no portfolio fraction). They ignore the position-sizing subsystem (PositionSize / Kelly / ATR): per-trade PNL is a return on the position's own invested capital, never scaled by the account balance. With DCA averaging, the cost basis is the sum of all entries and the entry price is dollar-cost-weighted, so per-trade % is measured against the averaged position. If your strategy risks X% of capital per trade, the realised portfolio return / drawdown is roughly X/100 of the reported figures — these are theoretical upper bounds under full allocation.*`,
|
|
24674
|
+
`*Negative values for Sharpe / Annualized Sharpe / Sortino / Calmar / Recovery / Expectancy / Expected Yearly Returns indicate a losing strategy (Average PNL < 0 or Total PNL < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
24378
24675
|
].join("\n");
|
|
24379
24676
|
}
|
|
24380
24677
|
/**
|
|
@@ -25004,6 +25301,15 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25004
25301
|
avgConsecutiveLossPnl: null,
|
|
25005
25302
|
avgWinDuration: null,
|
|
25006
25303
|
avgLossDuration: null,
|
|
25304
|
+
medianStepSize: null,
|
|
25305
|
+
buyerPressure: null,
|
|
25306
|
+
sellerPressure: null,
|
|
25307
|
+
buyerStrength: null,
|
|
25308
|
+
sellerStrength: null,
|
|
25309
|
+
pressureImbalance: null,
|
|
25310
|
+
trend: null,
|
|
25311
|
+
trendStrength: null,
|
|
25312
|
+
trendConfidence: null,
|
|
25007
25313
|
};
|
|
25008
25314
|
}
|
|
25009
25315
|
const closedEvents = this._eventList.filter((e) => e.action === "closed");
|
|
@@ -25053,14 +25359,16 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25053
25359
|
const tradesPerYear = canAnnualize ? rawTradesPerYear : 0;
|
|
25054
25360
|
// Per-trade Sharpe Ratio (risk-free rate = 0). Sample stddev (N-1).
|
|
25055
25361
|
// Per-trade ratios are gated by MIN_SIGNALS_FOR_RATIOS — below that, variance estimates
|
|
25056
|
-
// are too noisy to publish (high chance of spurious ±Sharpe).
|
|
25362
|
+
// are too noisy to publish (high chance of spurious ±Sharpe). When the gate fails the
|
|
25363
|
+
// standard deviation itself is reported as null (NOT 0) so the report doesn't suggest
|
|
25364
|
+
// a flat distribution for a small but variable sample.
|
|
25057
25365
|
const canComputeRatios = returns.length >= MIN_SIGNALS_FOR_RATIOS$1;
|
|
25058
25366
|
const stdDev = canComputeRatios
|
|
25059
25367
|
? Math.sqrt(returns.reduce((sum, r) => sum + Math.pow(r - avgPnl, 2), 0) / (returns.length - 1))
|
|
25060
|
-
:
|
|
25368
|
+
: null;
|
|
25061
25369
|
// STDDEV_EPSILON guard — protects against float-artifact stdDev from identical
|
|
25062
25370
|
// returns producing spuriously astronomical sharpe.
|
|
25063
|
-
const sharpeRatio = canComputeRatios && stdDev > STDDEV_EPSILON$1
|
|
25371
|
+
const sharpeRatio = canComputeRatios && stdDev !== null && stdDev > STDDEV_EPSILON$1
|
|
25064
25372
|
? avgPnl / stdDev
|
|
25065
25373
|
: null;
|
|
25066
25374
|
// Annualize only when gate passes; otherwise null.
|
|
@@ -25302,6 +25610,14 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25302
25610
|
const recoveryFactor = !canComputeRatios || blown || equityMaxDrawdown <= 0
|
|
25303
25611
|
? null
|
|
25304
25612
|
: Math.max(-MAX_CALMAR_RATIO$1, Math.min(MAX_CALMAR_RATIO$1, ((equityFinal - 1) * 100) / equityMaxDrawdown));
|
|
25613
|
+
// Price profile — buyer/seller pressure, trend classification. Built only
|
|
25614
|
+
// from closed events' (timestamp, currentPrice) pairs in chronological
|
|
25615
|
+
// close order — the same data the equity curve uses. N-gated internally
|
|
25616
|
+
// by the helper (MIN_SIGNALS = 10).
|
|
25617
|
+
const priceProfile = getPriceProfile(validClosed
|
|
25618
|
+
.slice()
|
|
25619
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
25620
|
+
.map((e) => ({ closeAt: e.timestamp, close: e.currentPrice })));
|
|
25305
25621
|
return {
|
|
25306
25622
|
eventList: this._eventList,
|
|
25307
25623
|
totalEvents: this._eventList.length,
|
|
@@ -25328,6 +25644,15 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25328
25644
|
avgConsecutiveLossPnl: isUnsafe$3(avgConsecutiveLossPnl) ? null : avgConsecutiveLossPnl,
|
|
25329
25645
|
avgWinDuration: isUnsafe$3(avgWinDuration) ? null : avgWinDuration,
|
|
25330
25646
|
avgLossDuration: isUnsafe$3(avgLossDuration) ? null : avgLossDuration,
|
|
25647
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
25648
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
25649
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
25650
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
25651
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
25652
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
25653
|
+
trend: priceProfile.trend,
|
|
25654
|
+
trendStrength: priceProfile.trendStrength,
|
|
25655
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
25331
25656
|
};
|
|
25332
25657
|
}
|
|
25333
25658
|
/**
|
|
@@ -25384,20 +25709,48 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25384
25709
|
`**Avg Loss Duration:** ${stats.avgLossDuration === null ? "N/A" : `${stats.avgLossDuration.toFixed(1)} min`}`,
|
|
25385
25710
|
`**Avg Consecutive Win PNL:** ${stats.avgConsecutiveWinPnl === null ? "N/A" : `${stats.avgConsecutiveWinPnl > 0 ? "+" : ""}${stats.avgConsecutiveWinPnl.toFixed(3)}% (higher is better)`}`,
|
|
25386
25711
|
`**Avg Consecutive Loss PNL:** ${stats.avgConsecutiveLossPnl === null ? "N/A" : `${stats.avgConsecutiveLossPnl.toFixed(3)}% (closer to 0 is better)`}`,
|
|
25712
|
+
`**Trend:** ${stats.trend === null ? "N/A" : stats.trend}`,
|
|
25713
|
+
`**Trend Strength:** ${stats.trendStrength === null ? "N/A" : `${stats.trendStrength > 0 ? "+" : ""}${stats.trendStrength.toFixed(3)}%/day`}`,
|
|
25714
|
+
`**Trend Confidence (R²):** ${stats.trendConfidence === null ? "N/A" : stats.trendConfidence.toFixed(3)}`,
|
|
25715
|
+
`**Buyer Pressure:** ${stats.buyerPressure === null ? "N/A" : `${(stats.buyerPressure * 100).toFixed(1)}%`}`,
|
|
25716
|
+
`**Seller Pressure:** ${stats.sellerPressure === null ? "N/A" : `${(stats.sellerPressure * 100).toFixed(1)}%`}`,
|
|
25717
|
+
`**Buyer Strength:** ${stats.buyerStrength === null ? "N/A" : `${(stats.buyerStrength * 100).toFixed(1)}%`}`,
|
|
25718
|
+
`**Seller Strength:** ${stats.sellerStrength === null ? "N/A" : `${(stats.sellerStrength * 100).toFixed(1)}%`}`,
|
|
25719
|
+
`**Pressure Imbalance:** ${stats.pressureImbalance === null ? "N/A" : `${stats.pressureImbalance > 0 ? "+" : ""}${stats.pressureImbalance.toFixed(3)}`}`,
|
|
25720
|
+
`**Median Step Size:** ${stats.medianStepSize === null ? "N/A" : `${stats.medianStepSize.toFixed(3)}%`}`,
|
|
25387
25721
|
"",
|
|
25388
|
-
`*Win Rate:
|
|
25389
|
-
`*
|
|
25390
|
-
`*
|
|
25391
|
-
`*
|
|
25392
|
-
`*
|
|
25393
|
-
`*
|
|
25394
|
-
`*
|
|
25395
|
-
`*
|
|
25396
|
-
`*
|
|
25397
|
-
`*
|
|
25398
|
-
`*
|
|
25399
|
-
`*
|
|
25400
|
-
`*
|
|
25722
|
+
`*Win Rate: percent of closed events that ended with per-trade PNL > 0, computed as winning-event count / (winning-event count + losing-event count) × 100 — break-even closed events (per-trade PNL == 0) are excluded from both numerator and denominator. UNITS: percent in [0, 100]. Statistical reliability: noisy below 30 closed events (a single streak can shift it 10–20 points); stable above 200.*`,
|
|
25723
|
+
`*Average PNL: arithmetic mean of per-trade PNL across every closed event, computed as Σ per-trade PNL / closed event count. UNITS: percent per trade. Sign mirrors strategy edge — positive = profitable on average per trade. Sensitive to one whale trade; cross-check with Median PNL to detect skew.*`,
|
|
25724
|
+
`*Total PNL: arithmetic sum of per-trade PNL across every closed event. UNITS: percent. This is the additive total, NOT the compounded equity return — for the geometrically-compounded variant see Recovery Factor's numerator and Expected Yearly Returns. Useful as a quick scoreboard but ignores volatility drag.*`,
|
|
25725
|
+
`*Standard Deviation Per Trade: sample standard deviation (Bessel-corrected, N−1 denominator) of per-trade PNL across closed events. UNITS: percent. Measures volatility of realised returns. Denominator for per-trade Sharpe Ratio. Below STDDEV_EPSILON = 1e-9 it is treated as zero (identical-returns guard). Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}.*`,
|
|
25726
|
+
`*Sharpe Ratio: per-trade Sharpe = Average PNL / Standard Deviation Per Trade (risk-free rate = 0). UNITS: dimensionless ratio. Higher = better risk-adjusted return per trade. Rule of thumb: below 1.0 poor, 1.0–2.0 acceptable, above 2.0 strong. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1} OR Standard Deviation ≤ 1e-9 (identical-returns / float-artifact guard).*`,
|
|
25727
|
+
`*Annualized Sharpe Ratio: per-trade Sharpe × √(trades per year), where trades per year = closed event count × 365 / calendar span in days. UNITS: dimensionless. Null when the closed event count < ${MIN_SIGNALS_FOR_ANNUALIZATION$1}, OR calendar span < ${MIN_CALENDAR_SPAN_DAYS$1} days, OR raw frequency > ${MAX_TRADES_PER_YEAR$1}. Assumes returns are iid — autocorrelated strategies are overstated.*`,
|
|
25728
|
+
`*Certainty Ratio: mean per-trade PNL over winning closed events, divided by the absolute value of the mean per-trade PNL over losing closed events. UNITS: dimensionless ratio. Below 1.0 means the typical loss exceeds the typical win; above 1.5 is generally good. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}, OR there are no losing events, OR the absolute mean losing PNL < 1e-9 (float-artifact loss guard).*`,
|
|
25729
|
+
`*Expected Yearly Returns: geometric annualisation of the equity curve: (final equity ^ (trades per year / closed event count) − 1) × 100, where final equity is the compounded product of (1 + per-trade PNL / 100) walked over all closed events in chronological close order. UNITS: percent per year. Accounts for volatility drag. Null under the same closed-event-count / calendar-span / frequency gates as Annualized Sharpe Ratio, AND null when |raw value| > ${MAX_EXPECTED_YEARLY_RETURNS$1}%. −100% when the equity curve hits ≤ 0 (account blown).*`,
|
|
25730
|
+
`*Avg Peak PNL: arithmetic mean of each closed event's peak-PNL snapshot — the best mark-to-market PNL recorded while the position was open. Closed events that never recorded such a snapshot are excluded from both numerator and denominator (no zero dilution). UNITS: percent. Describes the typical best-case unrealised PNL during the position's lifetime, not the realised close. NOT gated by MIN_SIGNALS — computed whenever at least one closed event carries the snapshot.*`,
|
|
25731
|
+
`*Avg Max Drawdown PNL: arithmetic mean of each closed event's trough-PNL snapshot — the worst mark-to-market PNL recorded while the position was open. Closed events that never recorded such a snapshot are excluded (no zero dilution). UNITS: percent (negative for losing excursions). Describes the typical worst-case unrealised PNL during the position's lifetime; closer to 0 is better. NOT gated by MIN_SIGNALS — computed whenever at least one closed event carries the snapshot.*`,
|
|
25732
|
+
`*Sortino Ratio: Average PNL / downside deviation, where downside deviation = √( Σ min(0, per-trade PNL)² / closed event count ) (canonical Sortino 1991: MAR = 0, divide by N_total). UNITS: dimensionless. Penalises only downside volatility. Rule of thumb: below 1.0 poor, 1.0–2.0 acceptable, above 2.0 strong. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}, OR there are no losing events, OR downside deviation ≤ 1e-9 (float-artifact guard).*`,
|
|
25733
|
+
`*Calmar Ratio: Expected Yearly Returns divided by the equity-curve mark-to-market max drawdown, clamped to ±${MAX_CALMAR_RATIO$1}. The denominator is the max drawdown of the compounded equity curve: each closed event's worst intra-trade excursion (the trough-PNL snapshot, ≤ 0) is applied before booking its realised close, so a position that dipped to −18% and recovered to +2% contributes a real drawdown rather than zero. UNITS: dimensionless. Rule of thumb: below 0.5 poor, 0.5–1.0 acceptable, above 1.0 strong. Null when Expected Yearly Returns is null OR the equity max drawdown ≤ 0.*`,
|
|
25734
|
+
`*Recovery Factor: (final equity − 1) × 100 divided by the equity-curve mark-to-market max drawdown, clamped to ±${MAX_CALMAR_RATIO$1}. The numerator is the compounded total return — NOT the arithmetic Total PNL shown above; the denominator is computed on the compounded curve, so mixing those units would inflate the ratio on long winning streaks. The denominator is the same mark-to-market max drawdown used by Calmar. UNITS: dimensionless. Below 1.0 = profit doesn't cover drawdown; above 3.0 = good. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}, the equity curve blew up (account ≤ 0), or the equity max drawdown ≤ 0.*`,
|
|
25735
|
+
`*Expectancy: per-trade expected value = (winning-event count / closed event count) × mean winning PNL + (losing-event count / closed event count) × mean losing PNL. Break-even events contribute 0 (excluded from both probabilities). UNITS: percent per trade. Positive = profitable on average per trade. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}.*`,
|
|
25736
|
+
`*Median PNL: median of per-trade PNL across all closed events (middle value after sorting; for even N the mean of the two middles). UNITS: percent. Robust to outliers — comparing it with Average PNL reveals distribution skew. A large gap (e.g. avg +1.5%, median +0.2%) means one or two events carry the arithmetic mean. NOT gated by MIN_SIGNALS — computed whenever the closed event count ≥ 1.*`,
|
|
25737
|
+
`*Avg Duration: arithmetic mean of (close-time − pending-time) / 60_000 over all closed events. UNITS: minutes. Describes the typical position hold time. Closed events that have no pending timestamp fall back to using their own close timestamp (yielding a zero-duration contribution). NOT gated by MIN_SIGNALS — computed whenever the closed event count ≥ 1.*`,
|
|
25738
|
+
`*Avg Win Duration: arithmetic mean of (close-time − pending-time) / 60_000 restricted to winning closed events (per-trade PNL > 0). UNITS: minutes. Null only when there are no winning events (NOT gated by MIN_SIGNALS). Pair with Avg Loss Duration to detect the classic asymmetry "let winners run, cut losers short" versus the inverse "cut winners short, let losers run".*`,
|
|
25739
|
+
`*Avg Loss Duration: arithmetic mean of (close-time − pending-time) / 60_000 restricted to losing closed events (per-trade PNL < 0). UNITS: minutes. Null only when there are no losing events (NOT gated by MIN_SIGNALS).*`,
|
|
25740
|
+
`*Avg Consecutive Win PNL: a "win streak" is a run of consecutive closed events with per-trade PNL > 0 bounded by either a losing or break-even event; this metric sums per-trade PNL within each streak and then averages those sums. Events are walked in chronological close order. UNITS: percent per streak. Higher = winning streaks are typically bigger. Null only when there is not a single complete win streak (NOT gated by MIN_SIGNALS).*`,
|
|
25741
|
+
`*Avg Consecutive Loss PNL: a "loss streak" is a run of consecutive closed events with per-trade PNL < 0 bounded by either a winning or break-even event; this metric sums per-trade PNL within each streak and then averages those sums. UNITS: percent per streak (negative). Closer to 0 = losing streaks are typically smaller. Null only when there is not a single complete loss streak (NOT gated by MIN_SIGNALS).*`,
|
|
25742
|
+
`*Trend: classification computed FROM CLOSING PRICES OF CLOSED EVENTS (one point per closed event = the price at which it closed, with its close-timestamp; the live mode ingests sub-minute events of other kinds but the price profile uses ONLY closed-event prices — no candles, no order book, no tick stream). Bivariate gate: "sideways" when R² < 0.30. Otherwise "neutral" when |Trend Strength| < 0.25 × Median Step Size. Otherwise "bullish" if Trend Strength > 0, "bearish" if Trend Strength < 0. Two-axis gate — never a single-axis if on a magic-constant slope magnitude. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}, which means Median Step Size, Trend Strength and Trend Confidence all gate as one bundle: if any is null the others are null too, so the classification never sees a partially-computed input. Boundary case: when every closed price is identical (a flat synthetic series at N ≥ ${MIN_SIGNALS_FOR_RATIOS$1}), Median Step Size is 0 and the slope-vs-step gate collapses — the regression then returns slope = R² = 0, R² < 0.30 fires first, and the classification is "sideways" as expected.*`,
|
|
25743
|
+
`*Trend Strength: ordinary-least-squares slope of \`log(close) ~ days\` regressed across CLOSING PRICES OF CLOSED EVENTS in chronological order. UNITS: percent per day (small-slope approximation). NOT ATR-normalised, NOT ADX, NOT a rolling-window indicator at signal entry — a static fit over the entire stored history at report time. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1} or the calendar span is degenerate.*`,
|
|
25744
|
+
`*Trend Confidence (R²): coefficient of determination of the same log-price regression that produces Trend Strength, computed on log(close) — NOT on absolute close. UNITS: dimensionless in [0, 1]. High R² = log-price moves linearly with time (clean trend); low R² = the series is noisy regardless of slope sign. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1} or the calendar span is degenerate.*`,
|
|
25745
|
+
`*Buyer Pressure: frequency-based, computed FROM CLOSING PRICES OF CLOSED EVENTS. (count of i where close[i] > close[i−1]) / (count of decisive moves), where "decisive" excludes flats. UNITS: dimensionless fraction in [0, 1]. NOT order-flow aggressor volume — the live ingest does not separate buy/sell side; "buyer" labels only the SIGN of the close-to-close return. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}.*`,
|
|
25746
|
+
`*Seller Pressure: frequency-based, computed FROM CLOSING PRICES OF CLOSED EVENTS. (count of i where close[i] < close[i−1]) / (count of decisive moves). By construction Seller Pressure = 1 − Buyer Pressure (the two sum to 1). UNITS: dimensionless fraction in [0, 1]. NOT order-flow aggressor volume. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}.*`,
|
|
25747
|
+
`*Buyer Strength: magnitude-based, computed FROM CLOSING PRICES OF CLOSED EVENTS. Σ |close[i] − close[i−1]| / close[i−1] over up-moves, divided by the same sum over ALL decisive moves. UNITS: dimensionless fraction in [0, 1]. Buyer Pressure (count of up-moves) and Buyer Strength (sum of up-magnitudes) use the SAME close-to-close return series; only count vs magnitude differs. A divergence between them (e.g. Pressure 0.70 with Strength 0.45) means "many small up-moves, fewer but larger down-moves". Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}.*`,
|
|
25748
|
+
`*Seller Strength: magnitude-based, computed FROM CLOSING PRICES OF CLOSED EVENTS. Σ |close[i] − close[i−1]| / close[i−1] over down-moves, divided by the same sum over ALL decisive moves. By construction Seller Strength = 1 − Buyer Strength (the two sum to 1). UNITS: dimensionless fraction in [0, 1]. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}.*`,
|
|
25749
|
+
`*Pressure Imbalance: DIFFERENCE, not ratio: Buyer Strength − Seller Strength, computed FROM CLOSING PRICES OF CLOSED EVENTS. Equivalent to (2 × Buyer Strength − 1). UNITS: dimensionless in [−1, +1]. Sign = direction of magnitude bias (positive = bullish bias on magnitude); absolute value = how lopsided. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}.*`,
|
|
25750
|
+
`*Median Step Size: median over i ∈ [1..N−1] of |close[i] − close[i−1]| / close[i−1], computed FROM CLOSING PRICES OF CLOSED EVENTS — "step" = consecutive closed-event prices (NOT ticks, NOT bars; the report has no candle access even though the live mode ingests sub-minute events — they are not used here). UNITS: percent, normalised by price → directly comparable across symbols at any price level. Median (not mean) → robust to one whale trade. NOT classical candle-based volatility (ATR / σ of returns); it measures the step distribution AT THE RATE TRADES CLOSE. Null when the closed event count < ${MIN_SIGNALS_FOR_RATIOS$1}.*`,
|
|
25751
|
+
`*General reliability note: per-trade ratios (Sharpe, Sortino, Certainty, Recovery, Expectancy) are gated to N/A below ${MIN_SIGNALS_FOR_RATIOS$1} closed events. Annualised metrics additionally require a calendar span ≥ ${MIN_CALENDAR_SPAN_DAYS$1} days and a raw trade frequency ≤ ${MAX_TRADES_PER_YEAR$1} per year. 100+ closed events are needed for statistical reliability of the ratios.*`,
|
|
25752
|
+
`*IMPORTANT: Total PNL and the equity-curve metrics (Expected Yearly Returns, Calmar, Recovery, and the equity max drawdown that feeds them) all assume **100% capital allocation per position** (no portfolio fraction). They ignore the position-sizing subsystem: per-trade PNL is a return on the position's own invested capital, never scaled by the account balance. With DCA averaging the cost basis is the sum of all entries and the entry price is dollar-cost-weighted. If your strategy risks X% of capital per trade, the realised portfolio return / drawdown is roughly X/100 of the reported figures — these are theoretical upper bounds under full allocation.*`,
|
|
25753
|
+
`*Negative values for Sharpe / Annualized Sharpe / Sortino / Calmar / Recovery / Expectancy / Expected Yearly Returns indicate a losing strategy (Average PNL < 0 or Total PNL < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
25401
25754
|
].join("\n");
|
|
25402
25755
|
}
|
|
25403
25756
|
/**
|
|
@@ -25952,7 +26305,14 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
25952
26305
|
`**Average activation time:** ${stats.avgActivationTime === null ? "N/A" : `${stats.avgActivationTime.toFixed(2)} minutes`}`,
|
|
25953
26306
|
`**Average wait time (cancelled):** ${stats.avgWaitTime === null ? "N/A" : `${stats.avgWaitTime.toFixed(2)} minutes`}`,
|
|
25954
26307
|
"",
|
|
25955
|
-
`*
|
|
26308
|
+
`*Total events: integer count of every scheduled / opened / cancelled event in the ring buffer (capped at CC_MAX_SCHEDULE_MARKDOWN_ROWS, newest-first; oldest entries are dropped past the cap). UNITS: count.*`,
|
|
26309
|
+
`*Scheduled signals (raw): count of events whose action is "scheduled" in the buffer. UNITS: count. NOTE: this is the unmatched raw count — it may include records whose outcome (opened/cancelled) has not yet arrived or was evicted from the buffer. Activation / Cancellation rates do NOT use this denominator (see below).*`,
|
|
26310
|
+
`*Opened signals: count of events whose action is "opened" in the buffer. UNITS: count.*`,
|
|
26311
|
+
`*Cancelled signals: count of events whose action is "cancelled" in the buffer. UNITS: count.*`,
|
|
26312
|
+
`*Activation rate: matched-opened count / matched-resolved count × 100, where matched-opened = number of "opened" events whose signal id also appears among the buffer's "scheduled" events, matched-cancelled = same for "cancelled" events, and matched-resolved = matched-opened + matched-cancelled. The signal-id match guarantees the numerator and denominator come from the SAME population — a sliding window of CC_MAX_SCHEDULE_MARKDOWN_ROWS can otherwise drop a "scheduled" record before its outcome arrives, inflating rates above 100% or firing one rate without the other. UNITS: percent in [0, 100]. Null when matched-resolved = 0 (no matched outcomes in the buffer).*`,
|
|
26313
|
+
`*Cancellation rate: matched-cancelled count / matched-resolved count × 100 — same signal-id-matched denominator as Activation rate. UNITS: percent in [0, 100]. Null when matched-resolved = 0. By construction Activation rate + Cancellation rate = 100% (or both null together).*`,
|
|
26314
|
+
`*Average activation time: arithmetic mean of duration over events whose action is "opened" AND whose duration is a number. Events without a numeric duration are excluded from both numerator and denominator (no zero dilution). UNITS: minutes. Null when no "opened" event carries a numeric duration. The duration on an opened event represents wait time from the moment the signal was scheduled until the moment it activated (became pending).*`,
|
|
26315
|
+
`*Average wait time (cancelled): arithmetic mean of duration over events whose action is "cancelled" AND whose duration is a number. Same exclusion rule as Average activation time. UNITS: minutes. Null when no "cancelled" event carries a numeric duration. The duration on a cancelled event represents wait time from the moment the signal was scheduled until cancellation.*`,
|
|
25956
26316
|
].join("\n");
|
|
25957
26317
|
}
|
|
25958
26318
|
/**
|
|
@@ -26435,8 +26795,6 @@ class PerformanceStorage {
|
|
|
26435
26795
|
"",
|
|
26436
26796
|
summaryTable,
|
|
26437
26797
|
"",
|
|
26438
|
-
"**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type.",
|
|
26439
|
-
"",
|
|
26440
26798
|
`**Total events:** ${stats.totalEvents}`,
|
|
26441
26799
|
`**Total execution time:** ${stats.totalDuration.toFixed(2)}ms`,
|
|
26442
26800
|
`**Number of metric types:** ${Object.keys(stats.metricStats).length}`,
|
|
@@ -26445,6 +26803,17 @@ class PerformanceStorage {
|
|
|
26445
26803
|
"",
|
|
26446
26804
|
percentages.join("\n"),
|
|
26447
26805
|
"",
|
|
26806
|
+
`*Total events: integer count of every performance event in the ring buffer (capped at CC_MAX_PERFORMANCE_MARKDOWN_ROWS, newest-first; oldest entries are dropped past the cap). UNITS: count.*`,
|
|
26807
|
+
`*Total execution time: arithmetic sum of every event's duration in the buffer. UNITS: milliseconds.*`,
|
|
26808
|
+
`*Number of metric types: count of distinct metric-type labels present in the buffer. UNITS: count.*`,
|
|
26809
|
+
`*Time Distribution (per metric): the metric's Total Duration divided by overall Total Execution Time × 100; rendered as a percent next to that metric's total duration in milliseconds. Guard: when the overall execution time is 0 (all-instant operations) the percent is forced to 0 instead of NaN. UNITS: percent of total execution time.*`,
|
|
26810
|
+
`*Count (table column): integer number of events of this metric type in the buffer.*`,
|
|
26811
|
+
`*Total Duration (table column): sum of every event's duration for that metric type. UNITS: milliseconds.*`,
|
|
26812
|
+
`*Avg Duration (table column): Total Duration divided by Count for that metric type. UNITS: milliseconds.*`,
|
|
26813
|
+
`*Min / Max Duration (table columns): smallest / largest single-event duration in the metric-type bucket. UNITS: milliseconds.*`,
|
|
26814
|
+
`*Std Dev (table column): sample standard deviation (Bessel-corrected, N−1 denominator) of single-event durations for that metric type. UNITS: milliseconds. Zero when the bucket has only one event (no variance defined).*`,
|
|
26815
|
+
`*Median / P95 / P99 (table columns): percentiles of the sorted single-event durations using linear interpolation between adjacent ranks — equivalent to numpy.percentile with the default linear method. P95 / P99 represent the 95th / 99th percentile response times (tail latency). UNITS: milliseconds. Buckets of size 0 fall back to 0; size 1 returns the single value for every percentile.*`,
|
|
26816
|
+
`*Avg / Min / Max Wait Time (table columns): wait time = (event's timestamp − the previous event's timestamp) for events where a previous-timestamp value is recorded; this captures the interval between consecutive events of the same metric type. Mean / smallest / largest of those gaps. UNITS: milliseconds. All zero when no event in the bucket carries a previous-timestamp value.*`,
|
|
26448
26817
|
].join("\n");
|
|
26449
26818
|
}
|
|
26450
26819
|
/**
|
|
@@ -26904,7 +27273,17 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
26904
27273
|
"",
|
|
26905
27274
|
await this.getPnlTable(pnlColumns),
|
|
26906
27275
|
"",
|
|
26907
|
-
"
|
|
27276
|
+
"",
|
|
27277
|
+
`*Optimization Metric: the name of the per-strategy statistic that this walker run ranked strategies by. The "Best ${results.metric}" value below is the largest value of that statistic among all tested strategies (or, if the statistic is one for which "lower is better", the smallest); the strategy that produced it is reported as Best Strategy. Strategies whose ${results.metric} is N/A are skipped from "best" selection — a missing value never wins.*`,
|
|
27278
|
+
`*Strategies Tested: integer count of distinct strategies that were run during this walker comparison. UNITS: count.*`,
|
|
27279
|
+
`*Total Signals (best strategy): integer count of closed signals produced by the best strategy during this walker run.*`,
|
|
27280
|
+
`*Sortino Ratio (best strategy summary): the best strategy's Average PNL divided by downside deviation (risk-free rate = 0). Downside deviation = √( Σ min(0, per-trade PNL)² / closed signal count ) — squared sums computed over the strategy's closed signals and divided by the total signal count (canonical Sortino 1991, MAR = 0). UNITS: dimensionless. N/A when the best strategy has fewer than 10 closed signals, when it has no losing trades, or when downside deviation is below a float-artifact threshold (~1e-9). Rule of thumb: below 1.0 poor, 1.0–2.0 acceptable, above 2.0 strong.*`,
|
|
27281
|
+
`*Calmar Ratio (best strategy summary): the best strategy's Expected Yearly Returns divided by its equity max drawdown, clamped to ±1000. Expected Yearly Returns is the geometric annualisation of the strategy's compounded equity curve; the equity max drawdown is the mark-to-market max drawdown of that same curve (each trade's worst intra-trade excursion is applied as a trough before booking the realised close, so deep round-trip dips count rather than only close-to-close drops). UNITS: dimensionless. N/A when Expected Yearly Returns is N/A (e.g. too few signals, calendar span too short, or trade frequency too clustered to extrapolate) or when the equity max drawdown ≤ 0. Rule of thumb: below 0.5 poor, above 1.0 strong.*`,
|
|
27282
|
+
`*Recovery Factor (best strategy summary): the best strategy's (final equity − 1) × 100 divided by its equity max drawdown, clamped to ±1000. The numerator is the strategy's compounded total return (the equity curve's end value minus its start, in percent) — NOT the arithmetic sum of per-trade PNLs; mixing those units would inflate the ratio on long winning streaks. The denominator is the same mark-to-market max drawdown described under Calmar Ratio. UNITS: dimensionless. N/A when the best strategy has fewer than 10 closed signals, the strategy blew its account (equity ≤ 0), or the equity max drawdown ≤ 0. Rule of thumb: below 1.0 means profit doesn't cover drawdown, above 3.0 is good.*`,
|
|
27283
|
+
`*Top Strategies Comparison (table): up to N strategies (newest-best first) ranked by the optimization metric, where N is the configured top-N limit for this report. Each row shows that strategy's full statistical summary through the configured comparison columns.*`,
|
|
27284
|
+
`*All Signals (PNL Table): every closed signal produced by every tested strategy during the run, in storage order (newest first). Each row shows one closed trade — entry / exit prices, realised PNL percentage and cost, peak / trough excursions, close reason, durations — through the configured PNL columns.*`,
|
|
27285
|
+
"",
|
|
27286
|
+
"**Note:** Higher values are better for all metrics except Standard Deviation (lower is better). The per-strategy ratios shown above (Sortino, Calmar, Recovery) are forwarded verbatim from each strategy's own backtest statistics — Walker only ranks and surfaces those values, it does not recompute them. Their formulas, null-conditions and statistical gates are spelled out in the individual legend entries above."
|
|
26908
27287
|
].join("\n");
|
|
26909
27288
|
}
|
|
26910
27289
|
/**
|
|
@@ -27679,6 +28058,12 @@ class HeatmapStorage {
|
|
|
27679
28058
|
calmarRatio = null;
|
|
27680
28059
|
if (isUnsafe(recoveryFactor))
|
|
27681
28060
|
recoveryFactor = null;
|
|
28061
|
+
// Price profile — buyer/seller pressure, trend classification. Built from
|
|
28062
|
+
// the chronological close-price series of this symbol's closed signals.
|
|
28063
|
+
// N-gated internally by the helper (MIN_SIGNALS = 10).
|
|
28064
|
+
const priceProfile = getPriceProfile([...signals]
|
|
28065
|
+
.sort((a, b) => a.closeTimestamp - b.closeTimestamp)
|
|
28066
|
+
.map((s) => ({ closeAt: s.closeTimestamp, close: s.currentPrice })));
|
|
27682
28067
|
return {
|
|
27683
28068
|
symbol,
|
|
27684
28069
|
totalPnl,
|
|
@@ -27713,6 +28098,15 @@ class HeatmapStorage {
|
|
|
27713
28098
|
certaintyRatio,
|
|
27714
28099
|
expectedYearlyReturns,
|
|
27715
28100
|
tradesPerYear,
|
|
28101
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
28102
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
28103
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
28104
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
28105
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
28106
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
28107
|
+
trend: priceProfile.trend,
|
|
28108
|
+
trendStrength: priceProfile.trendStrength,
|
|
28109
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
27716
28110
|
};
|
|
27717
28111
|
}
|
|
27718
28112
|
/**
|
|
@@ -28156,27 +28550,69 @@ class HeatmapStorage {
|
|
|
28156
28550
|
"",
|
|
28157
28551
|
table,
|
|
28158
28552
|
"",
|
|
28159
|
-
`*
|
|
28160
|
-
`*
|
|
28161
|
-
`*
|
|
28162
|
-
`*
|
|
28163
|
-
`*
|
|
28164
|
-
`*Trades Per Year
|
|
28165
|
-
`*
|
|
28166
|
-
`*
|
|
28167
|
-
`*
|
|
28168
|
-
`*
|
|
28169
|
-
`*
|
|
28170
|
-
`*
|
|
28171
|
-
`*Median PNL: middle value of the
|
|
28172
|
-
`*Avg
|
|
28173
|
-
`*
|
|
28174
|
-
`*Avg Duration
|
|
28175
|
-
`*Avg Consecutive Win
|
|
28176
|
-
`*
|
|
28177
|
-
`*
|
|
28178
|
-
`*
|
|
28179
|
-
`*
|
|
28553
|
+
`*Total Symbols: count of distinct symbol buckets currently tracked in this storage. Each bucket holds at most CC_MAX_HEATMAP_MARKDOWN_ROWS closed signals (newest-first ring buffer); when capacity is reached the oldest signal is dropped. UNITS: integer count of symbols.*`,
|
|
28554
|
+
`*Portfolio PNL: arithmetic sum of per-trade PNL across every closed signal of every tracked symbol (Σ per-symbol Total PNL over symbols whose Total PNL is non-null). UNITS: percent. Additive total — NOT the compounded equity return. Useful as a quick scoreboard; ignores volatility drag and cross-symbol diversification.*`,
|
|
28555
|
+
`*Pooled Sharpe: pooled mean per-trade PNL divided by the pooled sample standard deviation (Bessel-corrected, N−1 denominator) of per-trade PNL, computed over the POOLED set of every closed signal of every symbol treated as one sample. UNITS: dimensionless ratio. NOT a Markowitz portfolio Sharpe — ignores cross-symbol correlations and capital allocation. Null when pooled trade count < ${MIN_SIGNALS_FOR_RATIOS} OR pooled standard deviation ≤ 1e-9. Rule of thumb: below 1.0 poor, 1.0–2.0 acceptable, above 2.0 strong.*`,
|
|
28556
|
+
`*Annualized Sharpe (portfolio): Pooled Sharpe × √(portfolio Trades Per Year). UNITS: dimensionless. Null when Pooled Sharpe is null OR portfolio Trades Per Year is null OR ≤ 0. Assumes returns are iid — autocorrelated strategies are overstated.*`,
|
|
28557
|
+
`*Certainty Ratio (portfolio): pooledAvgWin / |pooledAvgLoss| where pooledAvgWin = mean over pooled winning closed signals and pooledAvgLoss = mean over pooled losing closed signals. UNITS: dimensionless. Below 1.0 means the typical loss exceeds the typical win; above 1.5 is generally good. Null when pooled N < ${MIN_SIGNALS_FOR_RATIOS}, OR no losing trades in the pool, OR |pooledAvgLoss| < 1e-9.*`,
|
|
28558
|
+
`*Expected Yearly Returns (portfolio): geometric annualisation of the pooled equity curve: (pooled final equity ^ (portfolio Trades Per Year / pooled trade count) − 1) × 100, where the pooled equity curve walks every closed signal of every symbol chronologically by close-time and compounds (1 + per-trade PNL / 100). UNITS: percent per year. Null when the pooled calendar span < ${MIN_CALENDAR_SPAN_DAYS} days, raw portfolio Trades Per Year > ${MAX_TRADES_PER_YEAR}, or |raw value| > ${MAX_EXPECTED_YEARLY_RETURNS}%. −100% if the pooled equity curve hits ≤ 0.*`,
|
|
28559
|
+
`*Trades Per Year (portfolio): pooled trade count × 365 / pooled span in days, where pooled span = (latest close-time − earliest pending-time across the whole pool) / day. UNITS: trades / year. Null when pooled trade count < ${MIN_SIGNALS_FOR_RATIOS}, OR pooled span < ${MIN_CALENDAR_SPAN_DAYS} days, OR raw value > ${MAX_TRADES_PER_YEAR} (sample too clustered to extrapolate reliably). NOTE: portfolio Trades Per Year inherits the ratios-block sample-size gate (MIN_SIGNALS_FOR_RATIOS) on the pooled count, whereas the per-symbol Trades Per Year column uses the annualisation-block gate (MIN_SIGNALS_FOR_ANNUALIZATION) on the per-symbol count. The two constants are equal (${MIN_SIGNALS_FOR_RATIOS}) in this build, so this never produces a discrepancy in practice; should they ever diverge, portfolio Trades Per Year would surface earlier than per-symbol Trades Per Year, which is consistent with the rest of the portfolio block already being scoped under the ratios gate.*`,
|
|
28560
|
+
`*Total Trades (portfolio = portfolioTotalTrades): sum of per-symbol totalTrades over all tracked symbols. UNITS: integer count.*`,
|
|
28561
|
+
`*Avg Peak PNL (portfolio): trade-count-weighted mean of per-symbol Avg Peak PNL over symbols whose value is non-null. Weights are per-symbol Total Trades; symbols without any peak-snapshot signals don't contribute and don't dilute. UNITS: percent. Describes the portfolio's typical best-case unrealised excursion. NOT gated by MIN_SIGNALS at portfolio level — null only if every symbol's Avg Peak PNL is null.*`,
|
|
28562
|
+
`*Avg Max Drawdown PNL (portfolio): trade-count-weighted mean of per-symbol Avg Max Drawdown PNL over symbols whose value is non-null. UNITS: percent (negative for losing excursions). Closer to 0 is better — describes the portfolio's typical worst-case unrealised excursion. NOT gated by MIN_SIGNALS at portfolio level — null only if every symbol's Avg Max Drawdown PNL is null.*`,
|
|
28563
|
+
`*Peak Profit PNL (portfolio): MAX of per-symbol Peak Profit PNL over all symbols whose value is non-null. UNITS: percent. The single best best-case excursion observed anywhere across the portfolio (tail behaviour the average hides). NOT gated by MIN_SIGNALS at portfolio level — null only if every symbol's Peak Profit PNL is null.*`,
|
|
28564
|
+
`*Max Drawdown PNL (portfolio): MIN of per-symbol Max Drawdown PNL over all symbols whose value is non-null. UNITS: percent (negative). The single deepest unrealised dip observed anywhere across the portfolio. NOT gated by MIN_SIGNALS at portfolio level — null only if every symbol's Max Drawdown PNL is null.*`,
|
|
28565
|
+
`*Median PNL (portfolio): median of per-trade PNL over the POOLED set of every closed signal of every symbol (middle value after sorting; for even N the mean of the two middles). UNITS: percent. Robust to outliers; compare with the pooled mean per-trade PNL that underlies Pooled Sharpe to detect distribution skew (a large gap means one or two trades drag the arithmetic mean). NOT gated by MIN_SIGNALS — computed whenever pooled trade count ≥ 1.*`,
|
|
28566
|
+
`*Avg Duration (portfolio): arithmetic mean of (close-time − pending-time) / 60_000 over the pooled set of every closed signal of every symbol that has both timestamps. UNITS: minutes. NOT gated by MIN_SIGNALS — computed whenever at least one closed signal across the pool carries valid timestamps.*`,
|
|
28567
|
+
`*Avg Win Duration (portfolio): mean hold time in minutes restricted to pooled winning closed signals (per-trade PNL > 0). UNITS: minutes. Null only when there are no winning trades across the pool (NOT gated by MIN_SIGNALS).*`,
|
|
28568
|
+
`*Avg Loss Duration (portfolio): mean hold time in minutes restricted to pooled losing closed signals (per-trade PNL < 0). UNITS: minutes. Null only when there are no losing trades across the pool (NOT gated by MIN_SIGNALS). Pair with Avg Win Duration to detect the asymmetry "winner-shorter-than-loser" (a red flag: cut winners short, let losers run).*`,
|
|
28569
|
+
`*Avg Consecutive Win PNL (portfolio): trade-count-weighted mean of per-symbol Avg Consecutive Win PNL over symbols whose value is non-null. A per-symbol "win streak" is a run of consecutive same-symbol closed signals with per-trade PNL > 0 bounded by a losing or break-even trade; the per-symbol metric averages the SUM of per-trade PNL within each streak. The portfolio aggregate weights by per-symbol Total Trades — concatenating streaks ACROSS symbols would be meaningless (different markets, different timeframes). UNITS: percent per streak. NOT gated by MIN_SIGNALS at portfolio level — null only when every per-symbol Avg Consecutive Win PNL is null (i.e. no symbol has a single complete win streak).*`,
|
|
28570
|
+
`*Avg Consecutive Loss PNL (portfolio): trade-count-weighted mean of per-symbol Avg Consecutive Loss PNL over symbols whose value is non-null. Same weighting rationale as the Win-streak counterpart above. UNITS: percent per streak (negative). Closer to 0 = losing streaks are typically smaller. NOT gated by MIN_SIGNALS at portfolio level — null only when every per-symbol Avg Consecutive Loss PNL is null.*`,
|
|
28571
|
+
`*Standard Deviation Per Trade (portfolio): sample standard deviation (Bessel-corrected, N−1 denominator) of per-trade PNL over the pooled set of every closed signal of every symbol. UNITS: percent. Denominator for Pooled Sharpe. Below 1e-9 it is treated as zero (identical-returns guard). Null when pooled trade count < ${MIN_SIGNALS_FOR_RATIOS}.*`,
|
|
28572
|
+
`*Sortino Ratio (portfolio): pooled-mean per-trade PNL divided by pooled downside deviation, where downside deviation = √( Σ min(0, per-trade PNL)² / pooled trade count ) over the pool (canonical Sortino: MAR = 0, divide by N_total). UNITS: dimensionless. Penalises only downside volatility. Rule of thumb: below 1.0 poor, 1.0–2.0 acceptable, above 2.0 strong. Null when pooled trade count < ${MIN_SIGNALS_FOR_RATIOS}, OR no losing trades in the pool, OR pooled downside deviation ≤ 1e-9 (float-artifact guard).*`,
|
|
28573
|
+
`*Calmar Ratio (portfolio): portfolio Expected Yearly Returns divided by the pooled mark-to-market max drawdown, clamped to ±${MAX_CALMAR_RATIO}. The denominator is the max drawdown of the POOLED equity curve walked chronologically by close-timestamp across every symbol — each closed signal's worst intra-trade excursion (its trough-PNL snapshot, ≤ 0) is applied before booking its realised close, so deep round-trip dips count. Cross-symbol simultaneous drawdowns within the same minute are still serialised (one signal applied at a time), so genuine same-instant tail correlation is not modelled. UNITS: dimensionless. Null when the portfolio Expected Yearly Returns is null OR the pooled equity max drawdown ≤ 0.*`,
|
|
28574
|
+
`*Recovery Factor (portfolio): (pooled final equity − 1) × 100 divided by the pooled equity max drawdown, clamped to ±${MAX_CALMAR_RATIO}. The numerator is the compounded total return of the pooled equity curve — NOT the arithmetic Portfolio PNL shown at the top of this report; the denominator is computed on the compounded curve, so mixing those units would inflate the ratio on long winning streaks. The denominator is the same pooled mark-to-market max drawdown used by Calmar. UNITS: dimensionless. Below 1.0 = compounded profit doesn't cover max drawdown; above 3.0 = good. Null when pooled trade count < ${MIN_SIGNALS_FOR_RATIOS}, the pooled equity curve blew up (reached ≤ 0), or the pooled equity max drawdown ≤ 0.*`,
|
|
28575
|
+
`*Expectancy (portfolio): (pooled winning-trade count / pooled trade count) × pooled mean of winning PNLs + (pooled losing-trade count / pooled trade count) × pooled mean of losing PNLs. Break-even closed signals contribute 0 (excluded from both probabilities). UNITS: percent per trade. Positive = profitable on average per trade across the portfolio. Null when pooled trade count < ${MIN_SIGNALS_FOR_RATIOS}, OR when the entire pool is break-even (no wins AND no losses).*`,
|
|
28576
|
+
``,
|
|
28577
|
+
`*--- Per-symbol table columns ---*`,
|
|
28578
|
+
`*Total PNL (column): arithmetic sum of per-trade PNL across that symbol's stored closed signals. UNITS: percent. Additive total — NOT a compounded equity return. Null only when that symbol has zero stored signals.*`,
|
|
28579
|
+
`*Total Trades (column): integer count of that symbol's stored closed signals. UNITS: count.*`,
|
|
28580
|
+
`*Win Rate (column): per-symbol winning-trade count / (winning-trade count + losing-trade count) × 100; break-even trades (per-trade PNL == 0) are excluded from both numerator and denominator. UNITS: percent in [0, 100]. Statistically noisy below 30 per-symbol signals; stable above 200. NOT gated by MIN_SIGNALS — null only when there are no decisive trades for that symbol.*`,
|
|
28581
|
+
`*Avg PNL (column): arithmetic mean of per-trade PNL across that symbol's signals — Σ per-trade PNL / Total Trades. UNITS: percent per trade. Null only when Total Trades = 0.*`,
|
|
28582
|
+
`*Standard Deviation (column): per-symbol sample standard deviation (Bessel-corrected, N−1 denominator) of per-trade PNL. UNITS: percent. Denominator for per-symbol Sharpe Ratio. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS}.*`,
|
|
28583
|
+
`*Avg Win (column): arithmetic mean of per-trade PNL restricted to that symbol's winning trades (per-trade PNL > 0). UNITS: percent. NOT gated by MIN_SIGNALS — null only when that symbol has no winning trades.*`,
|
|
28584
|
+
`*Avg Loss (column): arithmetic mean of per-trade PNL restricted to that symbol's losing trades (per-trade PNL < 0). UNITS: percent (negative). NOT gated by MIN_SIGNALS — null only when that symbol has no losing trades.*`,
|
|
28585
|
+
`*Max Win Streak (column): longest run of consecutive same-symbol closed signals with per-trade PNL > 0, bounded by an opposite-sign or break-even trade. UNITS: integer count of consecutive wins. The streak structure is sign-invariant under reversal, so the count is identical whether signals are walked in chronological or reverse-chronological order. NOT gated by MIN_SIGNALS — 0 when that symbol has no winning trades.*`,
|
|
28586
|
+
`*Max Loss Streak (column): longest run of consecutive same-symbol closed signals with per-trade PNL < 0. UNITS: integer count. Same iteration rules as Max Win Streak. NOT gated by MIN_SIGNALS — 0 when that symbol has no losing trades.*`,
|
|
28587
|
+
`*Median PNL (column): per-symbol median of per-trade PNL (middle value after sorting; for even N the mean of the two middles). UNITS: percent. Robust to outliers; compare with Avg PNL to detect distribution skew. NOT gated by MIN_SIGNALS — null only when Total Trades = 0.*`,
|
|
28588
|
+
`*Sharpe Ratio (column): per-symbol Avg PNL / Standard Deviation (risk-free rate = 0). UNITS: dimensionless. Below 1.0 poor, 1.0–2.0 acceptable, above 2.0 strong. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS} OR Standard Deviation ≤ 1e-9 (identical-returns / float-artifact guard).*`,
|
|
28589
|
+
`*Annualized Sharpe (column): per-symbol Sharpe Ratio × √(per-symbol Trades Per Year), where Trades Per Year for that symbol = its trade count × 365 / its calendar span in days. UNITS: dimensionless. Null when per-symbol Sharpe Ratio is null, OR that symbol's Trades Per Year is null (requires trade count ≥ ${MIN_SIGNALS_FOR_ANNUALIZATION}, calendar span ≥ ${MIN_CALENDAR_SPAN_DAYS} days, raw frequency ≤ ${MAX_TRADES_PER_YEAR}). Assumes iid returns.*`,
|
|
28590
|
+
`*Certainty Ratio (column): per-symbol Avg Win / |Avg Loss|. UNITS: dimensionless. Below 1.0 = typical loss exceeds typical win; above 1.5 generally good. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS}, OR Avg Win / Avg Loss missing, OR Avg Loss ≥ 0, OR |Avg Loss| < 1e-9 (float-artifact loss guard).*`,
|
|
28591
|
+
`*Expected Yearly Returns (column): per-symbol geometric annualisation of that symbol's equity curve — (final equity ^ (Trades Per Year / trade count) − 1) × 100, where final equity is the compounded product of (1 + per-trade PNL / 100) walked over that symbol's signals in chronological close order. UNITS: percent per year. Null when trade count < ${MIN_SIGNALS_FOR_ANNUALIZATION}, calendar span < ${MIN_CALENDAR_SPAN_DAYS} days, raw frequency > ${MAX_TRADES_PER_YEAR}, or |raw value| > ${MAX_EXPECTED_YEARLY_RETURNS}%. −100% when the symbol's equity curve hits ≤ 0.*`,
|
|
28592
|
+
`*Trades Per Year (column): per-symbol trade count × 365 / calendar span in days, where calendar span = (latest close-time − earliest pending-time for that symbol) / day. UNITS: trades / year. Null when trade count < ${MIN_SIGNALS_FOR_ANNUALIZATION}, calendar span < ${MIN_CALENDAR_SPAN_DAYS} days, or raw value > ${MAX_TRADES_PER_YEAR}.*`,
|
|
28593
|
+
`*Profit Factor (column): per-symbol Σ winning per-trade PNL / |Σ losing per-trade PNL|. UNITS: dimensionless. Below 1.0 means the symbol is net losing; above 1.5 is generally good. Null when there are no winning or no losing trades for that symbol, or when Σ|losing PNL| < 1e-9 (float-artifact guard).*`,
|
|
28594
|
+
`*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
|
+
`*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
|
+
`*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.)*`,
|
|
28598
|
+
`*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
|
+
`*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
|
+
`*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.*`,
|
|
28601
|
+
`*Avg Duration / Avg Win Duration / Avg Loss Duration (columns): per-symbol mean hold time in minutes — (close-time − pending-time) / 60_000 — over all signals / winning signals / losing signals respectively. UNITS: minutes. NOT gated by MIN_SIGNALS — each is null only if the corresponding bucket (all / wins / losses) is empty for that symbol. Pair Avg Win vs Avg Loss durations to detect "let winners run, cut losers short" (Avg Win Duration > Avg Loss Duration is healthy).*`,
|
|
28602
|
+
`*Avg Consecutive Win PNL / Avg Consecutive Loss PNL (columns): a per-symbol "streak" is a run of consecutive same-symbol closed signals with same-sign per-trade PNL bounded by an opposite-sign or break-even trade; each metric sums per-trade PNL within each streak and averages those sums. The streak structure is sign-invariant under iteration order, so the resulting average is the same whether signals are walked chronologically or in reverse. UNITS: percent per streak. NOT gated by MIN_SIGNALS — each is null only if not a single complete streak of the corresponding sign exists in that symbol's history. Pair with Max Win Streak / Max Loss Streak to compare typical vs worst-case streak magnitude.*`,
|
|
28603
|
+
`*Trend (column, per-symbol only): bivariate classification computed FROM CLOSING PRICES OF CLOSED SIGNALS of that symbol (one point per closed trade = the price at which it closed, with its close-timestamp — no candles, no order book, no tick stream). "sideways" when R² < 0.30; otherwise "neutral" when |Trend Strength| < 0.25 × Median Step Size; otherwise "bullish" if Trend Strength > 0, "bearish" if Trend Strength < 0. Two-axis gate, never a single magic constant on slope magnitude. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS}, which means Median Step Size, Trend Strength and Trend Confidence all gate as one bundle: if any is null the others are null too, so the classification never sees a partially-computed input. Boundary case: when every closed price is identical (a flat synthetic series at N ≥ ${MIN_SIGNALS_FOR_RATIOS}), Median Step Size is 0 and the slope-vs-step gate collapses — the regression then returns slope = R² = 0, R² < 0.30 fires first, and the classification is "sideways" as expected. Not aggregated portfolio-wide because price series across symbols are not comparable.*`,
|
|
28604
|
+
`*Trend %/d (column, per-symbol only): ordinary-least-squares slope of \`log(close) ~ days\` regressed across CLOSING PRICES OF CLOSED SIGNALS of that symbol. UNITS: percent per day (small-slope approximation). NOT ATR-normalised, NOT ADX, NOT a rolling-window indicator — static fit at report time. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS} or the calendar span is degenerate.*`,
|
|
28605
|
+
`*Trend R² (column, per-symbol only): coefficient of determination of the same log-price regression that produces Trend %/d, computed on log(close) (NOT on absolute close). UNITS: dimensionless in [0, 1]. High R² = clean trend; low R² = noisy regardless of slope sign. Null when that symbol's trade count < ${MIN_SIGNALS_FOR_RATIOS} or the calendar span is degenerate.*`,
|
|
28606
|
+
`*Buyer Pres (column, per-symbol only): frequency-based, computed FROM CLOSING PRICES OF CLOSED SIGNALS of that symbol. (count of i where close[i] > close[i−1]) / (count of decisive moves); flats (close[i] == close[i−1]) excluded from numerator and denominator. UNITS: dimensionless fraction in [0, 1]. NOT order-flow aggressor volume — the report has no orderbook access; "buyer" labels only the SIGN of the close-to-close return.*`,
|
|
28607
|
+
`*Seller Pres (column, per-symbol only): frequency-based, computed FROM CLOSING PRICES OF CLOSED SIGNALS of that symbol. (count of i where close[i] < close[i−1]) / (count of decisive moves). By construction Buyer Pres + Seller Pres = 1. UNITS: dimensionless fraction in [0, 1].*`,
|
|
28608
|
+
`*Buyer Str (column, per-symbol only): magnitude-based, computed FROM CLOSING PRICES OF CLOSED SIGNALS of that symbol. Σ |close[i] − close[i−1]| / close[i−1] over up-moves, divided by the same sum over ALL decisive moves. UNITS: dimensionless fraction in [0, 1]. Buyer Pres (count) and Buyer Str (Σ|Δ|) use the SAME return series; divergence between them (e.g. Pres 0.70 with Str 0.45) reveals regime asymmetry: many small up-moves vs fewer but larger down-moves.*`,
|
|
28609
|
+
`*Seller Str (column, per-symbol only): magnitude-based, computed FROM CLOSING PRICES OF CLOSED SIGNALS of that symbol. Σ |close[i] − close[i−1]| / close[i−1] over down-moves, divided by the same sum over ALL decisive moves. By construction Buyer Str + Seller Str = 1.*`,
|
|
28610
|
+
`*Pres Imb (column, per-symbol only): DIFFERENCE, not ratio: Buyer Str − Seller Str = (2 × Buyer Str − 1), computed FROM CLOSING PRICES OF CLOSED SIGNALS of that symbol. UNITS: dimensionless in [−1, +1]. Sign = direction of magnitude bias.*`,
|
|
28611
|
+
`*Median Step (column, per-symbol only): median over i ∈ [1..N−1] of |close[i] − close[i−1]| / close[i−1], computed FROM CLOSING PRICES OF CLOSED SIGNALS of that symbol — "step" = consecutive trade closes (NOT ticks, NOT bars of any timeframe; the report has no candle access). UNITS: percent (normalised by price → directly comparable between symbols at any price level). Median (not mean) → robust to one whale trade. NOT classical candle-based volatility (ATR / σ of returns); it measures the step distribution AT THE RATE TRADES CLOSE.*`,
|
|
28612
|
+
``,
|
|
28613
|
+
`*General reliability note: per-symbol ratios (Sharpe, Sortino, Certainty, Recovery, Expectancy) are gated to N/A below ${MIN_SIGNALS_FOR_RATIOS} per-symbol signals. Annualised metrics (Annualized Sharpe, Expected Yearly Returns, Calmar) additionally require a calendar span ≥ ${MIN_CALENDAR_SPAN_DAYS} days and a raw trade frequency ≤ ${MAX_TRADES_PER_YEAR} per year. The same gates apply to portfolio-level pooled metrics, with the pooled trade count replacing per-symbol trade count. 100+ per-symbol signals are needed for statistical reliability of the ratios; annualised metrics assume the observed frequency and market regime persist year-round.*`,
|
|
28614
|
+
`*IMPORTANT: per-symbol AND pooled equity curve, Expected Yearly Returns, Calmar, Recovery and Max Drawdown all assume **100% capital allocation per position** (no portfolio fraction). They ignore the position-sizing subsystem (PositionSize / Kelly / ATR): per-trade PNL is a return on the position's own invested capital, never scaled by the account balance. With DCA averaging, the cost basis is the sum of all entries and the entry price is dollar-cost-weighted. If your strategy risks X% of capital per trade, the realised return / drawdown is roughly X/100 of the reported figures — these are theoretical upper bounds under full allocation.*`,
|
|
28615
|
+
`*Negative values for Sharpe / Annualized Sharpe / Sortino / Calmar / Recovery / Expectancy / Expected Yearly Returns indicate a losing symbol or portfolio (Average PNL < 0 or Total PNL < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
28180
28616
|
].join("\n");
|
|
28181
28617
|
}
|
|
28182
28618
|
/**
|
|
@@ -66976,6 +67412,7 @@ exports.getPositionPartials = getPositionPartials;
|
|
|
66976
67412
|
exports.getPositionPnlCost = getPositionPnlCost;
|
|
66977
67413
|
exports.getPositionPnlPercent = getPositionPnlPercent;
|
|
66978
67414
|
exports.getPositionWaitingMinutes = getPositionWaitingMinutes;
|
|
67415
|
+
exports.getPriceScale = getPriceScale;
|
|
66979
67416
|
exports.getRawCandles = getRawCandles;
|
|
66980
67417
|
exports.getRiskSchema = getRiskSchema;
|
|
66981
67418
|
exports.getRuntimeInfo = getRuntimeInfo;
|