backtest-kit 11.9.0 → 12.0.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 +414 -55
- package/build/index.mjs +414 -56
- 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,184 @@ MarkdownFileBase = functoolsKit.makeExtendable(MarkdownFileBase);
|
|
|
23852
23935
|
ReportBase = functoolsKit.makeExtendable(ReportBase);
|
|
23853
23936
|
const ReportWriter = new ReportWriterAdapter();
|
|
23854
23937
|
|
|
23938
|
+
/**
|
|
23939
|
+
* Price-profile metrics derived purely from a series of trade closes — no
|
|
23940
|
+
* candles, no exchange queries. Designed for `BacktestMarkdownService`,
|
|
23941
|
+
* `LiveMarkdownService` and per-symbol Heat: all three already have a
|
|
23942
|
+
* chronological series of `(closeAt, close)` points and nothing else.
|
|
23943
|
+
*
|
|
23944
|
+
* Conventions
|
|
23945
|
+
* -----------
|
|
23946
|
+
* - Pressure: fraction of up-moves vs down-moves (frequency).
|
|
23947
|
+
* - Strength: fraction of upward magnitude vs total movement.
|
|
23948
|
+
* - A divergence between pressure and strength surfaces asymmetry — e.g.
|
|
23949
|
+
* frequent shallow up-moves vs rare deep down-moves is "rising on weak
|
|
23950
|
+
* buys, falling on strong sells".
|
|
23951
|
+
* - Trend: linear regression of log(close) vs days. Slope in %/day,
|
|
23952
|
+
* confidence in R². Classification is bivariate (slope × R²): neither
|
|
23953
|
+
* axis alone fires, both must agree. Slope threshold is normalised by
|
|
23954
|
+
* medianStepSize so the metric self-tunes to the instrument's typical
|
|
23955
|
+
* move size.
|
|
23956
|
+
*/
|
|
23957
|
+
/** Minimum samples to surface any price-profile metric. Below this the
|
|
23958
|
+
* per-trade step-distribution and the regression are statistically noisy. */
|
|
23959
|
+
const MIN_SIGNALS = 10;
|
|
23960
|
+
/** R² gate for declaring any trend at all. Below this the regression is
|
|
23961
|
+
* too weak to claim a direction — treat the series as sideways even if
|
|
23962
|
+
* the slope is large. 0.30 is the conventional weak-to-moderate-fit
|
|
23963
|
+
* boundary in econometrics (Cohen's f² ≈ 0.43). */
|
|
23964
|
+
const R2_TREND_GATE = 0.30;
|
|
23965
|
+
/** Slope-magnitude threshold relative to medianStepSize for declaring the
|
|
23966
|
+
* trend strong enough to call bullish/bearish. Below this the regression
|
|
23967
|
+
* fits but the actual drift is weaker than the typical daily step, so we
|
|
23968
|
+
* downgrade to "neutral" (a real but uninteresting tilt). */
|
|
23969
|
+
const SLOPE_VS_STEP_GATE = 0.25;
|
|
23970
|
+
const isFiniteNumber = (v) => typeof v === "number" && Number.isFinite(v);
|
|
23971
|
+
const median = (values) => {
|
|
23972
|
+
const sorted = values.slice().sort((a, b) => a - b);
|
|
23973
|
+
const mid = sorted.length >> 1;
|
|
23974
|
+
return sorted.length % 2 === 0
|
|
23975
|
+
? (sorted[mid - 1] + sorted[mid]) / 2
|
|
23976
|
+
: sorted[mid];
|
|
23977
|
+
};
|
|
23978
|
+
const emptyProfile = () => ({
|
|
23979
|
+
medianStepSize: null,
|
|
23980
|
+
buyerPressure: null,
|
|
23981
|
+
sellerPressure: null,
|
|
23982
|
+
buyerStrength: null,
|
|
23983
|
+
sellerStrength: null,
|
|
23984
|
+
pressureImbalance: null,
|
|
23985
|
+
trend: null,
|
|
23986
|
+
trendStrength: null,
|
|
23987
|
+
trendConfidence: null,
|
|
23988
|
+
});
|
|
23989
|
+
/**
|
|
23990
|
+
* Computes the price-profile bundle for a chronological series of trade
|
|
23991
|
+
* closes. The input is expected to be sorted by `closeAt` ascending; the
|
|
23992
|
+
* function does not sort defensively (the markdown services already sort).
|
|
23993
|
+
*
|
|
23994
|
+
* @param series - One point per closed trade: timestamp (ms) and close price.
|
|
23995
|
+
* @returns A bundle of nine metrics, each `null` when the input is too small
|
|
23996
|
+
* or numerically unsafe.
|
|
23997
|
+
*/
|
|
23998
|
+
const getPriceProfile = (series) => {
|
|
23999
|
+
const valid = series.filter((p) => isFiniteNumber(p.closeAt) &&
|
|
24000
|
+
p.closeAt > 0 &&
|
|
24001
|
+
isFiniteNumber(p.close) &&
|
|
24002
|
+
p.close > 0);
|
|
24003
|
+
if (valid.length < MIN_SIGNALS)
|
|
24004
|
+
return emptyProfile();
|
|
24005
|
+
const n = valid.length;
|
|
24006
|
+
const closes = valid.map((p) => p.close);
|
|
24007
|
+
const times = valid.map((p) => p.closeAt);
|
|
24008
|
+
const absSteps = [];
|
|
24009
|
+
let upMoves = 0;
|
|
24010
|
+
let downMoves = 0;
|
|
24011
|
+
let upMagnitude = 0;
|
|
24012
|
+
let downMagnitude = 0;
|
|
24013
|
+
for (let i = 1; i < n; i++) {
|
|
24014
|
+
const prev = closes[i - 1];
|
|
24015
|
+
const cur = closes[i];
|
|
24016
|
+
const ret = (cur - prev) / prev;
|
|
24017
|
+
const abs = Math.abs(ret);
|
|
24018
|
+
absSteps.push(abs);
|
|
24019
|
+
if (ret > 0) {
|
|
24020
|
+
upMoves++;
|
|
24021
|
+
upMagnitude += abs;
|
|
24022
|
+
}
|
|
24023
|
+
else if (ret < 0) {
|
|
24024
|
+
downMoves++;
|
|
24025
|
+
downMagnitude += abs;
|
|
24026
|
+
}
|
|
24027
|
+
}
|
|
24028
|
+
if (absSteps.length === 0)
|
|
24029
|
+
return emptyProfile();
|
|
24030
|
+
const medianStepSize = median(absSteps) * 100; // percent
|
|
24031
|
+
// --- Pressure / strength ---
|
|
24032
|
+
const decisiveMoves = upMoves + downMoves;
|
|
24033
|
+
const totalMagnitude = upMagnitude + downMagnitude;
|
|
24034
|
+
const buyerPressure = decisiveMoves > 0 ? upMoves / decisiveMoves : null;
|
|
24035
|
+
const sellerPressure = decisiveMoves > 0 ? downMoves / decisiveMoves : null;
|
|
24036
|
+
const buyerStrength = totalMagnitude > 0 ? upMagnitude / totalMagnitude : null;
|
|
24037
|
+
const sellerStrength = totalMagnitude > 0 ? downMagnitude / totalMagnitude : null;
|
|
24038
|
+
const pressureImbalance = buyerStrength !== null && sellerStrength !== null
|
|
24039
|
+
? buyerStrength - sellerStrength
|
|
24040
|
+
: null;
|
|
24041
|
+
// --- Trend: linear regression of log(close) vs days ---
|
|
24042
|
+
// log-price slope is scale-invariant: 1%/day means the same whether the
|
|
24043
|
+
// asset trades at $0.01 or $10000. Use `closeAt[0]` as time origin so x_i
|
|
24044
|
+
// starts at zero — keeps numerical conditioning sane on long horizons.
|
|
24045
|
+
const t0 = times[0];
|
|
24046
|
+
const xs = new Array(n);
|
|
24047
|
+
const ys = new Array(n);
|
|
24048
|
+
for (let i = 0; i < n; i++) {
|
|
24049
|
+
xs[i] = (times[i] - t0) / (1000 * 60 * 60 * 24); // days
|
|
24050
|
+
ys[i] = Math.log(closes[i]);
|
|
24051
|
+
}
|
|
24052
|
+
// Calendar span must be non-degenerate for the slope to mean anything.
|
|
24053
|
+
const xRange = xs[n - 1] - xs[0];
|
|
24054
|
+
let trend = null;
|
|
24055
|
+
let trendStrength = null;
|
|
24056
|
+
let trendConfidence = null;
|
|
24057
|
+
if (xRange > 0) {
|
|
24058
|
+
let sumX = 0;
|
|
24059
|
+
let sumY = 0;
|
|
24060
|
+
for (let i = 0; i < n; i++) {
|
|
24061
|
+
sumX += xs[i];
|
|
24062
|
+
sumY += ys[i];
|
|
24063
|
+
}
|
|
24064
|
+
const meanX = sumX / n;
|
|
24065
|
+
const meanY = sumY / n;
|
|
24066
|
+
let ssXX = 0;
|
|
24067
|
+
let ssXY = 0;
|
|
24068
|
+
let ssYY = 0;
|
|
24069
|
+
for (let i = 0; i < n; i++) {
|
|
24070
|
+
const dx = xs[i] - meanX;
|
|
24071
|
+
const dy = ys[i] - meanY;
|
|
24072
|
+
ssXX += dx * dx;
|
|
24073
|
+
ssXY += dx * dy;
|
|
24074
|
+
ssYY += dy * dy;
|
|
24075
|
+
}
|
|
24076
|
+
if (ssXX > 0) {
|
|
24077
|
+
const slopeLog = ssXY / ssXX; // log-return per day
|
|
24078
|
+
const slopePct = slopeLog * 100; // %/day (small-slope approx)
|
|
24079
|
+
// R² = 1 - SS_res / SS_tot. With a single explanatory variable this
|
|
24080
|
+
// equals (ssXY)² / (ssXX * ssYY) when ssYY > 0.
|
|
24081
|
+
const r2 = ssYY > 0 ? (ssXY * ssXY) / (ssXX * ssYY) : 0;
|
|
24082
|
+
trendStrength = slopePct;
|
|
24083
|
+
trendConfidence = Math.max(0, Math.min(1, r2));
|
|
24084
|
+
if (trendConfidence < R2_TREND_GATE) {
|
|
24085
|
+
trend = "sideways";
|
|
24086
|
+
}
|
|
24087
|
+
else {
|
|
24088
|
+
const slopeMagnitude = Math.abs(slopePct);
|
|
24089
|
+
const stepScale = medianStepSize * SLOPE_VS_STEP_GATE;
|
|
24090
|
+
if (slopeMagnitude < stepScale) {
|
|
24091
|
+
trend = "neutral";
|
|
24092
|
+
}
|
|
24093
|
+
else if (slopePct > 0) {
|
|
24094
|
+
trend = "bullish";
|
|
24095
|
+
}
|
|
24096
|
+
else {
|
|
24097
|
+
trend = "bearish";
|
|
24098
|
+
}
|
|
24099
|
+
}
|
|
24100
|
+
}
|
|
24101
|
+
}
|
|
24102
|
+
const safe = (v) => v === null || !Number.isFinite(v) ? null : v;
|
|
24103
|
+
return {
|
|
24104
|
+
medianStepSize: safe(medianStepSize),
|
|
24105
|
+
buyerPressure: safe(buyerPressure),
|
|
24106
|
+
sellerPressure: safe(sellerPressure),
|
|
24107
|
+
buyerStrength: safe(buyerStrength),
|
|
24108
|
+
sellerStrength: safe(sellerStrength),
|
|
24109
|
+
pressureImbalance: safe(pressureImbalance),
|
|
24110
|
+
trend,
|
|
24111
|
+
trendStrength: safe(trendStrength),
|
|
24112
|
+
trendConfidence: safe(trendConfidence),
|
|
24113
|
+
};
|
|
24114
|
+
};
|
|
24115
|
+
|
|
23855
24116
|
/**
|
|
23856
24117
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
23857
24118
|
* Key format: "symbol:strategyName:exchangeName:frameName:backtest" or "symbol:strategyName:exchangeName:live"
|
|
@@ -23984,6 +24245,15 @@ let ReportStorage$a = class ReportStorage {
|
|
|
23984
24245
|
avgConsecutiveLossPnl: null,
|
|
23985
24246
|
avgWinDuration: null,
|
|
23986
24247
|
avgLossDuration: null,
|
|
24248
|
+
medianStepSize: null,
|
|
24249
|
+
buyerPressure: null,
|
|
24250
|
+
sellerPressure: null,
|
|
24251
|
+
buyerStrength: null,
|
|
24252
|
+
sellerStrength: null,
|
|
24253
|
+
pressureImbalance: null,
|
|
24254
|
+
trend: null,
|
|
24255
|
+
trendStrength: null,
|
|
24256
|
+
trendConfidence: null,
|
|
23987
24257
|
};
|
|
23988
24258
|
}
|
|
23989
24259
|
// Valid signal set — those with usable pendingAt AND closeTimestamp. Single source
|
|
@@ -24280,6 +24550,13 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24280
24550
|
const recoveryFactor = !canComputeRatios || blown || equityMaxDrawdown <= 0
|
|
24281
24551
|
? null
|
|
24282
24552
|
: Math.max(-MAX_CALMAR_RATIO$2, Math.min(MAX_CALMAR_RATIO$2, ((equityFinal - 1) * 100) / equityMaxDrawdown));
|
|
24553
|
+
// Price profile — buyer/seller pressure, trend classification. Walks the
|
|
24554
|
+
// chronological close series (`orderedSignals` is already sorted by
|
|
24555
|
+
// closeTimestamp). N-gated internally by the helper (MIN_SIGNALS = 10).
|
|
24556
|
+
const priceProfile = getPriceProfile(orderedSignals.map((s) => ({
|
|
24557
|
+
closeAt: s.closeTimestamp,
|
|
24558
|
+
close: s.currentPrice,
|
|
24559
|
+
})));
|
|
24283
24560
|
return {
|
|
24284
24561
|
signalList: this._signalList,
|
|
24285
24562
|
totalSignals,
|
|
@@ -24305,6 +24582,15 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24305
24582
|
avgConsecutiveLossPnl: isUnsafe$4(avgConsecutiveLossPnl) ? null : avgConsecutiveLossPnl,
|
|
24306
24583
|
avgWinDuration: isUnsafe$4(avgWinDuration) ? null : avgWinDuration,
|
|
24307
24584
|
avgLossDuration: isUnsafe$4(avgLossDuration) ? null : avgLossDuration,
|
|
24585
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
24586
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
24587
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
24588
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
24589
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
24590
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
24591
|
+
trend: priceProfile.trend,
|
|
24592
|
+
trendStrength: priceProfile.trendStrength,
|
|
24593
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
24308
24594
|
};
|
|
24309
24595
|
}
|
|
24310
24596
|
/**
|
|
@@ -24361,6 +24647,15 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24361
24647
|
`**Avg Loss Duration:** ${stats.avgLossDuration === null ? "N/A" : `${stats.avgLossDuration.toFixed(1)} min`}`,
|
|
24362
24648
|
`**Avg Consecutive Win PNL:** ${stats.avgConsecutiveWinPnl === null ? "N/A" : `${stats.avgConsecutiveWinPnl > 0 ? "+" : ""}${stats.avgConsecutiveWinPnl.toFixed(3)}% (higher is better)`}`,
|
|
24363
24649
|
`**Avg Consecutive Loss PNL:** ${stats.avgConsecutiveLossPnl === null ? "N/A" : `${stats.avgConsecutiveLossPnl.toFixed(3)}% (closer to 0 is better)`}`,
|
|
24650
|
+
`**Trend:** ${stats.trend === null ? "N/A" : stats.trend}`,
|
|
24651
|
+
`**Trend Strength:** ${stats.trendStrength === null ? "N/A" : `${stats.trendStrength > 0 ? "+" : ""}${stats.trendStrength.toFixed(3)}%/day`}`,
|
|
24652
|
+
`**Trend Confidence (R²):** ${stats.trendConfidence === null ? "N/A" : stats.trendConfidence.toFixed(3)}`,
|
|
24653
|
+
`**Buyer Pressure:** ${stats.buyerPressure === null ? "N/A" : `${(stats.buyerPressure * 100).toFixed(1)}%`}`,
|
|
24654
|
+
`**Seller Pressure:** ${stats.sellerPressure === null ? "N/A" : `${(stats.sellerPressure * 100).toFixed(1)}%`}`,
|
|
24655
|
+
`**Buyer Strength:** ${stats.buyerStrength === null ? "N/A" : `${(stats.buyerStrength * 100).toFixed(1)}%`}`,
|
|
24656
|
+
`**Seller Strength:** ${stats.sellerStrength === null ? "N/A" : `${(stats.sellerStrength * 100).toFixed(1)}%`}`,
|
|
24657
|
+
`**Pressure Imbalance:** ${stats.pressureImbalance === null ? "N/A" : `${stats.pressureImbalance > 0 ? "+" : ""}${stats.pressureImbalance.toFixed(3)}`}`,
|
|
24658
|
+
`**Median Step Size:** ${stats.medianStepSize === null ? "N/A" : `${stats.medianStepSize.toFixed(3)}%`}`,
|
|
24364
24659
|
"",
|
|
24365
24660
|
`*Win Rate: reliable above 200+ signals; below 30 signals a single streak can shift it by 10-20%.*`,
|
|
24366
24661
|
`*Sharpe Ratio: below 1.0 is poor, 1.0-2.0 is acceptable, above 2.0 is strong. Requires 30+ signals.*`,
|
|
@@ -24374,6 +24669,11 @@ let ReportStorage$a = class ReportStorage {
|
|
|
24374
24669
|
`*Expectancy: per-trade expected value (winProb × avgWin + lossProb × avgLoss). Positive = profitable on average per trade. Break-even trades contribute 0.*`,
|
|
24375
24670
|
`*All metrics require 100+ signals to be statistically reliable. Annualized metrics assume the observed trading frequency and market conditions persist year-round.*`,
|
|
24376
24671
|
`*IMPORTANT: Equity curve, Expected Yearly Returns, Calmar, Recovery and Max Drawdown all assume **100% capital allocation per position** (no portfolio fraction). These metrics ignore the position-sizing subsystem (PositionSize / Kelly / ATR): pnlPercentage is a return on the position's own invested capital, never scaled by account balance. With DCA (commitAverageBuy) 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, not a fixed stake. If your strategy risks X% of capital per trade, the realized portfolio return / drawdown will be roughly X/100 of the reported figures — these metrics represent a theoretical upper bound under full allocation.*`,
|
|
24672
|
+
`*Trend / Trend Strength / Trend Confidence: linear regression of log(close) vs days across closed-trade prices. "Trend Strength" is the slope in %/day; "Trend Confidence" is R². Classification gates on R² ≥ 0.30 (else "sideways") AND |slope| ≥ 0.25 × medianStepSize (else "neutral" — a real but weak tilt). Self-tunes to the instrument's typical move size — no magic constants on price magnitude.*`,
|
|
24673
|
+
`*Buyer / Seller Pressure: fraction of up-moves (resp. down-moves) among decisive close-to-close changes. Frequency-based; flats excluded.*`,
|
|
24674
|
+
`*Buyer / Seller Strength: share of upward (resp. downward) absolute movement in total movement. Magnitude-based. A divergence between pressure and strength surfaces asymmetry: "frequent shallow buys, rare deep sells" is a different regime than "rare deep buys, frequent shallow sells".*`,
|
|
24675
|
+
`*Pressure Imbalance: buyerStrength − sellerStrength ∈ [−1, +1]. Single signed summary of magnitude bias.*`,
|
|
24676
|
+
`*Median Step Size: median |close[i] − close[i−1]| / close[i−1] across closed trades, in %. Robust to outliers — describes the typical close-to-close jump. Used as the scale that normalises the slope-vs-step trend gate. NOTE: this is NOT classical (candle-based) volatility — there are no candles between trade closes in the report data; it measures step-distribution at the rate trades close.*`,
|
|
24377
24677
|
`*Negative values for Sharpe / Sortino / Calmar / Recovery / Expected Yearly Returns indicate a losing strategy (avgPnl < 0 or totalPnl < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
24378
24678
|
].join("\n");
|
|
24379
24679
|
}
|
|
@@ -25004,6 +25304,15 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25004
25304
|
avgConsecutiveLossPnl: null,
|
|
25005
25305
|
avgWinDuration: null,
|
|
25006
25306
|
avgLossDuration: null,
|
|
25307
|
+
medianStepSize: null,
|
|
25308
|
+
buyerPressure: null,
|
|
25309
|
+
sellerPressure: null,
|
|
25310
|
+
buyerStrength: null,
|
|
25311
|
+
sellerStrength: null,
|
|
25312
|
+
pressureImbalance: null,
|
|
25313
|
+
trend: null,
|
|
25314
|
+
trendStrength: null,
|
|
25315
|
+
trendConfidence: null,
|
|
25007
25316
|
};
|
|
25008
25317
|
}
|
|
25009
25318
|
const closedEvents = this._eventList.filter((e) => e.action === "closed");
|
|
@@ -25302,6 +25611,14 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25302
25611
|
const recoveryFactor = !canComputeRatios || blown || equityMaxDrawdown <= 0
|
|
25303
25612
|
? null
|
|
25304
25613
|
: Math.max(-MAX_CALMAR_RATIO$1, Math.min(MAX_CALMAR_RATIO$1, ((equityFinal - 1) * 100) / equityMaxDrawdown));
|
|
25614
|
+
// Price profile — buyer/seller pressure, trend classification. Built only
|
|
25615
|
+
// from closed events' (timestamp, currentPrice) pairs in chronological
|
|
25616
|
+
// close order — the same data the equity curve uses. N-gated internally
|
|
25617
|
+
// by the helper (MIN_SIGNALS = 10).
|
|
25618
|
+
const priceProfile = getPriceProfile(validClosed
|
|
25619
|
+
.slice()
|
|
25620
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
25621
|
+
.map((e) => ({ closeAt: e.timestamp, close: e.currentPrice })));
|
|
25305
25622
|
return {
|
|
25306
25623
|
eventList: this._eventList,
|
|
25307
25624
|
totalEvents: this._eventList.length,
|
|
@@ -25328,6 +25645,15 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25328
25645
|
avgConsecutiveLossPnl: isUnsafe$3(avgConsecutiveLossPnl) ? null : avgConsecutiveLossPnl,
|
|
25329
25646
|
avgWinDuration: isUnsafe$3(avgWinDuration) ? null : avgWinDuration,
|
|
25330
25647
|
avgLossDuration: isUnsafe$3(avgLossDuration) ? null : avgLossDuration,
|
|
25648
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
25649
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
25650
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
25651
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
25652
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
25653
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
25654
|
+
trend: priceProfile.trend,
|
|
25655
|
+
trendStrength: priceProfile.trendStrength,
|
|
25656
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
25331
25657
|
};
|
|
25332
25658
|
}
|
|
25333
25659
|
/**
|
|
@@ -25384,6 +25710,15 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25384
25710
|
`**Avg Loss Duration:** ${stats.avgLossDuration === null ? "N/A" : `${stats.avgLossDuration.toFixed(1)} min`}`,
|
|
25385
25711
|
`**Avg Consecutive Win PNL:** ${stats.avgConsecutiveWinPnl === null ? "N/A" : `${stats.avgConsecutiveWinPnl > 0 ? "+" : ""}${stats.avgConsecutiveWinPnl.toFixed(3)}% (higher is better)`}`,
|
|
25386
25712
|
`**Avg Consecutive Loss PNL:** ${stats.avgConsecutiveLossPnl === null ? "N/A" : `${stats.avgConsecutiveLossPnl.toFixed(3)}% (closer to 0 is better)`}`,
|
|
25713
|
+
`**Trend:** ${stats.trend === null ? "N/A" : stats.trend}`,
|
|
25714
|
+
`**Trend Strength:** ${stats.trendStrength === null ? "N/A" : `${stats.trendStrength > 0 ? "+" : ""}${stats.trendStrength.toFixed(3)}%/day`}`,
|
|
25715
|
+
`**Trend Confidence (R²):** ${stats.trendConfidence === null ? "N/A" : stats.trendConfidence.toFixed(3)}`,
|
|
25716
|
+
`**Buyer Pressure:** ${stats.buyerPressure === null ? "N/A" : `${(stats.buyerPressure * 100).toFixed(1)}%`}`,
|
|
25717
|
+
`**Seller Pressure:** ${stats.sellerPressure === null ? "N/A" : `${(stats.sellerPressure * 100).toFixed(1)}%`}`,
|
|
25718
|
+
`**Buyer Strength:** ${stats.buyerStrength === null ? "N/A" : `${(stats.buyerStrength * 100).toFixed(1)}%`}`,
|
|
25719
|
+
`**Seller Strength:** ${stats.sellerStrength === null ? "N/A" : `${(stats.sellerStrength * 100).toFixed(1)}%`}`,
|
|
25720
|
+
`**Pressure Imbalance:** ${stats.pressureImbalance === null ? "N/A" : `${stats.pressureImbalance > 0 ? "+" : ""}${stats.pressureImbalance.toFixed(3)}`}`,
|
|
25721
|
+
`**Median Step Size:** ${stats.medianStepSize === null ? "N/A" : `${stats.medianStepSize.toFixed(3)}%`}`,
|
|
25387
25722
|
"",
|
|
25388
25723
|
`*Win Rate: reliable above 200+ signals; below 30 signals a single streak can shift it by 10-20%.*`,
|
|
25389
25724
|
`*Sharpe Ratio: below 1.0 is poor, 1.0-2.0 is acceptable, above 2.0 is strong. Requires 30+ signals.*`,
|
|
@@ -25397,6 +25732,11 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
25397
25732
|
`*Expectancy: per-trade expected value (winProb × avgWin + lossProb × avgLoss). Positive = profitable on average per trade. Break-even trades contribute 0.*`,
|
|
25398
25733
|
`*All metrics require 100+ signals to be statistically reliable. Annualized metrics assume the observed trading frequency and market conditions persist year-round.*`,
|
|
25399
25734
|
`*IMPORTANT: Equity curve, Expected Yearly Returns, Calmar, Recovery and Max Drawdown all assume **100% capital allocation per position** (no portfolio fraction). These metrics ignore the position-sizing subsystem (PositionSize / Kelly / ATR): pnlPercentage is a return on the position's own invested capital, never scaled by account balance. With DCA (commitAverageBuy) 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, not a fixed stake. If your strategy risks X% of capital per trade, the realized portfolio return / drawdown will be roughly X/100 of the reported figures — these metrics represent a theoretical upper bound under full allocation.*`,
|
|
25735
|
+
`*Trend / Trend Strength / Trend Confidence: linear regression of log(close) vs days across closed-trade prices. "Trend Strength" is the slope in %/day; "Trend Confidence" is R². Classification gates on R² ≥ 0.30 (else "sideways") AND |slope| ≥ 0.25 × medianStepSize (else "neutral" — a real but weak tilt). Self-tunes to the instrument's typical move size — no magic constants on price magnitude.*`,
|
|
25736
|
+
`*Buyer / Seller Pressure: fraction of up-moves (resp. down-moves) among decisive close-to-close changes. Frequency-based; flats excluded.*`,
|
|
25737
|
+
`*Buyer / Seller Strength: share of upward (resp. downward) absolute movement in total movement. Magnitude-based. A divergence between pressure and strength surfaces asymmetry: "frequent shallow buys, rare deep sells" is a different regime than "rare deep buys, frequent shallow sells".*`,
|
|
25738
|
+
`*Pressure Imbalance: buyerStrength − sellerStrength ∈ [−1, +1]. Single signed summary of magnitude bias.*`,
|
|
25739
|
+
`*Median Step Size: median |close[i] − close[i−1]| / close[i−1] across closed trades, in %. Robust to outliers — describes the typical close-to-close jump. Used as the scale that normalises the slope-vs-step trend gate. NOTE: this is NOT classical (candle-based) volatility — there are no candles between trade closes in the report data; it measures step-distribution at the rate trades close.*`,
|
|
25400
25740
|
`*Negative values for Sharpe / Sortino / Calmar / Recovery / Expected Yearly Returns indicate a losing strategy (avgPnl < 0 or totalPnl < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
25401
25741
|
].join("\n");
|
|
25402
25742
|
}
|
|
@@ -27679,6 +28019,12 @@ class HeatmapStorage {
|
|
|
27679
28019
|
calmarRatio = null;
|
|
27680
28020
|
if (isUnsafe(recoveryFactor))
|
|
27681
28021
|
recoveryFactor = null;
|
|
28022
|
+
// Price profile — buyer/seller pressure, trend classification. Built from
|
|
28023
|
+
// the chronological close-price series of this symbol's closed signals.
|
|
28024
|
+
// N-gated internally by the helper (MIN_SIGNALS = 10).
|
|
28025
|
+
const priceProfile = getPriceProfile([...signals]
|
|
28026
|
+
.sort((a, b) => a.closeTimestamp - b.closeTimestamp)
|
|
28027
|
+
.map((s) => ({ closeAt: s.closeTimestamp, close: s.currentPrice })));
|
|
27682
28028
|
return {
|
|
27683
28029
|
symbol,
|
|
27684
28030
|
totalPnl,
|
|
@@ -27713,6 +28059,15 @@ class HeatmapStorage {
|
|
|
27713
28059
|
certaintyRatio,
|
|
27714
28060
|
expectedYearlyReturns,
|
|
27715
28061
|
tradesPerYear,
|
|
28062
|
+
medianStepSize: priceProfile.medianStepSize,
|
|
28063
|
+
buyerPressure: priceProfile.buyerPressure,
|
|
28064
|
+
sellerPressure: priceProfile.sellerPressure,
|
|
28065
|
+
buyerStrength: priceProfile.buyerStrength,
|
|
28066
|
+
sellerStrength: priceProfile.sellerStrength,
|
|
28067
|
+
pressureImbalance: priceProfile.pressureImbalance,
|
|
28068
|
+
trend: priceProfile.trend,
|
|
28069
|
+
trendStrength: priceProfile.trendStrength,
|
|
28070
|
+
trendConfidence: priceProfile.trendConfidence,
|
|
27716
28071
|
};
|
|
27717
28072
|
}
|
|
27718
28073
|
/**
|
|
@@ -28176,6 +28531,9 @@ class HeatmapStorage {
|
|
|
28176
28531
|
`*Max Drawdown: mark-to-market — both the per-symbol and pooled equity curves apply each trade's worst intra-trade excursion (the lowest unrealized point while the position was open) before booking its realized close, so deep round-trip dips count. It is NOT realized-only (close-to-close); a realized-only curve would understate drawdown and inflate Calmar/Recovery. The pooled curve walks trades chronologically by closeTimestamp; simultaneous cross-symbol drawdowns within the same minute are still serialised (one trade applied at a time), so genuine same-instant tail correlation is not modelled.*`,
|
|
28177
28532
|
`*All metrics require 100+ signals per symbol to be statistically reliable. Annualized metrics assume the observed trading frequency persists year-round.*`,
|
|
28178
28533
|
`*IMPORTANT: Per-symbol equity curve, Expected Yearly Returns, Calmar, Recovery and Max Drawdown all assume **100% capital allocation per position** (no portfolio fraction). These metrics ignore the position-sizing subsystem (PositionSize / Kelly / ATR): pnlPercentage is a return on the position's own invested capital, never scaled by account balance. With DCA (commitAverageBuy) 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, not a fixed stake. If your strategy risks X% of capital per trade, the realized return / drawdown will be roughly X/100 of the reported figures — these metrics represent a theoretical upper bound under full allocation.*`,
|
|
28534
|
+
`*Trend / Trend %/d / Trend R² (per-symbol only): linear regression of log(close) vs days across that symbol's closed-trade prices. Classification gates on R² ≥ 0.30 AND |slope| ≥ 0.25 × medianStepSize — self-tunes to the instrument. Not aggregated portfolio-wide (price series across symbols are not comparable).*`,
|
|
28535
|
+
`*Buyer / Seller Pres (per-symbol only): fraction of up-moves (resp. down-moves) among decisive close-to-close changes for that symbol. Buyer / Seller Str: magnitude share. Pres Imb: buyerStrength − sellerStrength ∈ [−1, +1].*`,
|
|
28536
|
+
`*Median Step (per-symbol only): typical |close[i] − close[i−1]| / close[i−1] across closed trades, in %. Robust to outliers. NOT classical volatility — there are no candles between closes in the report data.*`,
|
|
28179
28537
|
`*Negative values for Sharpe / Annualized Sharpe / Sortino / Calmar / Recovery / Expectancy / Expected Yearly Returns indicate a losing symbol (avgPnl < 0 or totalPnl < 0). "Higher is better" still applies — closer to zero is less bad, positive is profitable.*`,
|
|
28180
28538
|
].join("\n");
|
|
28181
28539
|
}
|
|
@@ -66976,6 +67334,7 @@ exports.getPositionPartials = getPositionPartials;
|
|
|
66976
67334
|
exports.getPositionPnlCost = getPositionPnlCost;
|
|
66977
67335
|
exports.getPositionPnlPercent = getPositionPnlPercent;
|
|
66978
67336
|
exports.getPositionWaitingMinutes = getPositionWaitingMinutes;
|
|
67337
|
+
exports.getPriceScale = getPriceScale;
|
|
66979
67338
|
exports.getRawCandles = getRawCandles;
|
|
66980
67339
|
exports.getRiskSchema = getRiskSchema;
|
|
66981
67340
|
exports.getRuntimeInfo = getRuntimeInfo;
|