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.mjs CHANGED
@@ -21041,6 +21041,24 @@ class WalkerCommandService {
21041
21041
  }
21042
21042
  }
21043
21043
 
21044
+ /**
21045
+ * Derives the number of decimal places to show for a price based on the
21046
+ * magnitude of its integer part. Larger prices need fewer decimals; sub-dollar
21047
+ * prices keep full precision.
21048
+ * @param value - The price to derive the decimal scale for
21049
+ * @returns The number of digits after the decimal point
21050
+ */
21051
+ const getPriceScale = (value) => {
21052
+ const abs = Math.abs(value);
21053
+ if (abs >= 1) {
21054
+ // 1..9 -> 4, 10..99 -> 3, 100..999 -> 2, 1000+ -> 2 (floor), capped at 2
21055
+ const digits = Math.floor(Math.log10(abs)) + 1;
21056
+ return Math.max(2, 6 - digits);
21057
+ }
21058
+ // Sub-dollar prices need more precision.
21059
+ return 8;
21060
+ };
21061
+
21044
21062
  /**
21045
21063
  * Converts markdown content to plain text with minimal formatting
21046
21064
  * @param content - Markdown string to convert
@@ -21149,43 +21167,43 @@ const backtest_columns = [
21149
21167
  {
21150
21168
  key: "openPrice",
21151
21169
  label: "Open Price",
21152
- format: (data) => `${data.signal.priceOpen.toFixed(8)} USD`,
21170
+ format: (data) => `${data.signal.priceOpen.toFixed(getPriceScale(data.signal.priceOpen))} USD`,
21153
21171
  isVisible: () => true,
21154
21172
  },
21155
21173
  {
21156
21174
  key: "closePrice",
21157
21175
  label: "Close Price",
21158
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
21176
+ format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
21159
21177
  isVisible: () => true,
21160
21178
  },
21161
21179
  {
21162
21180
  key: "takeProfit",
21163
21181
  label: "Take Profit",
21164
- format: (data) => `${data.signal.priceTakeProfit.toFixed(8)} USD`,
21182
+ format: (data) => `${data.signal.priceTakeProfit.toFixed(getPriceScale(data.signal.priceTakeProfit))} USD`,
21165
21183
  isVisible: () => true,
21166
21184
  },
21167
21185
  {
21168
21186
  key: "stopLoss",
21169
21187
  label: "Stop Loss",
21170
- format: (data) => `${data.signal.priceStopLoss.toFixed(8)} USD`,
21188
+ format: (data) => `${data.signal.priceStopLoss.toFixed(getPriceScale(data.signal.priceStopLoss))} USD`,
21171
21189
  isVisible: () => true,
21172
21190
  },
21173
21191
  {
21174
21192
  key: "originalPriceTakeProfit",
21175
21193
  label: "Original TP",
21176
- format: (data) => `${data.signal.originalPriceTakeProfit.toFixed(8)} USD`,
21194
+ format: (data) => `${data.signal.originalPriceTakeProfit.toFixed(getPriceScale(data.signal.originalPriceTakeProfit))} USD`,
21177
21195
  isVisible: () => true,
21178
21196
  },
21179
21197
  {
21180
21198
  key: "originalPriceStopLoss",
21181
21199
  label: "Original SL",
21182
- format: (data) => `${data.signal.originalPriceStopLoss.toFixed(8)} USD`,
21200
+ format: (data) => `${data.signal.originalPriceStopLoss.toFixed(getPriceScale(data.signal.originalPriceStopLoss))} USD`,
21183
21201
  isVisible: () => true,
21184
21202
  },
21185
21203
  {
21186
21204
  key: "originalPriceOpen",
21187
21205
  label: "Original Entry",
21188
- format: (data) => `${data.signal.originalPriceOpen.toFixed(8)} USD`,
21206
+ format: (data) => `${data.signal.originalPriceOpen.toFixed(getPriceScale(data.signal.originalPriceOpen))} USD`,
21189
21207
  isVisible: () => true,
21190
21208
  },
21191
21209
  {
@@ -21488,6 +21506,71 @@ const heat_columns = [
21488
21506
  : "N/A",
21489
21507
  isVisible: () => true,
21490
21508
  },
21509
+ {
21510
+ key: "trend",
21511
+ label: "Trend",
21512
+ format: (data) => data.trend ?? "N/A",
21513
+ isVisible: () => true,
21514
+ },
21515
+ {
21516
+ key: "trendStrength",
21517
+ label: "Trend %/d",
21518
+ format: (data) => data.trendStrength !== null ? str(data.trendStrength, "%") : "N/A",
21519
+ isVisible: () => true,
21520
+ },
21521
+ {
21522
+ key: "trendConfidence",
21523
+ label: "Trend R²",
21524
+ format: (data) => data.trendConfidence !== null ? data.trendConfidence.toFixed(3) : "N/A",
21525
+ isVisible: () => true,
21526
+ },
21527
+ {
21528
+ key: "buyerPressure",
21529
+ label: "Buyer Pres",
21530
+ format: (data) => data.buyerPressure !== null
21531
+ ? (data.buyerPressure * 100).toFixed(1) + "%"
21532
+ : "N/A",
21533
+ isVisible: () => true,
21534
+ },
21535
+ {
21536
+ key: "sellerPressure",
21537
+ label: "Seller Pres",
21538
+ format: (data) => data.sellerPressure !== null
21539
+ ? (data.sellerPressure * 100).toFixed(1) + "%"
21540
+ : "N/A",
21541
+ isVisible: () => true,
21542
+ },
21543
+ {
21544
+ key: "buyerStrength",
21545
+ label: "Buyer Str",
21546
+ format: (data) => data.buyerStrength !== null
21547
+ ? (data.buyerStrength * 100).toFixed(1) + "%"
21548
+ : "N/A",
21549
+ isVisible: () => true,
21550
+ },
21551
+ {
21552
+ key: "sellerStrength",
21553
+ label: "Seller Str",
21554
+ format: (data) => data.sellerStrength !== null
21555
+ ? (data.sellerStrength * 100).toFixed(1) + "%"
21556
+ : "N/A",
21557
+ isVisible: () => true,
21558
+ },
21559
+ {
21560
+ key: "pressureImbalance",
21561
+ label: "Pres Imb",
21562
+ format: (data) => data.pressureImbalance !== null
21563
+ ? (data.pressureImbalance > 0 ? "+" : "") +
21564
+ data.pressureImbalance.toFixed(3)
21565
+ : "N/A",
21566
+ isVisible: () => true,
21567
+ },
21568
+ {
21569
+ key: "medianStepSize",
21570
+ label: "Median Step",
21571
+ format: (data) => data.medianStepSize !== null ? str(data.medianStepSize, "%") : "N/A",
21572
+ isVisible: () => true,
21573
+ },
21491
21574
  {
21492
21575
  key: "sortinoRatio",
21493
21576
  label: "Sortino",
@@ -21584,34 +21667,34 @@ const live_columns = [
21584
21667
  {
21585
21668
  key: "currentPrice",
21586
21669
  label: "Current Price",
21587
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
21670
+ format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
21588
21671
  isVisible: () => true,
21589
21672
  },
21590
21673
  {
21591
21674
  key: "openPrice",
21592
21675
  label: "Open Price",
21593
- format: (data) => data.priceOpen !== undefined ? `${data.priceOpen.toFixed(8)} USD` : "N/A",
21676
+ format: (data) => data.priceOpen !== undefined ? `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD` : "N/A",
21594
21677
  isVisible: () => true,
21595
21678
  },
21596
21679
  {
21597
21680
  key: "takeProfit",
21598
21681
  label: "Take Profit",
21599
21682
  format: (data) => data.priceTakeProfit !== undefined
21600
- ? `${data.priceTakeProfit.toFixed(8)} USD`
21683
+ ? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`
21601
21684
  : "N/A",
21602
21685
  isVisible: () => true,
21603
21686
  },
21604
21687
  {
21605
21688
  key: "stopLoss",
21606
21689
  label: "Stop Loss",
21607
- format: (data) => data.priceStopLoss !== undefined ? `${data.priceStopLoss.toFixed(8)} USD` : "N/A",
21690
+ format: (data) => data.priceStopLoss !== undefined ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A",
21608
21691
  isVisible: () => true,
21609
21692
  },
21610
21693
  {
21611
21694
  key: "originalPriceTakeProfit",
21612
21695
  label: "Original TP",
21613
21696
  format: (data) => data.originalPriceTakeProfit !== undefined
21614
- ? `${data.originalPriceTakeProfit.toFixed(8)} USD`
21697
+ ? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD`
21615
21698
  : "N/A",
21616
21699
  isVisible: () => true,
21617
21700
  },
@@ -21619,7 +21702,7 @@ const live_columns = [
21619
21702
  key: "originalPriceStopLoss",
21620
21703
  label: "Original SL",
21621
21704
  format: (data) => data.originalPriceStopLoss !== undefined
21622
- ? `${data.originalPriceStopLoss.toFixed(8)} USD`
21705
+ ? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD`
21623
21706
  : "N/A",
21624
21707
  isVisible: () => true,
21625
21708
  },
@@ -21627,7 +21710,7 @@ const live_columns = [
21627
21710
  key: "originalPriceOpen",
21628
21711
  label: "Original Entry",
21629
21712
  format: (data) => data.originalPriceOpen !== undefined
21630
- ? `${data.originalPriceOpen.toFixed(8)} USD`
21713
+ ? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD`
21631
21714
  : "N/A",
21632
21715
  isVisible: () => true,
21633
21716
  },
@@ -21798,43 +21881,43 @@ const partial_columns = [
21798
21881
  {
21799
21882
  key: "currentPrice",
21800
21883
  label: "Current Price",
21801
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
21884
+ format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
21802
21885
  isVisible: () => true,
21803
21886
  },
21804
21887
  {
21805
21888
  key: "priceOpen",
21806
21889
  label: "Entry Price",
21807
- format: (data) => (data.priceOpen ? `${data.priceOpen.toFixed(8)} USD` : "N/A"),
21890
+ format: (data) => (data.priceOpen ? `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD` : "N/A"),
21808
21891
  isVisible: () => true,
21809
21892
  },
21810
21893
  {
21811
21894
  key: "priceTakeProfit",
21812
21895
  label: "Take Profit",
21813
- format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(8)} USD` : "N/A"),
21896
+ format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD` : "N/A"),
21814
21897
  isVisible: () => true,
21815
21898
  },
21816
21899
  {
21817
21900
  key: "priceStopLoss",
21818
21901
  label: "Stop Loss",
21819
- format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(8)} USD` : "N/A"),
21902
+ format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A"),
21820
21903
  isVisible: () => true,
21821
21904
  },
21822
21905
  {
21823
21906
  key: "originalPriceTakeProfit",
21824
21907
  label: "Original TP",
21825
- format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(8)} USD` : "N/A"),
21908
+ format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD` : "N/A"),
21826
21909
  isVisible: () => true,
21827
21910
  },
21828
21911
  {
21829
21912
  key: "originalPriceStopLoss",
21830
21913
  label: "Original SL",
21831
- format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
21914
+ format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD` : "N/A"),
21832
21915
  isVisible: () => true,
21833
21916
  },
21834
21917
  {
21835
21918
  key: "originalPriceOpen",
21836
21919
  label: "Original Entry",
21837
- format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(8)} USD` : "N/A"),
21920
+ format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD` : "N/A"),
21838
21921
  isVisible: () => true,
21839
21922
  },
21840
21923
  {
@@ -21958,43 +22041,43 @@ const breakeven_columns = [
21958
22041
  {
21959
22042
  key: "priceOpen",
21960
22043
  label: "Entry Price",
21961
- format: (data) => `${data.priceOpen.toFixed(8)} USD`,
22044
+ format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
21962
22045
  isVisible: () => true,
21963
22046
  },
21964
22047
  {
21965
22048
  key: "currentPrice",
21966
22049
  label: "Breakeven Price",
21967
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
22050
+ format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
21968
22051
  isVisible: () => true,
21969
22052
  },
21970
22053
  {
21971
22054
  key: "priceTakeProfit",
21972
22055
  label: "Take Profit",
21973
- format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(8)} USD` : "N/A"),
22056
+ format: (data) => (data.priceTakeProfit ? `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD` : "N/A"),
21974
22057
  isVisible: () => true,
21975
22058
  },
21976
22059
  {
21977
22060
  key: "priceStopLoss",
21978
22061
  label: "Stop Loss",
21979
- format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(8)} USD` : "N/A"),
22062
+ format: (data) => (data.priceStopLoss ? `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD` : "N/A"),
21980
22063
  isVisible: () => true,
21981
22064
  },
21982
22065
  {
21983
22066
  key: "originalPriceTakeProfit",
21984
22067
  label: "Original TP",
21985
- format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(8)} USD` : "N/A"),
22068
+ format: (data) => (data.originalPriceTakeProfit ? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD` : "N/A"),
21986
22069
  isVisible: () => true,
21987
22070
  },
21988
22071
  {
21989
22072
  key: "originalPriceStopLoss",
21990
22073
  label: "Original SL",
21991
- format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
22074
+ format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD` : "N/A"),
21992
22075
  isVisible: () => true,
21993
22076
  },
21994
22077
  {
21995
22078
  key: "originalPriceOpen",
21996
22079
  label: "Original Entry",
21997
- format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(8)} USD` : "N/A"),
22080
+ format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD` : "N/A"),
21998
22081
  isVisible: () => true,
21999
22082
  },
22000
22083
  {
@@ -22251,7 +22334,7 @@ const risk_columns = [
22251
22334
  key: "openPrice",
22252
22335
  label: "Open Price",
22253
22336
  format: (data) => data.currentSignal.priceOpen !== undefined
22254
- ? `${data.currentSignal.priceOpen.toFixed(8)} USD`
22337
+ ? `${data.currentSignal.priceOpen.toFixed(getPriceScale(data.currentSignal.priceOpen))} USD`
22255
22338
  : "N/A",
22256
22339
  isVisible: () => true,
22257
22340
  },
@@ -22259,7 +22342,7 @@ const risk_columns = [
22259
22342
  key: "takeProfit",
22260
22343
  label: "Take Profit",
22261
22344
  format: (data) => data.currentSignal.priceTakeProfit !== undefined
22262
- ? `${data.currentSignal.priceTakeProfit.toFixed(8)} USD`
22345
+ ? `${data.currentSignal.priceTakeProfit.toFixed(getPriceScale(data.currentSignal.priceTakeProfit))} USD`
22263
22346
  : "N/A",
22264
22347
  isVisible: () => true,
22265
22348
  },
@@ -22267,7 +22350,7 @@ const risk_columns = [
22267
22350
  key: "stopLoss",
22268
22351
  label: "Stop Loss",
22269
22352
  format: (data) => data.currentSignal.priceStopLoss !== undefined
22270
- ? `${data.currentSignal.priceStopLoss.toFixed(8)} USD`
22353
+ ? `${data.currentSignal.priceStopLoss.toFixed(getPriceScale(data.currentSignal.priceStopLoss))} USD`
22271
22354
  : "N/A",
22272
22355
  isVisible: () => true,
22273
22356
  },
@@ -22275,7 +22358,7 @@ const risk_columns = [
22275
22358
  key: "originalPriceTakeProfit",
22276
22359
  label: "Original TP",
22277
22360
  format: (data) => data.currentSignal.originalPriceTakeProfit !== undefined
22278
- ? `${data.currentSignal.originalPriceTakeProfit.toFixed(8)} USD`
22361
+ ? `${data.currentSignal.originalPriceTakeProfit.toFixed(getPriceScale(data.currentSignal.originalPriceTakeProfit))} USD`
22279
22362
  : "N/A",
22280
22363
  isVisible: () => true,
22281
22364
  },
@@ -22283,7 +22366,7 @@ const risk_columns = [
22283
22366
  key: "originalPriceStopLoss",
22284
22367
  label: "Original SL",
22285
22368
  format: (data) => data.currentSignal.originalPriceStopLoss !== undefined
22286
- ? `${data.currentSignal.originalPriceStopLoss.toFixed(8)} USD`
22369
+ ? `${data.currentSignal.originalPriceStopLoss.toFixed(getPriceScale(data.currentSignal.originalPriceStopLoss))} USD`
22287
22370
  : "N/A",
22288
22371
  isVisible: () => true,
22289
22372
  },
@@ -22291,7 +22374,7 @@ const risk_columns = [
22291
22374
  key: "originalPriceOpen",
22292
22375
  label: "Original Entry",
22293
22376
  format: (data) => data.currentSignal.originalPriceOpen !== undefined
22294
- ? `${data.currentSignal.originalPriceOpen.toFixed(8)} USD`
22377
+ ? `${data.currentSignal.originalPriceOpen.toFixed(getPriceScale(data.currentSignal.originalPriceOpen))} USD`
22295
22378
  : "N/A",
22296
22379
  isVisible: () => true,
22297
22380
  },
@@ -22322,7 +22405,7 @@ const risk_columns = [
22322
22405
  {
22323
22406
  key: "currentPrice",
22324
22407
  label: "Current Price",
22325
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
22408
+ format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
22326
22409
  isVisible: () => true,
22327
22410
  },
22328
22411
  {
@@ -22448,32 +22531,32 @@ const schedule_columns = [
22448
22531
  {
22449
22532
  key: "currentPrice",
22450
22533
  label: "Current Price",
22451
- format: (data) => data.currentPrice ? `${data.currentPrice.toFixed(8)} USD` : "N/A",
22534
+ format: (data) => data.currentPrice ? `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD` : "N/A",
22452
22535
  isVisible: () => true,
22453
22536
  },
22454
22537
  {
22455
22538
  key: "priceOpen",
22456
22539
  label: "Entry Price",
22457
- format: (data) => `${data.priceOpen.toFixed(8)} USD`,
22540
+ format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
22458
22541
  isVisible: () => true,
22459
22542
  },
22460
22543
  {
22461
22544
  key: "takeProfit",
22462
22545
  label: "Take Profit",
22463
- format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
22546
+ format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
22464
22547
  isVisible: () => true,
22465
22548
  },
22466
22549
  {
22467
22550
  key: "stopLoss",
22468
22551
  label: "Stop Loss",
22469
- format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
22552
+ format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
22470
22553
  isVisible: () => true,
22471
22554
  },
22472
22555
  {
22473
22556
  key: "originalPriceTakeProfit",
22474
22557
  label: "Original TP",
22475
22558
  format: (data) => data.originalPriceTakeProfit !== undefined
22476
- ? `${data.originalPriceTakeProfit.toFixed(8)} USD`
22559
+ ? `${data.originalPriceTakeProfit.toFixed(getPriceScale(data.originalPriceTakeProfit))} USD`
22477
22560
  : "N/A",
22478
22561
  isVisible: () => true,
22479
22562
  },
@@ -22481,7 +22564,7 @@ const schedule_columns = [
22481
22564
  key: "originalPriceStopLoss",
22482
22565
  label: "Original SL",
22483
22566
  format: (data) => data.originalPriceStopLoss !== undefined
22484
- ? `${data.originalPriceStopLoss.toFixed(8)} USD`
22567
+ ? `${data.originalPriceStopLoss.toFixed(getPriceScale(data.originalPriceStopLoss))} USD`
22485
22568
  : "N/A",
22486
22569
  isVisible: () => true,
22487
22570
  },
@@ -22489,7 +22572,7 @@ const schedule_columns = [
22489
22572
  key: "originalPriceOpen",
22490
22573
  label: "Original Entry",
22491
22574
  format: (data) => data.originalPriceOpen !== undefined
22492
- ? `${data.originalPriceOpen.toFixed(8)} USD`
22575
+ ? `${data.originalPriceOpen.toFixed(getPriceScale(data.originalPriceOpen))} USD`
22493
22576
  : "N/A",
22494
22577
  isVisible: () => true,
22495
22578
  },
@@ -22614,7 +22697,7 @@ const strategy_columns = [
22614
22697
  {
22615
22698
  key: "currentPrice",
22616
22699
  label: "Price",
22617
- format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(8)} USD` : "N/A"),
22700
+ format: (data) => (data.currentPrice !== undefined ? `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD` : "N/A"),
22618
22701
  isVisible: () => true,
22619
22702
  },
22620
22703
  {
@@ -22734,25 +22817,25 @@ const sync_columns = [
22734
22817
  {
22735
22818
  key: "currentPrice",
22736
22819
  label: "Current Price",
22737
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
22820
+ format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
22738
22821
  isVisible: () => true,
22739
22822
  },
22740
22823
  {
22741
22824
  key: "priceOpen",
22742
22825
  label: "Entry Price",
22743
- format: (data) => `${data.priceOpen.toFixed(8)} USD`,
22826
+ format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
22744
22827
  isVisible: () => true,
22745
22828
  },
22746
22829
  {
22747
22830
  key: "priceTakeProfit",
22748
22831
  label: "Take Profit",
22749
- format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
22832
+ format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
22750
22833
  isVisible: () => true,
22751
22834
  },
22752
22835
  {
22753
22836
  key: "priceStopLoss",
22754
22837
  label: "Stop Loss",
22755
- format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
22838
+ format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
22756
22839
  isVisible: () => true,
22757
22840
  },
22758
22841
  {
@@ -22845,25 +22928,25 @@ const highest_profit_columns = [
22845
22928
  {
22846
22929
  key: "currentPrice",
22847
22930
  label: "Peak Price",
22848
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
22931
+ format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
22849
22932
  isVisible: () => true,
22850
22933
  },
22851
22934
  {
22852
22935
  key: "priceOpen",
22853
22936
  label: "Entry Price",
22854
- format: (data) => `${data.priceOpen.toFixed(8)} USD`,
22937
+ format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
22855
22938
  isVisible: () => true,
22856
22939
  },
22857
22940
  {
22858
22941
  key: "priceTakeProfit",
22859
22942
  label: "Take Profit",
22860
- format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
22943
+ format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
22861
22944
  isVisible: () => true,
22862
22945
  },
22863
22946
  {
22864
22947
  key: "priceStopLoss",
22865
22948
  label: "Stop Loss",
22866
- format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
22949
+ format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
22867
22950
  isVisible: () => true,
22868
22951
  },
22869
22952
  {
@@ -22932,25 +23015,25 @@ const max_drawdown_columns = [
22932
23015
  {
22933
23016
  key: "currentPrice",
22934
23017
  label: "DD Price",
22935
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
23018
+ format: (data) => `${data.currentPrice.toFixed(getPriceScale(data.currentPrice))} USD`,
22936
23019
  isVisible: () => true,
22937
23020
  },
22938
23021
  {
22939
23022
  key: "priceOpen",
22940
23023
  label: "Entry Price",
22941
- format: (data) => `${data.priceOpen.toFixed(8)} USD`,
23024
+ format: (data) => `${data.priceOpen.toFixed(getPriceScale(data.priceOpen))} USD`,
22942
23025
  isVisible: () => true,
22943
23026
  },
22944
23027
  {
22945
23028
  key: "priceTakeProfit",
22946
23029
  label: "Take Profit",
22947
- format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
23030
+ format: (data) => `${data.priceTakeProfit.toFixed(getPriceScale(data.priceTakeProfit))} USD`,
22948
23031
  isVisible: () => true,
22949
23032
  },
22950
23033
  {
22951
23034
  key: "priceStopLoss",
22952
23035
  label: "Stop Loss",
22953
- format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
23036
+ format: (data) => `${data.priceStopLoss.toFixed(getPriceScale(data.priceStopLoss))} USD`,
22954
23037
  isVisible: () => true,
22955
23038
  },
22956
23039
  {
@@ -23832,6 +23915,184 @@ MarkdownFileBase = makeExtendable(MarkdownFileBase);
23832
23915
  ReportBase = makeExtendable(ReportBase);
23833
23916
  const ReportWriter = new ReportWriterAdapter();
23834
23917
 
23918
+ /**
23919
+ * Price-profile metrics derived purely from a series of trade closes — no
23920
+ * candles, no exchange queries. Designed for `BacktestMarkdownService`,
23921
+ * `LiveMarkdownService` and per-symbol Heat: all three already have a
23922
+ * chronological series of `(closeAt, close)` points and nothing else.
23923
+ *
23924
+ * Conventions
23925
+ * -----------
23926
+ * - Pressure: fraction of up-moves vs down-moves (frequency).
23927
+ * - Strength: fraction of upward magnitude vs total movement.
23928
+ * - A divergence between pressure and strength surfaces asymmetry — e.g.
23929
+ * frequent shallow up-moves vs rare deep down-moves is "rising on weak
23930
+ * buys, falling on strong sells".
23931
+ * - Trend: linear regression of log(close) vs days. Slope in %/day,
23932
+ * confidence in R². Classification is bivariate (slope × R²): neither
23933
+ * axis alone fires, both must agree. Slope threshold is normalised by
23934
+ * medianStepSize so the metric self-tunes to the instrument's typical
23935
+ * move size.
23936
+ */
23937
+ /** Minimum samples to surface any price-profile metric. Below this the
23938
+ * per-trade step-distribution and the regression are statistically noisy. */
23939
+ const MIN_SIGNALS = 10;
23940
+ /** R² gate for declaring any trend at all. Below this the regression is
23941
+ * too weak to claim a direction — treat the series as sideways even if
23942
+ * the slope is large. 0.30 is the conventional weak-to-moderate-fit
23943
+ * boundary in econometrics (Cohen's f² ≈ 0.43). */
23944
+ const R2_TREND_GATE = 0.30;
23945
+ /** Slope-magnitude threshold relative to medianStepSize for declaring the
23946
+ * trend strong enough to call bullish/bearish. Below this the regression
23947
+ * fits but the actual drift is weaker than the typical daily step, so we
23948
+ * downgrade to "neutral" (a real but uninteresting tilt). */
23949
+ const SLOPE_VS_STEP_GATE = 0.25;
23950
+ const isFiniteNumber = (v) => typeof v === "number" && Number.isFinite(v);
23951
+ const median = (values) => {
23952
+ const sorted = values.slice().sort((a, b) => a - b);
23953
+ const mid = sorted.length >> 1;
23954
+ return sorted.length % 2 === 0
23955
+ ? (sorted[mid - 1] + sorted[mid]) / 2
23956
+ : sorted[mid];
23957
+ };
23958
+ const emptyProfile = () => ({
23959
+ medianStepSize: null,
23960
+ buyerPressure: null,
23961
+ sellerPressure: null,
23962
+ buyerStrength: null,
23963
+ sellerStrength: null,
23964
+ pressureImbalance: null,
23965
+ trend: null,
23966
+ trendStrength: null,
23967
+ trendConfidence: null,
23968
+ });
23969
+ /**
23970
+ * Computes the price-profile bundle for a chronological series of trade
23971
+ * closes. The input is expected to be sorted by `closeAt` ascending; the
23972
+ * function does not sort defensively (the markdown services already sort).
23973
+ *
23974
+ * @param series - One point per closed trade: timestamp (ms) and close price.
23975
+ * @returns A bundle of nine metrics, each `null` when the input is too small
23976
+ * or numerically unsafe.
23977
+ */
23978
+ const getPriceProfile = (series) => {
23979
+ const valid = series.filter((p) => isFiniteNumber(p.closeAt) &&
23980
+ p.closeAt > 0 &&
23981
+ isFiniteNumber(p.close) &&
23982
+ p.close > 0);
23983
+ if (valid.length < MIN_SIGNALS)
23984
+ return emptyProfile();
23985
+ const n = valid.length;
23986
+ const closes = valid.map((p) => p.close);
23987
+ const times = valid.map((p) => p.closeAt);
23988
+ const absSteps = [];
23989
+ let upMoves = 0;
23990
+ let downMoves = 0;
23991
+ let upMagnitude = 0;
23992
+ let downMagnitude = 0;
23993
+ for (let i = 1; i < n; i++) {
23994
+ const prev = closes[i - 1];
23995
+ const cur = closes[i];
23996
+ const ret = (cur - prev) / prev;
23997
+ const abs = Math.abs(ret);
23998
+ absSteps.push(abs);
23999
+ if (ret > 0) {
24000
+ upMoves++;
24001
+ upMagnitude += abs;
24002
+ }
24003
+ else if (ret < 0) {
24004
+ downMoves++;
24005
+ downMagnitude += abs;
24006
+ }
24007
+ }
24008
+ if (absSteps.length === 0)
24009
+ return emptyProfile();
24010
+ const medianStepSize = median(absSteps) * 100; // percent
24011
+ // --- Pressure / strength ---
24012
+ const decisiveMoves = upMoves + downMoves;
24013
+ const totalMagnitude = upMagnitude + downMagnitude;
24014
+ const buyerPressure = decisiveMoves > 0 ? upMoves / decisiveMoves : null;
24015
+ const sellerPressure = decisiveMoves > 0 ? downMoves / decisiveMoves : null;
24016
+ const buyerStrength = totalMagnitude > 0 ? upMagnitude / totalMagnitude : null;
24017
+ const sellerStrength = totalMagnitude > 0 ? downMagnitude / totalMagnitude : null;
24018
+ const pressureImbalance = buyerStrength !== null && sellerStrength !== null
24019
+ ? buyerStrength - sellerStrength
24020
+ : null;
24021
+ // --- Trend: linear regression of log(close) vs days ---
24022
+ // log-price slope is scale-invariant: 1%/day means the same whether the
24023
+ // asset trades at $0.01 or $10000. Use `closeAt[0]` as time origin so x_i
24024
+ // starts at zero — keeps numerical conditioning sane on long horizons.
24025
+ const t0 = times[0];
24026
+ const xs = new Array(n);
24027
+ const ys = new Array(n);
24028
+ for (let i = 0; i < n; i++) {
24029
+ xs[i] = (times[i] - t0) / (1000 * 60 * 60 * 24); // days
24030
+ ys[i] = Math.log(closes[i]);
24031
+ }
24032
+ // Calendar span must be non-degenerate for the slope to mean anything.
24033
+ const xRange = xs[n - 1] - xs[0];
24034
+ let trend = null;
24035
+ let trendStrength = null;
24036
+ let trendConfidence = null;
24037
+ if (xRange > 0) {
24038
+ let sumX = 0;
24039
+ let sumY = 0;
24040
+ for (let i = 0; i < n; i++) {
24041
+ sumX += xs[i];
24042
+ sumY += ys[i];
24043
+ }
24044
+ const meanX = sumX / n;
24045
+ const meanY = sumY / n;
24046
+ let ssXX = 0;
24047
+ let ssXY = 0;
24048
+ let ssYY = 0;
24049
+ for (let i = 0; i < n; i++) {
24050
+ const dx = xs[i] - meanX;
24051
+ const dy = ys[i] - meanY;
24052
+ ssXX += dx * dx;
24053
+ ssXY += dx * dy;
24054
+ ssYY += dy * dy;
24055
+ }
24056
+ if (ssXX > 0) {
24057
+ const slopeLog = ssXY / ssXX; // log-return per day
24058
+ const slopePct = slopeLog * 100; // %/day (small-slope approx)
24059
+ // R² = 1 - SS_res / SS_tot. With a single explanatory variable this
24060
+ // equals (ssXY)² / (ssXX * ssYY) when ssYY > 0.
24061
+ const r2 = ssYY > 0 ? (ssXY * ssXY) / (ssXX * ssYY) : 0;
24062
+ trendStrength = slopePct;
24063
+ trendConfidence = Math.max(0, Math.min(1, r2));
24064
+ if (trendConfidence < R2_TREND_GATE) {
24065
+ trend = "sideways";
24066
+ }
24067
+ else {
24068
+ const slopeMagnitude = Math.abs(slopePct);
24069
+ const stepScale = medianStepSize * SLOPE_VS_STEP_GATE;
24070
+ if (slopeMagnitude < stepScale) {
24071
+ trend = "neutral";
24072
+ }
24073
+ else if (slopePct > 0) {
24074
+ trend = "bullish";
24075
+ }
24076
+ else {
24077
+ trend = "bearish";
24078
+ }
24079
+ }
24080
+ }
24081
+ }
24082
+ const safe = (v) => v === null || !Number.isFinite(v) ? null : v;
24083
+ return {
24084
+ medianStepSize: safe(medianStepSize),
24085
+ buyerPressure: safe(buyerPressure),
24086
+ sellerPressure: safe(sellerPressure),
24087
+ buyerStrength: safe(buyerStrength),
24088
+ sellerStrength: safe(sellerStrength),
24089
+ pressureImbalance: safe(pressureImbalance),
24090
+ trend,
24091
+ trendStrength: safe(trendStrength),
24092
+ trendConfidence: safe(trendConfidence),
24093
+ };
24094
+ };
24095
+
23835
24096
  /**
23836
24097
  * Creates a unique key for memoizing ReportStorage instances.
23837
24098
  * Key format: "symbol:strategyName:exchangeName:frameName:backtest" or "symbol:strategyName:exchangeName:live"
@@ -23964,6 +24225,15 @@ let ReportStorage$a = class ReportStorage {
23964
24225
  avgConsecutiveLossPnl: null,
23965
24226
  avgWinDuration: null,
23966
24227
  avgLossDuration: null,
24228
+ medianStepSize: null,
24229
+ buyerPressure: null,
24230
+ sellerPressure: null,
24231
+ buyerStrength: null,
24232
+ sellerStrength: null,
24233
+ pressureImbalance: null,
24234
+ trend: null,
24235
+ trendStrength: null,
24236
+ trendConfidence: null,
23967
24237
  };
23968
24238
  }
23969
24239
  // Valid signal set — those with usable pendingAt AND closeTimestamp. Single source
@@ -24260,6 +24530,13 @@ let ReportStorage$a = class ReportStorage {
24260
24530
  const recoveryFactor = !canComputeRatios || blown || equityMaxDrawdown <= 0
24261
24531
  ? null
24262
24532
  : Math.max(-MAX_CALMAR_RATIO$2, Math.min(MAX_CALMAR_RATIO$2, ((equityFinal - 1) * 100) / equityMaxDrawdown));
24533
+ // Price profile — buyer/seller pressure, trend classification. Walks the
24534
+ // chronological close series (`orderedSignals` is already sorted by
24535
+ // closeTimestamp). N-gated internally by the helper (MIN_SIGNALS = 10).
24536
+ const priceProfile = getPriceProfile(orderedSignals.map((s) => ({
24537
+ closeAt: s.closeTimestamp,
24538
+ close: s.currentPrice,
24539
+ })));
24263
24540
  return {
24264
24541
  signalList: this._signalList,
24265
24542
  totalSignals,
@@ -24285,6 +24562,15 @@ let ReportStorage$a = class ReportStorage {
24285
24562
  avgConsecutiveLossPnl: isUnsafe$4(avgConsecutiveLossPnl) ? null : avgConsecutiveLossPnl,
24286
24563
  avgWinDuration: isUnsafe$4(avgWinDuration) ? null : avgWinDuration,
24287
24564
  avgLossDuration: isUnsafe$4(avgLossDuration) ? null : avgLossDuration,
24565
+ medianStepSize: priceProfile.medianStepSize,
24566
+ buyerPressure: priceProfile.buyerPressure,
24567
+ sellerPressure: priceProfile.sellerPressure,
24568
+ buyerStrength: priceProfile.buyerStrength,
24569
+ sellerStrength: priceProfile.sellerStrength,
24570
+ pressureImbalance: priceProfile.pressureImbalance,
24571
+ trend: priceProfile.trend,
24572
+ trendStrength: priceProfile.trendStrength,
24573
+ trendConfidence: priceProfile.trendConfidence,
24288
24574
  };
24289
24575
  }
24290
24576
  /**
@@ -24341,6 +24627,15 @@ let ReportStorage$a = class ReportStorage {
24341
24627
  `**Avg Loss Duration:** ${stats.avgLossDuration === null ? "N/A" : `${stats.avgLossDuration.toFixed(1)} min`}`,
24342
24628
  `**Avg Consecutive Win PNL:** ${stats.avgConsecutiveWinPnl === null ? "N/A" : `${stats.avgConsecutiveWinPnl > 0 ? "+" : ""}${stats.avgConsecutiveWinPnl.toFixed(3)}% (higher is better)`}`,
24343
24629
  `**Avg Consecutive Loss PNL:** ${stats.avgConsecutiveLossPnl === null ? "N/A" : `${stats.avgConsecutiveLossPnl.toFixed(3)}% (closer to 0 is better)`}`,
24630
+ `**Trend:** ${stats.trend === null ? "N/A" : stats.trend}`,
24631
+ `**Trend Strength:** ${stats.trendStrength === null ? "N/A" : `${stats.trendStrength > 0 ? "+" : ""}${stats.trendStrength.toFixed(3)}%/day`}`,
24632
+ `**Trend Confidence (R²):** ${stats.trendConfidence === null ? "N/A" : stats.trendConfidence.toFixed(3)}`,
24633
+ `**Buyer Pressure:** ${stats.buyerPressure === null ? "N/A" : `${(stats.buyerPressure * 100).toFixed(1)}%`}`,
24634
+ `**Seller Pressure:** ${stats.sellerPressure === null ? "N/A" : `${(stats.sellerPressure * 100).toFixed(1)}%`}`,
24635
+ `**Buyer Strength:** ${stats.buyerStrength === null ? "N/A" : `${(stats.buyerStrength * 100).toFixed(1)}%`}`,
24636
+ `**Seller Strength:** ${stats.sellerStrength === null ? "N/A" : `${(stats.sellerStrength * 100).toFixed(1)}%`}`,
24637
+ `**Pressure Imbalance:** ${stats.pressureImbalance === null ? "N/A" : `${stats.pressureImbalance > 0 ? "+" : ""}${stats.pressureImbalance.toFixed(3)}`}`,
24638
+ `**Median Step Size:** ${stats.medianStepSize === null ? "N/A" : `${stats.medianStepSize.toFixed(3)}%`}`,
24344
24639
  "",
24345
24640
  `*Win Rate: reliable above 200+ signals; below 30 signals a single streak can shift it by 10-20%.*`,
24346
24641
  `*Sharpe Ratio: below 1.0 is poor, 1.0-2.0 is acceptable, above 2.0 is strong. Requires 30+ signals.*`,
@@ -24354,6 +24649,11 @@ let ReportStorage$a = class ReportStorage {
24354
24649
  `*Expectancy: per-trade expected value (winProb × avgWin + lossProb × avgLoss). Positive = profitable on average per trade. Break-even trades contribute 0.*`,
24355
24650
  `*All metrics require 100+ signals to be statistically reliable. Annualized metrics assume the observed trading frequency and market conditions persist year-round.*`,
24356
24651
  `*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.*`,
24652
+ `*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.*`,
24653
+ `*Buyer / Seller Pressure: fraction of up-moves (resp. down-moves) among decisive close-to-close changes. Frequency-based; flats excluded.*`,
24654
+ `*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".*`,
24655
+ `*Pressure Imbalance: buyerStrength − sellerStrength ∈ [−1, +1]. Single signed summary of magnitude bias.*`,
24656
+ `*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.*`,
24357
24657
  `*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.*`,
24358
24658
  ].join("\n");
24359
24659
  }
@@ -24984,6 +25284,15 @@ let ReportStorage$9 = class ReportStorage {
24984
25284
  avgConsecutiveLossPnl: null,
24985
25285
  avgWinDuration: null,
24986
25286
  avgLossDuration: null,
25287
+ medianStepSize: null,
25288
+ buyerPressure: null,
25289
+ sellerPressure: null,
25290
+ buyerStrength: null,
25291
+ sellerStrength: null,
25292
+ pressureImbalance: null,
25293
+ trend: null,
25294
+ trendStrength: null,
25295
+ trendConfidence: null,
24987
25296
  };
24988
25297
  }
24989
25298
  const closedEvents = this._eventList.filter((e) => e.action === "closed");
@@ -25282,6 +25591,14 @@ let ReportStorage$9 = class ReportStorage {
25282
25591
  const recoveryFactor = !canComputeRatios || blown || equityMaxDrawdown <= 0
25283
25592
  ? null
25284
25593
  : Math.max(-MAX_CALMAR_RATIO$1, Math.min(MAX_CALMAR_RATIO$1, ((equityFinal - 1) * 100) / equityMaxDrawdown));
25594
+ // Price profile — buyer/seller pressure, trend classification. Built only
25595
+ // from closed events' (timestamp, currentPrice) pairs in chronological
25596
+ // close order — the same data the equity curve uses. N-gated internally
25597
+ // by the helper (MIN_SIGNALS = 10).
25598
+ const priceProfile = getPriceProfile(validClosed
25599
+ .slice()
25600
+ .sort((a, b) => a.timestamp - b.timestamp)
25601
+ .map((e) => ({ closeAt: e.timestamp, close: e.currentPrice })));
25285
25602
  return {
25286
25603
  eventList: this._eventList,
25287
25604
  totalEvents: this._eventList.length,
@@ -25308,6 +25625,15 @@ let ReportStorage$9 = class ReportStorage {
25308
25625
  avgConsecutiveLossPnl: isUnsafe$3(avgConsecutiveLossPnl) ? null : avgConsecutiveLossPnl,
25309
25626
  avgWinDuration: isUnsafe$3(avgWinDuration) ? null : avgWinDuration,
25310
25627
  avgLossDuration: isUnsafe$3(avgLossDuration) ? null : avgLossDuration,
25628
+ medianStepSize: priceProfile.medianStepSize,
25629
+ buyerPressure: priceProfile.buyerPressure,
25630
+ sellerPressure: priceProfile.sellerPressure,
25631
+ buyerStrength: priceProfile.buyerStrength,
25632
+ sellerStrength: priceProfile.sellerStrength,
25633
+ pressureImbalance: priceProfile.pressureImbalance,
25634
+ trend: priceProfile.trend,
25635
+ trendStrength: priceProfile.trendStrength,
25636
+ trendConfidence: priceProfile.trendConfidence,
25311
25637
  };
25312
25638
  }
25313
25639
  /**
@@ -25364,6 +25690,15 @@ let ReportStorage$9 = class ReportStorage {
25364
25690
  `**Avg Loss Duration:** ${stats.avgLossDuration === null ? "N/A" : `${stats.avgLossDuration.toFixed(1)} min`}`,
25365
25691
  `**Avg Consecutive Win PNL:** ${stats.avgConsecutiveWinPnl === null ? "N/A" : `${stats.avgConsecutiveWinPnl > 0 ? "+" : ""}${stats.avgConsecutiveWinPnl.toFixed(3)}% (higher is better)`}`,
25366
25692
  `**Avg Consecutive Loss PNL:** ${stats.avgConsecutiveLossPnl === null ? "N/A" : `${stats.avgConsecutiveLossPnl.toFixed(3)}% (closer to 0 is better)`}`,
25693
+ `**Trend:** ${stats.trend === null ? "N/A" : stats.trend}`,
25694
+ `**Trend Strength:** ${stats.trendStrength === null ? "N/A" : `${stats.trendStrength > 0 ? "+" : ""}${stats.trendStrength.toFixed(3)}%/day`}`,
25695
+ `**Trend Confidence (R²):** ${stats.trendConfidence === null ? "N/A" : stats.trendConfidence.toFixed(3)}`,
25696
+ `**Buyer Pressure:** ${stats.buyerPressure === null ? "N/A" : `${(stats.buyerPressure * 100).toFixed(1)}%`}`,
25697
+ `**Seller Pressure:** ${stats.sellerPressure === null ? "N/A" : `${(stats.sellerPressure * 100).toFixed(1)}%`}`,
25698
+ `**Buyer Strength:** ${stats.buyerStrength === null ? "N/A" : `${(stats.buyerStrength * 100).toFixed(1)}%`}`,
25699
+ `**Seller Strength:** ${stats.sellerStrength === null ? "N/A" : `${(stats.sellerStrength * 100).toFixed(1)}%`}`,
25700
+ `**Pressure Imbalance:** ${stats.pressureImbalance === null ? "N/A" : `${stats.pressureImbalance > 0 ? "+" : ""}${stats.pressureImbalance.toFixed(3)}`}`,
25701
+ `**Median Step Size:** ${stats.medianStepSize === null ? "N/A" : `${stats.medianStepSize.toFixed(3)}%`}`,
25367
25702
  "",
25368
25703
  `*Win Rate: reliable above 200+ signals; below 30 signals a single streak can shift it by 10-20%.*`,
25369
25704
  `*Sharpe Ratio: below 1.0 is poor, 1.0-2.0 is acceptable, above 2.0 is strong. Requires 30+ signals.*`,
@@ -25377,6 +25712,11 @@ let ReportStorage$9 = class ReportStorage {
25377
25712
  `*Expectancy: per-trade expected value (winProb × avgWin + lossProb × avgLoss). Positive = profitable on average per trade. Break-even trades contribute 0.*`,
25378
25713
  `*All metrics require 100+ signals to be statistically reliable. Annualized metrics assume the observed trading frequency and market conditions persist year-round.*`,
25379
25714
  `*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.*`,
25715
+ `*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.*`,
25716
+ `*Buyer / Seller Pressure: fraction of up-moves (resp. down-moves) among decisive close-to-close changes. Frequency-based; flats excluded.*`,
25717
+ `*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".*`,
25718
+ `*Pressure Imbalance: buyerStrength − sellerStrength ∈ [−1, +1]. Single signed summary of magnitude bias.*`,
25719
+ `*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.*`,
25380
25720
  `*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.*`,
25381
25721
  ].join("\n");
25382
25722
  }
@@ -27659,6 +27999,12 @@ class HeatmapStorage {
27659
27999
  calmarRatio = null;
27660
28000
  if (isUnsafe(recoveryFactor))
27661
28001
  recoveryFactor = null;
28002
+ // Price profile — buyer/seller pressure, trend classification. Built from
28003
+ // the chronological close-price series of this symbol's closed signals.
28004
+ // N-gated internally by the helper (MIN_SIGNALS = 10).
28005
+ const priceProfile = getPriceProfile([...signals]
28006
+ .sort((a, b) => a.closeTimestamp - b.closeTimestamp)
28007
+ .map((s) => ({ closeAt: s.closeTimestamp, close: s.currentPrice })));
27662
28008
  return {
27663
28009
  symbol,
27664
28010
  totalPnl,
@@ -27693,6 +28039,15 @@ class HeatmapStorage {
27693
28039
  certaintyRatio,
27694
28040
  expectedYearlyReturns,
27695
28041
  tradesPerYear,
28042
+ medianStepSize: priceProfile.medianStepSize,
28043
+ buyerPressure: priceProfile.buyerPressure,
28044
+ sellerPressure: priceProfile.sellerPressure,
28045
+ buyerStrength: priceProfile.buyerStrength,
28046
+ sellerStrength: priceProfile.sellerStrength,
28047
+ pressureImbalance: priceProfile.pressureImbalance,
28048
+ trend: priceProfile.trend,
28049
+ trendStrength: priceProfile.trendStrength,
28050
+ trendConfidence: priceProfile.trendConfidence,
27696
28051
  };
27697
28052
  }
27698
28053
  /**
@@ -28156,6 +28511,9 @@ class HeatmapStorage {
28156
28511
  `*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.*`,
28157
28512
  `*All metrics require 100+ signals per symbol to be statistically reliable. Annualized metrics assume the observed trading frequency persists year-round.*`,
28158
28513
  `*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.*`,
28514
+ `*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).*`,
28515
+ `*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].*`,
28516
+ `*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.*`,
28159
28517
  `*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.*`,
28160
28518
  ].join("\n");
28161
28519
  }
@@ -66775,4 +67133,4 @@ const validateSignal = (signal, currentPrice) => {
66775
67133
  return !errors.length;
66776
67134
  };
66777
67135
 
66778
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Cron, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Lookup, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, beginContext, beginTime, cacheCandles, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getRuntimeInfo, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenAfterEnd, listenAfterEndOnce, listenBacktestProgress, listenBeforeStart, listenBeforeStartOnce, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toPlainString, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, waitForReady, warmCandles, writeMemory };
67136
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Cron, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Lookup, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, beginContext, beginTime, cacheCandles, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getPriceScale, getRawCandles, getRiskSchema, getRuntimeInfo, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenAfterEnd, listenAfterEndOnce, listenBacktestProgress, listenBeforeStart, listenBeforeStartOnce, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toPlainString, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, waitForReady, warmCandles, writeMemory };