dexbot 1.6.4 → 1.6.6

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.
Files changed (240) hide show
  1. package/CHANGELOG.md +75 -3
  2. package/README.md +109 -40
  3. package/analysis/README.md +19 -17
  4. package/analysis/account_resolver.ts +1 -11
  5. package/analysis/analyze_dynamic_weight.ts +9 -3
  6. package/analysis/bot_usage/kibana_bot_queries.ts +1 -1
  7. package/analysis/chain_pool.ts +1 -1
  8. package/analysis/fills_source.ts +0 -6
  9. package/analysis/grid_correction_check.ts +20 -26
  10. package/analysis/resolve_source.ts +1 -1
  11. package/analysis/trade_profitability.ts +2 -5
  12. package/analysis/tradingview/analyze_tradingview.ts +5 -0
  13. package/analysis/tradingview/tradingview_uplot_chart_generator.ts +2 -2
  14. package/analysis/trend_detection/DYNAMIC_WEIGHT_RESEARCH.md +35 -2
  15. package/analysis/trend_detection/README.md +1 -3
  16. package/analysis/trend_detection/dynamic_weight_chart_generator.ts +82 -10
  17. package/analysis/trend_detection/package.json +1 -1
  18. package/claw/modules/dexbot_profiles.ts +4 -3
  19. package/claw/package.json +1 -1
  20. package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
  21. package/claw/runtimes/openclaw-plugin/package.json +1 -1
  22. package/claw/tests/test_claw_mcp_transport.ts +2 -2
  23. package/claw/tests/test_dexbot_profiles.ts +18 -1
  24. package/dist/analysis/account_resolver.d.ts +1 -4
  25. package/dist/analysis/account_resolver.d.ts.map +1 -1
  26. package/dist/analysis/account_resolver.js +1 -1
  27. package/dist/analysis/account_resolver.js.map +1 -1
  28. package/dist/analysis/analyze_dynamic_weight.js +9 -3
  29. package/dist/analysis/analyze_dynamic_weight.js.map +1 -1
  30. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +1 -2
  31. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
  32. package/dist/analysis/bot_usage/kibana_bot_queries.js +1 -1
  33. package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
  34. package/dist/analysis/chain_pool.d.ts +1 -3
  35. package/dist/analysis/chain_pool.d.ts.map +1 -1
  36. package/dist/analysis/chain_pool.js +1 -1
  37. package/dist/analysis/chain_pool.js.map +1 -1
  38. package/dist/analysis/fills_source.d.ts +1 -51
  39. package/dist/analysis/fills_source.d.ts.map +1 -1
  40. package/dist/analysis/fills_source.js +1 -1
  41. package/dist/analysis/fills_source.js.map +1 -1
  42. package/dist/analysis/grid_correction_check.d.ts +2 -3
  43. package/dist/analysis/grid_correction_check.d.ts.map +1 -1
  44. package/dist/analysis/grid_correction_check.js +18 -26
  45. package/dist/analysis/grid_correction_check.js.map +1 -1
  46. package/dist/analysis/resolve_source.d.ts +1 -1
  47. package/dist/analysis/resolve_source.d.ts.map +1 -1
  48. package/dist/analysis/trade_profitability.d.ts.map +1 -1
  49. package/dist/analysis/trade_profitability.js +2 -7
  50. package/dist/analysis/trade_profitability.js.map +1 -1
  51. package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -1
  52. package/dist/analysis/tradingview/analyze_tradingview.js +5 -0
  53. package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
  54. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +2 -2
  55. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -1
  56. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +82 -10
  57. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -1
  58. package/dist/dexbot.d.ts.map +1 -1
  59. package/dist/dexbot.js +85 -140
  60. package/dist/dexbot.js.map +1 -1
  61. package/dist/market_adapter/core/asymmetric_bounds.d.ts +2 -2
  62. package/dist/market_adapter/core/asymmetric_bounds.d.ts.map +1 -1
  63. package/dist/market_adapter/core/asymmetric_bounds.js +15 -12
  64. package/dist/market_adapter/core/asymmetric_bounds.js.map +1 -1
  65. package/dist/market_adapter/core/kibana_candles.d.ts +1 -15
  66. package/dist/market_adapter/core/kibana_candles.d.ts.map +1 -1
  67. package/dist/market_adapter/core/kibana_candles.js +1 -1
  68. package/dist/market_adapter/core/kibana_candles.js.map +1 -1
  69. package/dist/market_adapter/inputs/fetch_lp_data.d.ts +1 -35
  70. package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
  71. package/dist/market_adapter/inputs/fetch_lp_data.js +10 -54
  72. package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
  73. package/dist/market_adapter/inputs/kibana_feed_source.d.ts +1 -8
  74. package/dist/market_adapter/inputs/kibana_feed_source.d.ts.map +1 -1
  75. package/dist/market_adapter/inputs/kibana_feed_source.js +1 -1
  76. package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
  77. package/dist/market_adapter/inputs/window_cache.d.ts +2 -63
  78. package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
  79. package/dist/market_adapter/inputs/window_cache.js +22 -102
  80. package/dist/market_adapter/inputs/window_cache.js.map +1 -1
  81. package/dist/market_adapter/interval_utils.d.ts +1 -1
  82. package/dist/market_adapter/interval_utils.js +1 -1
  83. package/dist/modules/account_bots.d.ts +70 -6
  84. package/dist/modules/account_bots.d.ts.map +1 -1
  85. package/dist/modules/account_bots.js +353 -149
  86. package/dist/modules/account_bots.js.map +1 -1
  87. package/dist/modules/bitshares-native/crypto/ecc.browser.d.ts.map +1 -1
  88. package/dist/modules/bitshares-native/crypto/ecc.browser.js +0 -31
  89. package/dist/modules/bitshares-native/crypto/ecc.browser.js.map +1 -1
  90. package/dist/modules/bot_defaults.d.ts +98 -0
  91. package/dist/modules/bot_defaults.d.ts.map +1 -0
  92. package/dist/modules/bot_defaults.js +169 -0
  93. package/dist/modules/bot_defaults.js.map +1 -0
  94. package/dist/modules/bot_settings.d.ts.map +1 -1
  95. package/dist/modules/bot_settings.js +4 -1
  96. package/dist/modules/bot_settings.js.map +1 -1
  97. package/dist/modules/config.d.ts +1 -0
  98. package/dist/modules/config.d.ts.map +1 -1
  99. package/dist/modules/config.js +1 -0
  100. package/dist/modules/config.js.map +1 -1
  101. package/dist/modules/constants.d.ts +11 -2
  102. package/dist/modules/constants.d.ts.map +1 -1
  103. package/dist/modules/constants.js +40 -27
  104. package/dist/modules/constants.js.map +1 -1
  105. package/dist/modules/credit_runtime.d.ts +0 -2
  106. package/dist/modules/credit_runtime.d.ts.map +1 -1
  107. package/dist/modules/credit_runtime.js +1 -10
  108. package/dist/modules/credit_runtime.js.map +1 -1
  109. package/dist/modules/dexbot_class.d.ts +13 -10
  110. package/dist/modules/dexbot_class.d.ts.map +1 -1
  111. package/dist/modules/dexbot_class.js +0 -9
  112. package/dist/modules/dexbot_class.js.map +1 -1
  113. package/dist/modules/dexbot_cow_runtime.d.ts +13 -0
  114. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  115. package/dist/modules/dexbot_cow_runtime.js +13 -1
  116. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  117. package/dist/modules/dexbot_maintenance_runtime.d.ts +1 -1
  118. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  119. package/dist/modules/dexbot_maintenance_runtime.js +9 -4
  120. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  121. package/dist/modules/launcher/adapter_requirement.js +1 -1
  122. package/dist/modules/launcher/launch_modes.d.ts +17 -1
  123. package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
  124. package/dist/modules/launcher/launch_modes.js +18 -1
  125. package/dist/modules/launcher/launch_modes.js.map +1 -1
  126. package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
  127. package/dist/modules/launcher/monolithic_runtime.js +8 -1
  128. package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
  129. package/dist/modules/market_adapter_whitelist.d.ts +40 -1
  130. package/dist/modules/market_adapter_whitelist.d.ts.map +1 -1
  131. package/dist/modules/market_adapter_whitelist.js +161 -11
  132. package/dist/modules/market_adapter_whitelist.js.map +1 -1
  133. package/dist/modules/order/accounting.d.ts +4 -18
  134. package/dist/modules/order/accounting.d.ts.map +1 -1
  135. package/dist/modules/order/accounting.js +4 -34
  136. package/dist/modules/order/accounting.js.map +1 -1
  137. package/dist/modules/order/export.d.ts +9 -12
  138. package/dist/modules/order/export.d.ts.map +1 -1
  139. package/dist/modules/order/export.js +9 -12
  140. package/dist/modules/order/export.js.map +1 -1
  141. package/dist/modules/order/grid.d.ts +44 -40
  142. package/dist/modules/order/grid.d.ts.map +1 -1
  143. package/dist/modules/order/grid.js +66 -48
  144. package/dist/modules/order/grid.js.map +1 -1
  145. package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
  146. package/dist/modules/order/grid_reconcile.js +111 -20
  147. package/dist/modules/order/grid_reconcile.js.map +1 -1
  148. package/dist/modules/order/grid_reconcile_internal.d.ts +5 -3
  149. package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
  150. package/dist/modules/order/grid_reconcile_internal.js +18 -3
  151. package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
  152. package/dist/modules/order/manager.d.ts +0 -8
  153. package/dist/modules/order/manager.d.ts.map +1 -1
  154. package/dist/modules/order/manager.js +3 -12
  155. package/dist/modules/order/manager.js.map +1 -1
  156. package/dist/modules/order/utils/order.d.ts +22 -7
  157. package/dist/modules/order/utils/order.d.ts.map +1 -1
  158. package/dist/modules/order/utils/order.js +103 -21
  159. package/dist/modules/order/utils/order.js.map +1 -1
  160. package/dist/modules/order/utils/system.d.ts +5 -1
  161. package/dist/modules/order/utils/system.d.ts.map +1 -1
  162. package/dist/modules/order/utils/system.js +5 -2
  163. package/dist/modules/order/utils/system.js.map +1 -1
  164. package/dist/modules/order/utils/withPoolRef.d.ts +12 -0
  165. package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -1
  166. package/dist/modules/order/utils/withPoolRef.js +17 -0
  167. package/dist/modules/order/utils/withPoolRef.js.map +1 -1
  168. package/dist/modules/order/working_grid.d.ts +8 -13
  169. package/dist/modules/order/working_grid.d.ts.map +1 -1
  170. package/dist/modules/order/working_grid.js +8 -29
  171. package/dist/modules/order/working_grid.js.map +1 -1
  172. package/dist/modules/settings_merge.d.ts +10 -1
  173. package/dist/modules/settings_merge.d.ts.map +1 -1
  174. package/dist/modules/settings_merge.js +27 -17
  175. package/dist/modules/settings_merge.js.map +1 -1
  176. package/dist/modules/validate_profiles.d.ts.map +1 -1
  177. package/dist/modules/validate_profiles.js +4 -3
  178. package/dist/modules/validate_profiles.js.map +1 -1
  179. package/dist/scripts/chart_command.d.ts +48 -0
  180. package/dist/scripts/chart_command.d.ts.map +1 -0
  181. package/dist/scripts/chart_command.js +504 -0
  182. package/dist/scripts/chart_command.js.map +1 -0
  183. package/dist/scripts/dw.d.ts +3 -0
  184. package/dist/scripts/dw.d.ts.map +1 -0
  185. package/dist/scripts/dw.js +30 -0
  186. package/dist/scripts/dw.js.map +1 -0
  187. package/dist/scripts/sync-version.js +0 -22
  188. package/dist/scripts/sync-version.js.map +1 -1
  189. package/dist/scripts/tv.d.ts +1 -31
  190. package/dist/scripts/tv.d.ts.map +1 -1
  191. package/dist/scripts/tv.js +10 -463
  192. package/dist/scripts/tv.js.map +1 -1
  193. package/dist/scripts/update.js +99 -62
  194. package/dist/scripts/update.js.map +1 -1
  195. package/dist/scripts/update_dist_freshness.d.ts +58 -0
  196. package/dist/scripts/update_dist_freshness.d.ts.map +1 -0
  197. package/dist/scripts/update_dist_freshness.js +144 -0
  198. package/dist/scripts/update_dist_freshness.js.map +1 -0
  199. package/dist/unlock.js +1 -1
  200. package/dist/unlock.js.map +1 -1
  201. package/docs/BITSHARES_ONBOARDING.md +16 -10
  202. package/docs/COW_INVARIANTS.md +1 -0
  203. package/docs/DEXBOT_COMPARISON.md +11 -11
  204. package/docs/EVOLUTION.md +9 -7
  205. package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +4 -4
  206. package/docs/GRID_PRICE_INVARIANT.md +518 -0
  207. package/docs/GRID_RECALCULATION.md +20 -24
  208. package/docs/GRID_RECONCILE.md +62 -41
  209. package/docs/LOGGING.md +1 -0
  210. package/docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md +556 -0
  211. package/docs/README.md +18 -18
  212. package/docs/WORKFLOW.md +13 -8
  213. package/docs/architecture.md +42 -40
  214. package/docs/developer_guide.md +5 -8
  215. package/market_adapter/README.md +45 -65
  216. package/modules/README.md +3 -2
  217. package/package.json +7 -4
  218. package/scripts/README.md +18 -34
  219. package/scripts/reset-settings.sh +1 -1
  220. package/analysis/ama_fitting/package.json +0 -12
  221. package/analysis/analyze_derivatives.ts +0 -320
  222. package/analysis/derivative_chart_generator.ts +0 -874
  223. package/analysis/trend_detection/SIGNAL_DOCUMENTATION.md +0 -403
  224. package/analysis/trend_detection/derivative_analyzer.ts +0 -1059
  225. package/dist/analysis/analyze_derivatives.d.ts +0 -68
  226. package/dist/analysis/analyze_derivatives.d.ts.map +0 -1
  227. package/dist/analysis/analyze_derivatives.js +0 -287
  228. package/dist/analysis/analyze_derivatives.js.map +0 -1
  229. package/dist/analysis/derivative_chart_generator.d.ts +0 -12
  230. package/dist/analysis/derivative_chart_generator.d.ts.map +0 -1
  231. package/dist/analysis/derivative_chart_generator.js +0 -891
  232. package/dist/analysis/derivative_chart_generator.js.map +0 -1
  233. package/dist/analysis/trend_detection/derivative_analyzer.d.ts +0 -248
  234. package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +0 -1
  235. package/dist/analysis/trend_detection/derivative_analyzer.js +0 -910
  236. package/dist/analysis/trend_detection/derivative_analyzer.js.map +0 -1
  237. package/dist/scripts/generate_market_adapter_whitelist.d.ts +0 -15
  238. package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +0 -1
  239. package/dist/scripts/generate_market_adapter_whitelist.js +0 -191
  240. package/dist/scripts/generate_market_adapter_whitelist.js.map +0 -1
@@ -2,9 +2,9 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * GRID CORRECTION CHECK
5
+ * LAST-FILL-GUARD CHECK
6
6
  *
7
- * Validates LAST-FILL-GUARD discipline (ceb53819): pivot ± halfIncrement
7
+ * Validates LAST-FILL-GUARD discipline: pivot ± halfIncrement
8
8
  * Last fill @x with increment i (half=i/2) gates BOTH sides regardless of
9
9
  * last side — BUY must be < x*(1-half/100), SELL > x*(1+half/100).
10
10
  * e.g. x=1000, i=0.5% => BUY < 997.5 / SELL > 1002.5.
@@ -21,7 +21,7 @@
21
21
  * modules/constants.ts:DEFAULT_CONFIG.incrementPercent fallback (0.5).
22
22
  *
23
23
  * Fetches fill_order operations from Kibana (same pipeline as
24
- * trade_profitability.ts) and checks for price-order violations.
24
+ * trade_profitability.ts) and checks for last-fill guard violations.
25
25
  *
26
26
  * Usage:
27
27
  * node dist/analysis/grid_correction_check.js --bot-key <bot-key> --hours 168
@@ -166,7 +166,6 @@ Time range (one of):
166
166
 
167
167
  Options:
168
168
  --account <id> Override account ID (default: from bot preferredAccount)
169
- --lookup Legacy: account names always resolve via BitShares node
170
169
  --refresh-account Force re-resolution of preferredAccount and update the
171
170
  stored accountId when it changed (default: reuse the
172
171
  stored accountId with no chain lookup)
@@ -176,7 +175,7 @@ Options:
176
175
  --include-cross-pair Check consecutive fills across different pairs (default: same pair only)
177
176
  --json <file> Export violations as JSON
178
177
  --csv <file> Export violations as CSV
179
- --verbose Show all consecutive pairs, not just violations
178
+ --verbose Print the fetched trade sequence before checking
180
179
  --list-bots List available bot keys and exit
181
180
  --help, -h Show this help
182
181
 
@@ -208,7 +207,6 @@ function parseArgs() {
208
207
  start: null,
209
208
  end: null,
210
209
  account: null,
211
- lookup: false,
212
210
  refreshAccount: false,
213
211
  perFill: false,
214
212
  includeCrossPair: false,
@@ -225,7 +223,6 @@ function parseArgs() {
225
223
  case '--start': opts.start = args[++i]; break;
226
224
  case '--end': opts.end = args[++i]; break;
227
225
  case '--account': opts.account = args[++i]; break;
228
- case '--lookup': opts.lookup = true; break;
229
226
  case '--refresh-account': opts.refreshAccount = true; break;
230
227
  case '--per-fill': opts.perFill = true; break;
231
228
  case '--include-cross-pair': opts.includeCrossPair = true; break;
@@ -472,10 +469,9 @@ function detectViolations(
472
469
  items: (TradeFill | AggregatedOrder)[],
473
470
  includeCrossPair: boolean,
474
471
  incrementPercent: number,
475
- ): { violations: Violation[]; checkedPairs: number; sameDirectionPairs: number } {
472
+ ): { violations: Violation[]; checkedTransitions: number } {
476
473
  const violations: Violation[] = [];
477
- let checkedPairs = 0;
478
- let sameDirectionPairs = 0;
474
+ let checkedTransitions = 0;
479
475
 
480
476
  // Helper for a single chronological sequence (already filtered to one pair or global)
481
477
  function checkSequence(seq: (TradeFill | AggregatedOrder)[]) {
@@ -484,8 +480,7 @@ function detectViolations(
484
480
  const curr = seq[i] as any;
485
481
  // Skip same orderId (multi-fill split of one order) — aggregated mode already collapsed, but per-fill may split
486
482
  if (prev.orderId && prev.orderId === curr.orderId) continue;
487
- checkedPairs++;
488
- if (prev.direction === curr.direction) sameDirectionPairs++;
483
+ checkedTransitions++;
489
484
 
490
485
  const check = isLastFillGuardBlocked(curr.price, curr.direction, prev.price, prev.direction, incrementPercent);
491
486
  if (check.blocked) {
@@ -528,7 +523,7 @@ function detectViolations(
528
523
  }
529
524
  // Sort violations chronologically for reporting
530
525
  violations.sort((a, b) => new Date(a.curr.time).getTime() - new Date(b.curr.time).getTime());
531
- return { violations, checkedPairs, sameDirectionPairs };
526
+ return { violations, checkedTransitions };
532
527
  }
533
528
 
534
529
  // ─── Reporting ────────────────────────────────────────────────────────────────
@@ -541,8 +536,7 @@ function printReport(
541
536
  trades: TradeFill[],
542
537
  orders: AggregatedOrder[] | null,
543
538
  violations: Violation[],
544
- checkedPairs: number,
545
- sameDirectionPairs: number,
539
+ checkedTransitions: number,
546
540
  skipped: number,
547
541
  rangeLabel: string,
548
542
  botKey: string,
@@ -565,7 +559,7 @@ function printReport(
565
559
 
566
560
  console.log('');
567
561
  console.log('═══════════════════════════════════════════════════════════════════');
568
- console.log(' GRID CORRECTION CHECK — LAST-FILL-GUARD (pivot ± halfIncrement)');
562
+ console.log(' LAST-FILL-GUARD CHECK (pivot ± halfIncrement)');
569
563
  console.log('═══════════════════════════════════════════════════════════════════');
570
564
  console.log(` Bot key: ${botKey}${botMeta?.name ? ` (name: ${botMeta.name})` : ''}`);
571
565
  if (botMeta) console.log(` Pair: ${botMeta.assetA ?? '?'} / ${botMeta.assetB ?? '?'}`);
@@ -582,16 +576,16 @@ function printReport(
582
576
  if (orders) console.log(` Orders (aggregated): ${orders.length} (from ${trades.length} fills)`);
583
577
  console.log(` Pairs observed: ${[...pairGroups.keys()].join(', ') || '-'}`);
584
578
  if (skipped > 0) console.log(` Skipped (precision): ${skipped}`);
585
- console.log(` Pairs checked: ${checkedPairs} consecutive pairs (same-direction pairs: ${sameDirectionPairs})`);
586
- console.log(` Violations: ${violations.length}${checkedPairs > 0 ? ` (${((violations.length / checkedPairs) * 100).toFixed(2)}%)` : ''}`);
579
+ console.log(` Transitions checked: ${checkedTransitions} consecutive fill/order transitions`);
580
+ console.log(` Violations: ${violations.length}${checkedTransitions > 0 ? ` (${((violations.length / checkedTransitions) * 100).toFixed(2)}%)` : ''}`);
587
581
  console.log('');
588
582
 
589
583
  if (violations.length === 0) {
590
- console.log(' ✅ PASS — no grid inversions detected (all BUY < pivot-half, SELL > pivot+half).');
584
+ console.log(' ✅ PASS — no LAST-FILL-GUARD violations detected (all BUY < pivot-half, SELL > pivot+half).');
591
585
  console.log('');
592
- if (checkedPairs === 0) {
593
- console.log(' Note: no consecutive pairs in range to check.');
594
- console.log(' (Need at least two fills/orders on the same pair.)');
586
+ if (checkedTransitions === 0) {
587
+ console.log(' Note: no consecutive transitions in range to check.');
588
+ console.log(' (Need at least two fills/orders to form a transition.)');
595
589
  }
596
590
  console.log('');
597
591
  return;
@@ -709,7 +703,7 @@ async function main() {
709
703
  const opts = parseArgs();
710
704
  const { gte, lte, label } = resolveTimeRange(opts);
711
705
 
712
- console.log(`\nGrid correction check — bot-key: ${opts.botKey}`);
706
+ console.log(`\nLast-fill guard check — bot-key: ${opts.botKey}`);
713
707
  console.log(`Range: ${label}`);
714
708
 
715
709
  const resolvedAccount = await resolveBotAccount(opts.botKey, {
@@ -770,10 +764,10 @@ async function main() {
770
764
  items = orders;
771
765
  }
772
766
 
773
- const { violations, checkedPairs, sameDirectionPairs } = detectViolations(items, opts.includeCrossPair, incrementPercent);
767
+ const { violations, checkedTransitions } = detectViolations(items, opts.includeCrossPair, incrementPercent);
774
768
 
775
769
  const ordersForReport = opts.perFill ? null : (items as AggregatedOrder[]);
776
- printReport(trades, ordersForReport, violations, checkedPairs, sameDirectionPairs, skipped, label, opts.botKey, accountId, botMeta, opts.perFill, opts.includeCrossPair, incrementPercent, gte, lte);
770
+ printReport(trades, ordersForReport, violations, checkedTransitions, skipped, label, opts.botKey, accountId, botMeta, opts.perFill, opts.includeCrossPair, incrementPercent, gte, lte);
777
771
 
778
772
  if (opts.json) exportJson(opts.json, violations, trades, label, opts.botKey, accountId, incrementPercent);
779
773
  if (opts.csv) exportCsv(opts.csv, violations);
@@ -781,7 +775,7 @@ async function main() {
781
775
  process.exit(violations.length > 0 ? 2 : 0);
782
776
  }
783
777
 
784
- export { isLastFillGuardBlocked, classifyFills, aggregateByOrder, detectViolations, TradeFill, FillRecord, Violation, AggregatedOrder };
778
+ export { isLastFillGuardBlocked, classifyFills, aggregateByOrder, detectViolations, TradeFill, Violation, AggregatedOrder };
785
779
 
786
780
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
787
781
  main().catch(e => {
@@ -96,4 +96,4 @@ function resolveSource(config: SourceConfig, options: { quiet?: boolean } = {}):
96
96
  }
97
97
 
98
98
  export { resolveSource, listAvailableBots }
99
- export type { SourceConfig, SourceResolution }
99
+ export type { SourceConfig }
@@ -28,7 +28,7 @@ import {
28
28
  * node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --asset 1.3.113
29
29
  * node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --csv trades.csv
30
30
  * node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --json results.json
31
- * node dist/analysis/trade_profitability.js "account-name" --lookup
31
+ * node dist/analysis/trade_profitability.js "account-name" --hours 168
32
32
  * node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --trades
33
33
  * node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --match-mode fifo
34
34
  */
@@ -118,7 +118,6 @@ Options:
118
118
  --end <iso> End time (ISO 8601)
119
119
  --hours <n> Lookback hours from now (alternative to --start/--end)
120
120
  --asset <assetId> Filter to one base asset (e.g. 1.3.113 for bitUSD)
121
- --lookup Legacy (no-op): account names always resolve automatically
122
121
  --refresh-account Force re-resolution and update the stored accountId
123
122
  --csv <file> Export trade list as CSV
124
123
  --json <file> Export full analysis as JSON
@@ -131,7 +130,7 @@ Options:
131
130
  Examples:
132
131
  node dist/analysis/trade_profitability.js 1.2.123456 --hours 720
133
132
  node dist/analysis/trade_profitability.js 1.2.123456 --start 2025-01-01 --end 2025-06-01
134
- node dist/analysis/trade_profitability.js "my-bot-account" --lookup --hours 168
133
+ node dist/analysis/trade_profitability.js "my-bot-account" --hours 168
135
134
  node dist/analysis/trade_profitability.js 1.2.123456 --hours 720 --asset 1.3.113 --csv trades.csv
136
135
  node dist/analysis/trade_profitability.js 1.2.123456 --hours 720 --match-mode sequential`);
137
136
  }
@@ -149,7 +148,6 @@ function parseArgs() {
149
148
  start: null,
150
149
  end: null,
151
150
  asset: null,
152
- lookup: false,
153
151
  refreshAccount: false,
154
152
  csv: null,
155
153
  json: null,
@@ -165,7 +163,6 @@ function parseArgs() {
165
163
  case '--start': opts.start = args[++i]; break;
166
164
  case '--end': opts.end = args[++i]; break;
167
165
  case '--asset': opts.asset = args[++i]; break;
168
- case '--lookup': opts.lookup = true; break;
169
166
  case '--refresh-account': opts.refreshAccount = true; break;
170
167
  case '--csv': opts.csv = args[++i]; break;
171
168
  case '--json': opts.json = args[++i]; break;
@@ -236,6 +236,11 @@ async function main() {
236
236
  },
237
237
  smaPeriod: config.smaPeriod,
238
238
  amaDefaults: {
239
+ // Intentionally the 3-param AMA (er/fast/slow) only: the in-page
240
+ // recomputation has no erSmoothPeriod input by decision — `dexbot tv`
241
+ // stays a plain candle chart. The dw research chart and the live
242
+ // adapter are the mirrors of the full bot AMA config (incl.
243
+ // ama.erSmoothPeriod); the knob is inert by default (global 0).
239
244
  erPeriod: config.amaErPeriod ?? amaConfig.erPeriod,
240
245
  fastPeriod: config.amaFastPeriod ?? amaConfig.fastPeriod,
241
246
  slowPeriod: config.amaSlowPeriod ?? amaConfig.slowPeriod,
@@ -1469,7 +1469,7 @@ function generateHTML(data: any, title: any = 'TradingView Style Research') {
1469
1469
  const maxSlopeOffset = Number(slopeCfg.maxSlopeOffset) > 0 ? Number(slopeCfg.maxSlopeOffset) : 0.5;
1470
1470
  const maxAsym = gridCfg && Number(gridCfg.maxAsymmetryFactor) > 0
1471
1471
  ? Number(gridCfg.maxAsymmetryFactor)
1472
- : (Number(slopeCfg.maxAsymmetryFactor) > 0 ? Number(slopeCfg.maxAsymmetryFactor) : 0.35);
1472
+ : (Number(slopeCfg.maxAsymmetryFactor) > 0 ? Number(slopeCfg.maxAsymmetryFactor) : 0.333);
1473
1473
  const inc = gridCfg && Number(gridCfg.incrementPercent) > 0 ? Number(gridCfg.incrementPercent) : null;
1474
1474
  const minSlots = Math.floor(Number(gridCfg && gridCfg.minScaleSlots) > 0 ? Number(gridCfg.minScaleSlots) : (Number(slopeCfg.minScaleSlots) || 0));
1475
1475
  // User x-range span (slider 1.2x–2.0x, default 1.55x): the only
@@ -3066,7 +3066,7 @@ function generateHTML(data: any, title: any = 'TradingView Style Research') {
3066
3066
  }
3067
3067
  makeStepper('sma-period', 1, 1, 0);
3068
3068
  makeStepper('vwap-bars', 1, 24, 0);
3069
- makeStepper('ama-er', 1, 1, 0);
3069
+ makeStepper('ama-er', 10, 1, 0);
3070
3070
  makeStepper('ama-fast', 0.1, 0.1, 1);
3071
3071
  makeStepper('ama-slow', 1, 0.1, 1);
3072
3072
  document.getElementById('ama-init-offset').addEventListener('input', () => {
@@ -63,6 +63,11 @@ On top of this blend, **Hurst Exponent** and **Permutation Entropy** act as a re
63
63
  ## Quick Start
64
64
 
65
65
  ```bash
66
+ # One-step (recommended): same fetch pipeline as `dexbot tv` — bot, pool, or pair
67
+ dexbot dw <bot-key>
68
+ dexbot dw 133 --month 6
69
+ dexbot dw TOKENA/TOKENB --month 1
70
+
66
71
  # From JSON candle file
67
72
  node dist/analysis/analyze_dynamic_weight.js \
68
73
  --file market_adapter/data/lp/<pair>/lp_pool_<id>_<interval>.json
@@ -77,6 +82,8 @@ node dist/analysis/analyze_dynamic_weight.js \
77
82
 
78
83
  Output: `analysis/charts/dynamic_weight_chart.html` (open in browser)
79
84
 
85
+ `dexbot dw` writes an auto-named chart instead: `analysis/charts/dw_<bot|pool_<id>|<a>_<b>>_1h_<N>m.html`.
86
+
80
87
  **Note**: Hurst requires 256 bars and PE requires 58 bars before their regime signals become active. The first portion of the chart will show the full weight without regime gating.
81
88
 
82
89
  ## CLI Flags
@@ -90,6 +97,7 @@ Output: `analysis/charts/dynamic_weight_chart.html` (open in browser)
90
97
  | `--alpha` | `0.5` | Initial α blend (0 = pure Kalman, 1 = pure AMA) |
91
98
  | `--dw` | `0.50` | Initial displacement weight (0 = pure velocity, 1 = full displacement) |
92
99
  | `--lb` | `9` | Initial lookback bars (1-32) for AMA slope calculation |
100
+ | `--ema` | `0` | Initial AMA input EMA span in bars (0 = off, 0-32) |
93
101
  | `--gain` | `1.0` | Initial gain multiplier |
94
102
  | `--clip` | `10` | Initial clip percentile |
95
103
  | `--quiet` | `false` | Suppress console output |
@@ -107,6 +115,7 @@ All panels share aligned vertical time grid lines, and the bottom output panel s
107
115
 
108
116
  ### Panel 2 — AMA Slope Input (14%)
109
117
  - **Orange line**: AMA3 slope percentage
118
+ - **Gray dashed line**: raw (unfiltered) slope — the reference when the `ema` knob > 0
110
119
  - Shows the directional strength of the slow KAMA's trend movement
111
120
  - Values outside the clip threshold are flattened before entering the offset formula
112
121
  - Background shading mirrors the output signal direction
@@ -159,6 +168,7 @@ All panels share aligned vertical time grid lines, and the bottom output panel s
159
168
  |------|-------|---------|---------|
160
169
  | **nz%** | 0–1 | 0.00 | Neutral zone: dead-band below which offset is forced to 0 |
161
170
  | **lb** | 1–32 | 9 | Logarithmic. Lookback bars for AMA slope calculation |
171
+ | **ema** | 0–32 | 0 | AMA input EMA span in bars (0 = off). Low-pass filters the AMA before the slope is taken; the gray dashed Raw‰ line shows the unfiltered slope for comparison |
162
172
  | **amaS%** | 0.06–0.12 | 0.09 | Logarithmic. Gear ratio for average per-bar AMA slope saturation |
163
173
  | **kalS%** | 0.5–1.5 | 1.0 | Logarithmic. Gear ratio for Kalman composite saturation |
164
174
  | **clip%** | 0–55 | 10 | Percentile clip: filters extreme inputs (0 = off) |
@@ -172,7 +182,7 @@ All panels share aligned vertical time grid lines, and the bottom output panel s
172
182
 
173
183
  ## Copy / Paste Parameters
174
184
 
175
- The **copy** button serializes all knob values (α, dw, kf, kfd, dsp, kdt, kfs, cf, lb, amaS%, kalS%, th%, gain, clip%, nz%, regi) to JSON and writes them to both the clipboard and `localStorage`.
185
+ The **copy** button serializes all knob values (α, dw, kf, kfd, dsp, kdt, kfs, cf, lb, ema, amaS%, kalS%, th%, gain, clip%, nz%, regi) to JSON and writes them to both the clipboard and `localStorage`.
176
186
 
177
187
  The **paste** button first checks `localStorage` for parameters from a previous copy in the same browser session. If none found, it prompts for Ctrl+V input. A confirmation popup shows the parsed values before applying them. Click **Apply** to set the knobs, **Cancel** or press **Escape** to dismiss.
178
188
 
@@ -182,7 +192,7 @@ The **paste** button first checks `localStorage` for parameters from a previous
182
192
 
183
193
  ### AMA slope
184
194
 
185
- Taking the slope of the AMA produces a signal that is already noise-filtered at the source — a near-zero slope genuinely means sideways, not oscillation that happened to average out. The neutral zone (`nz%` knob) dead-bands any remaining micro-slope residuals, which is all the additional filtering the slope channel needs.
195
+ Taking the slope of the AMA produces a signal that is already noise-filtered at the source — a near-zero slope genuinely means sideways, not oscillation that happened to average out. The neutral zone (`nz%` knob) dead-bands any remaining micro-slope residuals. When that is still too jumpy, the optional `ema` knob low-pass filters the AMA input itself before the slope is taken (0 = off keeps the raw input).
186
196
 
187
197
  ### Kalman filter
188
198
 
@@ -277,6 +287,7 @@ The lookup table can be customized per-market or per-bot (see [Custom Configurat
277
287
  ## Formulas
278
288
 
279
289
  ### AMA Offset
290
+ With `ema` > 0, `AMA_now` / `AMA_lb_bars_ago` below are read from the EMA-filtered AMA input instead of the raw AMA series (everything else identical):
280
291
  ```
281
292
  amaSlope% = ((AMA_now − AMA_lb_bars_ago) / AMA_lb_bars_ago × 100) / lb
282
293
  amaClip = clamp(amaSlope%, ±clipThreshold) // percentile-based clip
@@ -354,6 +365,27 @@ Number of bars to look back when computing AMA slope:
354
365
 
355
366
  AMA slope is normalized to an average percent per bar, not the cumulative move across the full lookback. Lower values = more noise, faster reaction. Higher values = smoother signals, more lag without adding gain just because the measurement window is longer.
356
367
 
368
+ ### ema (AMA input EMA span)
369
+ Optional low-pass EMA over the AMA values before the slope is taken (span in bars, α = 2/(span+1), `0` = off). Because differencing and an EMA are both linear filters, they commute — smoothing the AMA input produces exactly the smoothed slope series, modulo the seeding transient.
370
+
371
+ | `ema` | noise reduction* | added lag | verdict |
372
+ |---|---|---|---|
373
+ | 0 | 1× (off) | 0 bars | default, current behavior |
374
+ | 4 | ~2× | ~+1.5 bars | light |
375
+ | 9 (= lb) | ~3× | ~+4 bars | sweet spot |
376
+ | 16 | ~4× | ~+7.5 bars | heavy but usable |
377
+ | 32 | ~5.7× | ~+15.5 bars | upper-bound demo, too laggy to trade |
378
+
379
+ \* standard-deviation reduction for white-ish noise ≈ `sqrt(span)`; lag ≈ `(span-1)/2` bars (EMA group delay) on top of the slope window's `(lb-1)/2`.
380
+
381
+ Measured on ~8.7k hourly bars with the default AMA profile (`lb = 9`): the slow AMA's slope is dominated by low-frequency moves rather than white noise, so the `sqrt(span)` rule overstates the standard-deviation gain — `ema = 9` cut slope std only ~3% but trimmed the largest peaks ~22% and zero-crossings 86 → 74; `ema = 32` cut std ~11%, peaks ~48%, crossings 86 → 44, with a measured lag of ~3 bars at span 9 (theory 4). On this input the knob acts mainly as a peak-trimmer and sign-flip reducer, not as a general noise killer.
382
+
383
+ Effects while tuning:
384
+ - Zero-crossings get rarer, so the `cf` latch matters less and the `nz%` dead-band sits on a smooth curve instead of on noise.
385
+ - |slope| peaks shrink, so the same `amaS%` reaches full offset strength less often — expect to lower `amaS%` (or raise `gain`) to restore output amplitude. The `clip%` percentile pool is rebuilt from the filtered input, which partially self-compensates.
386
+ - Keep total lag in mind: ~`lb/2 + ema/2` bars before the slope reflects a move; at defaults that is ~8 bars (~8 h on the 1 h chart).
387
+ - Panel 2 plots the smoothed slope (orange) over the raw slope (gray dashed) so the noise/lag trade-off is directly visible.
388
+
357
389
  ### regi (regime sensitivity)
358
390
  - 0 = regime multiplier is always 1.0 (Hurst+PE ignored)
359
391
  - 1 = default table values used as-is
@@ -383,6 +415,7 @@ The 3×3 regime multiplier table can be customized per-market or per-bot (defaul
383
415
  Candle Data
384
416
  ├── AMA3 (slow KAMA, erPeriod=781)
385
417
  │ ├── calculateAMA() → AMA3 values per bar [price panel overlay]
418
+ │ ├── `ema` knob (0 = off) → EMA-filtered AMA input [research chart only]
386
419
  │ ├── computeAmaSlopeWeights() → slope%
387
420
  │ └── Percentile clip → amaClip → amaOff
388
421
  │
@@ -1,11 +1,10 @@
1
1
  # Trend Detection
2
2
 
3
- This folder contains the chart generators, derivative signal layer, and re-export shims used by the analysis runners. The canonical AMA, Kalman, Hurst, and Permutation Entropy implementations live in `market_adapter/core/` (see below); the legacy SMA/MACD/RSI derivative signal layer is in `derivative_analyzer.ts`.
3
+ This folder contains the chart generators and re-export shims used by the analysis runners. The canonical AMA, Kalman, Hurst, and Permutation Entropy implementations live in `market_adapter/core/` (see below).
4
4
 
5
5
  ## Docs
6
6
 
7
7
  - [DYNAMIC_WEIGHT_RESEARCH.md](DYNAMIC_WEIGHT_RESEARCH.md) - dynamic weight research notes for the Kalman/Hurst/PE blend
8
- - [SIGNAL_DOCUMENTATION.md](SIGNAL_DOCUMENTATION.md) - derivative signal layer documentation
9
8
 
10
9
  ## Live Counterpart
11
10
 
@@ -13,7 +12,6 @@ This folder contains the chart generators, derivative signal layer, and re-expor
13
12
 
14
13
  ## Modules
15
14
 
16
- - `derivative_analyzer.ts`
17
15
  - `dynamic_weight_chart_generator.ts`
18
16
  - `kalman_chart_generator.ts`
19
17
  - `regime_chart_generator.ts`
@@ -40,6 +40,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
40
40
  const AMA_MS_MIN = 0.06, AMA_MS_MAX = 0.12;
41
41
  const KAL_MS_MIN = 0.5, KAL_MS_MAX = 1.5;
42
42
  const CLIP_PCT_MAX = 20;
43
+ const EMA_MIN = 0, EMA_MAX = 32;
43
44
  const TH_MIN = 0, TH_MAX = 0.5;
44
45
  const KF_MIN = 0, KF_MAX = 200;
45
46
  const KFD_MIN = 1.0, KFD_MAX = 3.0;
@@ -53,6 +54,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
53
54
  dispWeight: [0, 1],
54
55
  neutralZonePct: [0, 1],
55
56
  lookbackBars: [LB_MIN, LB_MAX],
57
+ amaEmaSpan: [EMA_MIN, EMA_MAX],
56
58
  amaMaxSlopePct: [AMA_MS_MIN, AMA_MS_MAX],
57
59
  kalmanMaxSlopePct: [KAL_MS_MIN, KAL_MS_MAX],
58
60
  clipPct: [0, CLIP_PCT_MAX],
@@ -117,6 +119,10 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
117
119
  const defaultKalmanDispThresholdMult = data.kalmanDispThresholdMult ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_DISP_THRESHOLD_MULT_DEFAULT;
118
120
  const defaultKalmanSmoothSpanPct = data.kalmanSmoothSpanPct ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_SMOOTH_SPAN_PCT_DEFAULT;
119
121
  const defaultSignalConfirmBars = data.signalConfirmBars ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_SIGNAL_CONFIRM_BARS_DEFAULT;
122
+ // Research-only `ema` knob: EMA span in bars over the AMA input before the
123
+ // slope is taken. 0 (default) = off, keeps the raw AMA input.
124
+ const defaultAmaEmaSpanRaw = data.amaEmaSpan ?? ma.amaEmaSpan ?? 0;
125
+ const defaultAmaEmaSpan = Math.round(Math.min(EMA_MAX, Math.max(EMA_MIN, Number.isFinite(defaultAmaEmaSpanRaw) ? defaultAmaEmaSpanRaw : EMA_MIN)));
120
126
  const regimeInitSlider = Math.round(defaultRegimeSensitivity * 100);
121
127
 
122
128
  const interval = results.length > 1 ?
@@ -235,7 +241,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
235
241
  .legend-item { gap: 3px; }
236
242
  .legend-val { font-family: monospace; display: inline-block; text-align: right; min-width: 45px; }
237
243
  #l-price, #l-ama3 { min-width: 62px; }
238
- #l-ama-slope, #l-kal-vel, #l-kal-disp { min-width: 54px; }
244
+ #l-ama-slope, #l-ama-slope-raw, #l-kal-vel, #l-kal-disp { min-width: 54px; }
239
245
  #l-signal, #l-signal-latched { min-width: 70px; text-align: left; }
240
246
  #l-combined-raw, #l-combined-echo { min-width: 42px; }
241
247
  #l-mult { min-width: 38px; }
@@ -280,6 +286,9 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
280
286
  .ctrl.lb label { min-width: 20px; }
281
287
  .ctrl.lb input[type="range"] { accent-color: #39d0d8; width: 80px; }
282
288
  .ctrl.lb .val { color: #39d0d8; }
289
+ .ctrl.ema label { min-width: 20px; }
290
+ .ctrl.ema input[type="range"] { accent-color: #f0a000; width: 80px; }
291
+ .ctrl.ema .val { color: #f0a000; }
283
292
  .ctrl.off input[type="range"] { accent-color: #3fb950; }
284
293
  .ctrl.off .val { color: #3fb950; }
285
294
  .ctrl.nz input[type="range"] { accent-color: #8b949e; }
@@ -325,6 +334,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
325
334
  <div class="section-label">AMA SLOPE INPUT</div>
326
335
  <div class="legend">
327
336
  <div class="legend-item"><div class="dot" style="background:#f0a000;"></div>Slope‰: <span id="l-ama-slope" class="legend-val" style="font-weight:bold;">-</span></div>
337
+ <div class="legend-item"><div class="dot" style="background:#8b949e;"></div>Raw‰: <span id="l-ama-slope-raw" class="legend-val">-</span></div>
328
338
  </div>
329
339
  <div id="ama-chart"></div>
330
340
  </div>
@@ -355,6 +365,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
355
365
  <div class="group-sep"></div>
356
366
  <div class="ctrl nz"><label for="nz-slider">nz%</label><input type="range" id="nz-slider" min="0" max="100" value="${Math.round(defaultNeutralZone * 100)}" title="Neutral Zone %"><span class="val" id="nz-value">${defaultNeutralZone.toFixed(2)}</span></div>
357
367
  <div class="ctrl lb"><label for="lb-slider">lb</label><input type="range" id="lb-slider" min="0" max="1000" value="${lbInitSlider}" title="Lookback Bars (${LB_MIN}-${LB_MAX})"><span class="val" id="lb-value">${lookbackBars}</span></div>
368
+ <div class="ctrl ema"><label for="ema-slider">ema</label><input type="range" id="ema-slider" min="${EMA_MIN}" max="${EMA_MAX}" value="${defaultAmaEmaSpan}" title="AMA input EMA span in bars (${EMA_MIN} = off; noise std ~ 1/sqrt(span), lag ~ (span-1)/2 bars; span ~ lb is the balance point)"><span class="val" id="ema-value">${defaultAmaEmaSpan}</span></div>
358
369
  <div class="ctrl ms-ama"><label for="ama-ms-slider">amaS%</label><input type="range" id="ama-ms-slider" min="0" max="1000" value="${amaMsInitSlider}" title="AMA Max Slope % per bar (${AMA_MS_MIN}-${AMA_MS_MAX})"><span class="val" id="ama-ms-value">${defaultAmaMaxSlopePct.toFixed(4)}</span></div>
359
370
  <div class="ctrl clip"><label for="clip-slider">clip%</label><input type="range" id="clip-slider" min="0" max="${CLIP_PCT_MAX}" value="${Math.min(defaultClipPct, CLIP_PCT_MAX)}" title="Outlier Clip %"><span class="val" id="clip-value">${Math.min(defaultClipPct, CLIP_PCT_MAX)}%</span></div>
360
371
 
@@ -388,7 +399,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
388
399
  </div>
389
400
  </div>
390
401
 
391
- <script id="payload" type="application/json">${serializeJsonForScript({ dates, prices, hurstArr, peArr, hurstSegments, peSegments, ama3Prices, amaSeedSmaLine, amaSlopePct, kalmanVelocityPctRaw, kalmanVelocityPct, kalmanDisplacementPct, kalmanIsReady, signals, alpha: defaultAlpha, gain: defaultGain, kalmanSmoothPct: defaultKalmanSmoothPct, kalmanDispScaleMult: defaultKalmanDispScaleMult, kalmanDispThresholdMult: defaultKalmanDispThresholdMult, kalmanSmoothSpanPct: defaultKalmanSmoothSpanPct, signalConfirmBars: defaultSignalConfirmBars, neutralZonePct: defaultNeutralZone, dispWeight: defaultDispWeight, amaMaxSlopePct: defaultAmaMaxSlopePct, kalmanMaxSlopePct: defaultKalmanMaxSlopePct, maxDispPct, clipPct: defaultClipPct, minOutputThreshold: defaultMinOutputThreshold, outputClamp: defaultOutputClamp, regimeSensitivity: defaultRegimeSensitivity, absoluteThreshold: defaultAbsoluteThreshold, lookbackBars, amaErPeriod, amaSlowPeriod, amaWarmupBars, amaSlopeReadyBars, realBarCount, amaPctMax, kalPctMax, amaPercentiles, kalPercentiles, amaSlopeLogMin: AMA_MS_LOG_MIN_N, amaSlopeLogMax: AMA_MS_LOG_MAX_N, kalSlopeLogMin: KAL_MS_LOG_MIN_N, kalSlopeLogMax: KAL_MS_LOG_MAX_N, lbLogMin: LB_LOG_MIN_N, lbLogMax: LB_LOG_MAX_N, gainLogMin: GAIN_LOG_MIN_N, gainLogMax: GAIN_LOG_MAX_N, sliderRanges: SLIDER_RANGES, dispScaleMinPct: defaultDispScaleMinPct, weightMin: MARKET_ADAPTER.DYNAMIC_WEIGHT_MIN_WEIGHT, weightMax: MARKET_ADAPTER.DYNAMIC_WEIGHT_MAX_WEIGHT, marketAdapter: ma, amaWeightConfig, hNodes: [0.5 + MARKET_ADAPTER.HURST_ZONE_BAND, 0.5, 0.5 - MARKET_ADAPTER.HURST_ZONE_BAND], pNodes: MARKET_ADAPTER.PE_NODES, regimeTable: MARKET_ADAPTER.REGIME_TABLE })}</script>
402
+ <script id="payload" type="application/json">${serializeJsonForScript({ dates, prices, hurstArr, peArr, hurstSegments, peSegments, ama3Prices, amaSeedSmaLine, amaSlopePct, kalmanVelocityPctRaw, kalmanVelocityPct, kalmanDisplacementPct, kalmanIsReady, signals, alpha: defaultAlpha, gain: defaultGain, kalmanSmoothPct: defaultKalmanSmoothPct, kalmanDispScaleMult: defaultKalmanDispScaleMult, kalmanDispThresholdMult: defaultKalmanDispThresholdMult, kalmanSmoothSpanPct: defaultKalmanSmoothSpanPct, signalConfirmBars: defaultSignalConfirmBars, neutralZonePct: defaultNeutralZone, dispWeight: defaultDispWeight, amaMaxSlopePct: defaultAmaMaxSlopePct, kalmanMaxSlopePct: defaultKalmanMaxSlopePct, maxDispPct, clipPct: defaultClipPct, minOutputThreshold: defaultMinOutputThreshold, outputClamp: defaultOutputClamp, regimeSensitivity: defaultRegimeSensitivity, absoluteThreshold: defaultAbsoluteThreshold, lookbackBars, amaErPeriod, amaSlowPeriod, amaWarmupBars, amaSlopeReadyBars, realBarCount, amaPctMax, kalPctMax, amaPercentiles, kalPercentiles, amaSlopeLogMin: AMA_MS_LOG_MIN_N, amaSlopeLogMax: AMA_MS_LOG_MAX_N, kalSlopeLogMin: KAL_MS_LOG_MIN_N, kalSlopeLogMax: KAL_MS_LOG_MAX_N, lbLogMin: LB_LOG_MIN_N, lbLogMax: LB_LOG_MAX_N, amaEmaSpan: defaultAmaEmaSpan, gainLogMin: GAIN_LOG_MIN_N, gainLogMax: GAIN_LOG_MAX_N, sliderRanges: SLIDER_RANGES, dispScaleMinPct: defaultDispScaleMinPct, weightMin: MARKET_ADAPTER.DYNAMIC_WEIGHT_MIN_WEIGHT, weightMax: MARKET_ADAPTER.DYNAMIC_WEIGHT_MAX_WEIGHT, marketAdapter: ma, amaWeightConfig, hNodes: [0.5 + MARKET_ADAPTER.HURST_ZONE_BAND, 0.5, 0.5 - MARKET_ADAPTER.HURST_ZONE_BAND], pNodes: MARKET_ADAPTER.PE_NODES, regimeTable: MARKET_ADAPTER.REGIME_TABLE })}</script>
392
403
 
393
404
  <script>
394
405
  const data = JSON.parse(document.getElementById('payload').textContent);
@@ -430,6 +441,8 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
430
441
  const kalMsSliderToVal = (pos) => Math.exp(KAL_MS_LOG_MIN + (pos / 1000) * (KAL_MS_LOG_MAX - KAL_MS_LOG_MIN));
431
442
 
432
443
  let currentLookbackBars = data.lookbackBars ?? ${JSON.stringify(lookbackBars)};
444
+ let currentAmaEmaSpan = Math.max(SR.amaEmaSpan[0], Math.min(SR.amaEmaSpan[1], Math.round(data.amaEmaSpan ?? 0)));
445
+ let currentAmaSource = data.ama3Prices;
433
446
  const LB_LOG_MIN = data.lbLogMin;
434
447
  const LB_LOG_MAX = data.lbLogMax;
435
448
  const lbSliderToVal = (pos) => Math.round(Math.exp(LB_LOG_MIN + (pos / 1000) * (LB_LOG_MAX - LB_LOG_MIN)));
@@ -468,6 +481,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
468
481
  const currentLatchedSignals = new Array(data.dates.length).fill(null);
469
482
  const dynamicAmaOff = new Array(data.dates.length).fill(null);
470
483
  const dynamicAmaSlopePct = new Array(data.dates.length).fill(null);
484
+ const dynamicAmaSlopePctRaw = new Array(data.dates.length).fill(null);
471
485
  const dynamicKalOff = new Array(data.dates.length).fill(null);
472
486
  const combinedOff = new Array(data.dates.length).fill(null);
473
487
  const combinedSell = new Array(data.dates.length).fill(null);
@@ -582,9 +596,40 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
582
596
  }
583
597
  }
584
598
 
585
- function computeSlopeAtIndex(idx, lb) {
599
+ /**
600
+ * Research-only ema knob — EMA low-pass over the AMA input before the
601
+ * slope is taken (EMA-then-difference == difference-then-EMA, both are
602
+ * linear filters). span bars, alpha = 2/(span+1): noise std shrinks ~
603
+ * 1/sqrt(span), lag grows ~ (span-1)/2 bars. Seeded with an SMA over the
604
+ * first 'span' finite values (mirrors the AMA seed SMA) so the transient
605
+ * is gone before any slope bar can go ready. Nulls (warmup / trailing
606
+ * padding) stay null so no fabricated values reach the clip-percentile
607
+ * pool or the offset pipeline. span <= 0 returns the input untouched.
608
+ */
609
+ function buildEmaSeries(values, span) {
610
+ if (!(span > 0) || !Array.isArray(values)) return values;
611
+ const n = values.length;
612
+ const out = new Array(n);
613
+ const alpha = 2 / (span + 1);
614
+ let seedSum = 0, seedCount = 0;
615
+ for (let i = 0; i < n && seedCount < span; i++) {
616
+ const v = values[i];
617
+ if (v == null || !Number.isFinite(v)) continue;
618
+ seedSum += v; seedCount++;
619
+ }
620
+ let prev = seedCount > 0 ? seedSum / seedCount : null;
621
+ for (let i = 0; i < n; i++) {
622
+ const v = values[i];
623
+ if (v == null || !Number.isFinite(v)) { out[i] = null; continue; }
624
+ prev = prev == null ? v : alpha * v + (1 - alpha) * prev;
625
+ out[i] = prev;
626
+ }
627
+ return out;
628
+ }
629
+
630
+ function computeSlopeAtIndex(idx, lb, values) {
586
631
  // Canonical AMA slope % (computeAverageAmaSlopePct injected above).
587
- const sp = computeAverageAmaSlopePct(data.ama3Prices[idx], data.ama3Prices[idx - lb], lb);
632
+ const sp = computeAverageAmaSlopePct(values[idx], values[idx - lb], lb);
588
633
  return sp == null ? 0 : sp;
589
634
  }
590
635
 
@@ -592,6 +637,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
592
637
  recalcKalmanVelocity();
593
638
  recalcKalmanClipThreshold();
594
639
  recalcLatchedSignals();
640
+ currentAmaSource = buildEmaSeries(data.ama3Prices, currentAmaEmaSpan);
595
641
  const lb = currentLookbackBars;
596
642
  const amaErWarmup = Math.max(0, Number.isFinite(data.amaErPeriod) ? Math.ceil(data.amaErPeriod) : ${JSON.stringify(MARKET_ADAPTER.AMAS[MARKET_ADAPTER.DEFAULT_AMA_KEY as keyof typeof MARKET_ADAPTER.AMAS].erPeriod)});
597
643
  const amaReadyBar = Math.max(lb, amaErWarmup + lb);
@@ -600,14 +646,20 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
600
646
  // implementation (computeAmaSlopeClipThreshold injected above),
601
647
  // shared with the live market adapter service. Trailing padded
602
648
  // nulls beyond realBarCount are skipped by its finiteness guards.
603
- dynamicClipThreshold = computeAmaSlopeClipThreshold(data.ama3Prices, amaErWarmup, lb, currentClipPct);
649
+ // Clip pool follows the (possibly EMA-filtered) AMA input so the
650
+ // percentile threshold matches the series the offsets derive from.
651
+ dynamicClipThreshold = computeAmaSlopeClipThreshold(currentAmaSource, amaErWarmup, lb, currentClipPct);
604
652
 
605
653
  for (let i = 0; i < data.realBarCount; i++) {
606
- // AMA raw slope % for the display panel (offsets computed in recalcWeights)
607
- dynamicAmaSlopePct[i] = i < amaReadyBar ? null : computeSlopeAtIndex(i, lb);
654
+ // AMA slope % for the display panel (offsets computed in
655
+ // recalcWeights): the raw reference line from the unfiltered AMA
656
+ // input, the plotted series from the EMA-filtered input.
657
+ dynamicAmaSlopePctRaw[i] = i < amaReadyBar ? null : computeSlopeAtIndex(i, lb, data.ama3Prices);
658
+ dynamicAmaSlopePct[i] = i < amaReadyBar ? null : computeSlopeAtIndex(i, lb, currentAmaSource);
608
659
  }
609
660
  for (let i = data.realBarCount; i < data.dates.length; i++) {
610
661
  dynamicAmaSlopePct[i] = null;
662
+ dynamicAmaSlopePctRaw[i] = null;
611
663
  }
612
664
  }
613
665
 
@@ -633,7 +685,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
633
685
  // Per-bar offset pipeline — canonical implementation (computeDynamicWeightSeries
634
686
  // injected above), shared with the live market adapter service.
635
687
  const res = computeDynamicWeightSeries({
636
- amaValues: data.ama3Prices,
688
+ amaValues: currentAmaSource,
637
689
  kalmanVelocityPct: currentKalmanVelocityPct,
638
690
  kalmanDisplacementPct: data.kalmanDisplacementPct,
639
691
  kalmanIsReady: data.kalmanIsReady,
@@ -717,7 +769,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
717
769
  max: Number.isFinite(xs.max) ? xs.max : xMax,
718
770
  } : null;
719
771
 
720
- amaChart.setData([data.dates, dynamicAmaSlopePct], false);
772
+ amaChart.setData([data.dates, dynamicAmaSlopePctRaw, dynamicAmaSlopePct], false);
721
773
  kalmanChart.setData([data.dates, currentKalmanVelocityPct, data.kalmanDisplacementPct], false);
722
774
  outputChart.setData([data.dates, combinedOff, echoCombinedOff], false);
723
775
 
@@ -752,6 +804,11 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
752
804
  if (sp == null) { spEl.textContent = '-'; spEl.style.color = '#8b949e'; }
753
805
  else { spEl.textContent = (sp >= 0 ? '+' : '') + (sp * 10).toFixed(3) + '‰'; spEl.style.color = sp > 0.01 ? '#2ea043' : sp < -0.01 ? '#f85149' : '#8b949e'; }
754
806
 
807
+ const spRaw = dynamicAmaSlopePctRaw[idx];
808
+ const spRawEl = document.getElementById('l-ama-slope-raw');
809
+ if (spRaw == null) { spRawEl.textContent = '-'; spRawEl.style.color = '#8b949e'; }
810
+ else { spRawEl.textContent = (spRaw >= 0 ? '+' : '') + (spRaw * 10).toFixed(3) + '‰'; spRawEl.style.color = '#8b949e'; }
811
+
755
812
  const vp = currentKalmanVelocityPct[idx];
756
813
  const vpEl = document.getElementById('l-kal-vel');
757
814
  if (vp == null) { vpEl.textContent = '-'; vpEl.style.color = '#8b949e'; }
@@ -975,6 +1032,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
975
1032
  document.getElementById('clip-slider').value = currentClipPct;
976
1033
  document.getElementById('nz-slider').value = Math.round(currentNz * 100);
977
1034
  document.getElementById('lb-slider').value = lbValToSlider(currentLookbackBars);
1035
+ document.getElementById('ema-slider').value = currentAmaEmaSpan;
978
1036
 
979
1037
  const priceEl = document.getElementById('price-panel');
980
1038
  const amaEl = document.getElementById('ama-panel');
@@ -1010,6 +1068,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
1010
1068
  scales: { x: { time: true }, p: { auto: true } },
1011
1069
  series: [
1012
1070
  { label: 'Time' },
1071
+ { label: 'Raw‰', stroke: '#8b949e', width: 1, dash: [3, 3], scale: 'p', points: { show: false } },
1013
1072
  { label: 'Slope‰', stroke: '#f0a000', width: 2, scale: 'p', points: { show: false } },
1014
1073
  ],
1015
1074
  axes: [
@@ -1019,7 +1078,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
1019
1078
  ],
1020
1079
  cursor: cursorCfg,
1021
1080
  hooks: { draw: [makePctFillHook(dynamicAmaSlopePct, 'p', 'rgba(46,160,67,0.20)', 'rgba(248,81,73,0.20)'), makeSignalBgHook('p')] }
1022
- }, [data.dates, dynamicAmaSlopePct], document.getElementById('ama-chart'));
1081
+ }, [data.dates, dynamicAmaSlopePctRaw, dynamicAmaSlopePct], document.getElementById('ama-chart'));
1023
1082
 
1024
1083
  kalmanChart = new uPlot({
1025
1084
  width: kalmanEl.offsetWidth, height: kalmanEl.offsetHeight,
@@ -1181,6 +1240,12 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
1181
1240
  onSliderChange();
1182
1241
  });
1183
1242
 
1243
+ document.getElementById('ema-slider').addEventListener('input', (e) => {
1244
+ currentAmaEmaSpan = parseInt(e.target.value, 10);
1245
+ document.getElementById('ema-value').textContent = currentAmaEmaSpan;
1246
+ onSliderChange();
1247
+ });
1248
+
1184
1249
  document.getElementById('regime-slider').addEventListener('input', (e) => {
1185
1250
  currentRegimeSensitivity = parseInt(e.target.value, 10) / 100;
1186
1251
  document.getElementById('regime-value').textContent = currentRegimeSensitivity.toFixed(2);
@@ -1276,6 +1341,11 @@ function applyParams(p, btn) {
1276
1341
  document.getElementById('lb-slider').value = lbValToSlider(currentLookbackBars);
1277
1342
  document.getElementById('lb-value').textContent = currentLookbackBars;
1278
1343
  }
1344
+ if (p.amaEmaSpan != null) {
1345
+ currentAmaEmaSpan = Math.max(SR.amaEmaSpan[0], Math.min(SR.amaEmaSpan[1], Math.round(p.amaEmaSpan)));
1346
+ document.getElementById('ema-slider').value = currentAmaEmaSpan;
1347
+ document.getElementById('ema-value').textContent = currentAmaEmaSpan;
1348
+ }
1279
1349
  recalcInputs();
1280
1350
  recalcWeights();
1281
1351
  refreshChartsPreservingZoom();
@@ -1302,6 +1372,7 @@ function applyParams(p, btn) {
1302
1372
  regimeSensitivity: 'regime',
1303
1373
  dispWeight: 'dw',
1304
1374
  lookbackBars: 'lb',
1375
+ amaEmaSpan: 'ema',
1305
1376
  };
1306
1377
  document.getElementById('paste-confirm-vals').innerHTML = Object.entries(labels)
1307
1378
  .filter(([k]) => p[k] != null)
@@ -1355,6 +1426,7 @@ function applyParams(p, btn) {
1355
1426
  regimeSensitivity: +currentRegimeSensitivity.toFixed(2),
1356
1427
  dispWeight: +currentDw.toFixed(2),
1357
1428
  lookbackBars: currentLookbackBars,
1429
+ amaEmaSpan: currentAmaEmaSpan,
1358
1430
  };
1359
1431
  const json = JSON.stringify(params, null, 2);
1360
1432
  localStorage.setItem(LS_KEY, json);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trend-detection",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Kalman filter trend detection with tactical/modal state tracking",
5
5
  "main": "../../dist/analysis/trend_detection/kalman_trend_analyzer.js",
6
6
  "scripts": {