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 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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD` : "N/A",
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(8)} USD`
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(8)} USD` : "N/A",
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(8)} USD`
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(8)} USD`
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(8)} USD`
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(8)} USD`,
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD`,
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(8)} USD`,
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD` : "N/A"),
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(8)} USD`
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(8)} USD`
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(8)} USD`
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(8)} USD`
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(8)} USD`
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(8)} USD`
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(8)} USD`,
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(8)} USD` : "N/A",
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`
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(8)} USD`
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(8)} USD`
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(8)} USD` : "N/A"),
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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(8)} USD`,
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;