dexbot 1.4.25 → 1.5.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.
Files changed (182) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +6 -2
  3. package/analysis/README.md +14 -7
  4. package/analysis/ama_fitting/package.json +1 -1
  5. package/analysis/analyze_derivatives.ts +1 -1
  6. package/analysis/bot_key_utils.ts +272 -3
  7. package/analysis/bot_usage/discover_bot_accounts.ts +64 -9
  8. package/analysis/bot_usage/kibana_bot_queries.ts +40 -1
  9. package/analysis/chart_utils.ts +6 -3
  10. package/analysis/grid_correction_check.ts +56 -20
  11. package/analysis/resolve_bot_accounts.ts +199 -0
  12. package/analysis/trade_profitability.ts +36 -8
  13. package/analysis/tradingview/README.md +25 -0
  14. package/analysis/tradingview/analyze_tradingview.ts +2 -2
  15. package/analysis/trend_detection/package.json +1 -1
  16. package/claw/modules/claw_launcher.ts +5 -4
  17. package/claw/modules/credit_runtime_adapter.ts +2 -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/dist/analysis/analyze_derivatives.js +1 -1
  24. package/dist/analysis/bot_key_utils.d.ts +28 -1
  25. package/dist/analysis/bot_key_utils.d.ts.map +1 -1
  26. package/dist/analysis/bot_key_utils.js +385 -3
  27. package/dist/analysis/bot_key_utils.js.map +1 -1
  28. package/dist/analysis/bot_usage/discover_bot_accounts.js +60 -10
  29. package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -1
  30. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +45 -1
  31. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
  32. package/dist/analysis/bot_usage/kibana_bot_queries.js +39 -1
  33. package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
  34. package/dist/analysis/chart_utils.d.ts +2 -1
  35. package/dist/analysis/chart_utils.d.ts.map +1 -1
  36. package/dist/analysis/chart_utils.js +4 -1
  37. package/dist/analysis/chart_utils.js.map +1 -1
  38. package/dist/analysis/grid_correction_check.d.ts.map +1 -1
  39. package/dist/analysis/grid_correction_check.js +60 -20
  40. package/dist/analysis/grid_correction_check.js.map +1 -1
  41. package/dist/analysis/resolve_bot_accounts.d.ts +3 -0
  42. package/dist/analysis/resolve_bot_accounts.d.ts.map +1 -0
  43. package/dist/analysis/resolve_bot_accounts.js +216 -0
  44. package/dist/analysis/resolve_bot_accounts.js.map +1 -0
  45. package/dist/analysis/trade_profitability.d.ts.map +1 -1
  46. package/dist/analysis/trade_profitability.js +40 -8
  47. package/dist/analysis/trade_profitability.js.map +1 -1
  48. package/dist/analysis/tradingview/analyze_tradingview.js +2 -2
  49. package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
  50. package/dist/bot.d.ts.map +1 -1
  51. package/dist/bot.js +2 -1
  52. package/dist/bot.js.map +1 -1
  53. package/dist/dexbot.d.ts.map +1 -1
  54. package/dist/dexbot.js +73 -17
  55. package/dist/dexbot.js.map +1 -1
  56. package/dist/market_adapter/ama_signal_runner.js +2 -2
  57. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +2 -1
  58. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -1
  59. package/dist/market_adapter/inputs/fetch_lp_data.d.ts +4 -1
  60. package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
  61. package/dist/market_adapter/inputs/fetch_lp_data.js +3 -2
  62. package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
  63. package/dist/modules/account_bots.d.ts +42 -1
  64. package/dist/modules/account_bots.d.ts.map +1 -1
  65. package/dist/modules/account_bots.js +168 -17
  66. package/dist/modules/account_bots.js.map +1 -1
  67. package/dist/modules/bitshares_client.d.ts +4 -1
  68. package/dist/modules/bitshares_client.d.ts.map +1 -1
  69. package/dist/modules/bitshares_client.js +12 -4
  70. package/dist/modules/bitshares_client.js.map +1 -1
  71. package/dist/modules/bot_settings.d.ts.map +1 -1
  72. package/dist/modules/bot_settings.js +2 -1
  73. package/dist/modules/bot_settings.js.map +1 -1
  74. package/dist/modules/cli_colors.d.ts +39 -0
  75. package/dist/modules/cli_colors.d.ts.map +1 -0
  76. package/dist/modules/cli_colors.js +42 -0
  77. package/dist/modules/cli_colors.js.map +1 -0
  78. package/dist/modules/constants.d.ts.map +1 -1
  79. package/dist/modules/constants.js +15 -11
  80. package/dist/modules/constants.js.map +1 -1
  81. package/dist/modules/credit_pricing.d.ts +61 -0
  82. package/dist/modules/credit_pricing.d.ts.map +1 -0
  83. package/dist/modules/credit_pricing.js +237 -0
  84. package/dist/modules/credit_pricing.js.map +1 -0
  85. package/dist/modules/credit_runtime.d.ts +1 -0
  86. package/dist/modules/credit_runtime.d.ts.map +1 -1
  87. package/dist/modules/credit_runtime.js +49 -107
  88. package/dist/modules/credit_runtime.js.map +1 -1
  89. package/dist/modules/dexbot_class.d.ts +2 -1
  90. package/dist/modules/dexbot_class.d.ts.map +1 -1
  91. package/dist/modules/dexbot_class.js +22 -6
  92. package/dist/modules/dexbot_class.js.map +1 -1
  93. package/dist/modules/dexbot_cow_runtime.d.ts +19 -2
  94. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  95. package/dist/modules/dexbot_cow_runtime.js +249 -26
  96. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  97. package/dist/modules/dexbot_maintenance_runtime.d.ts +15 -16
  98. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  99. package/dist/modules/dexbot_maintenance_runtime.js +69 -42
  100. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  101. package/dist/modules/fund_registry.d.ts +1 -1
  102. package/dist/modules/fund_registry.js +1 -1
  103. package/dist/modules/launcher/adapter_requirement.d.ts +38 -0
  104. package/dist/modules/launcher/adapter_requirement.d.ts.map +1 -0
  105. package/dist/modules/launcher/adapter_requirement.js +141 -0
  106. package/dist/modules/launcher/adapter_requirement.js.map +1 -0
  107. package/dist/modules/launcher/bot_supervisor.d.ts +2 -2
  108. package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -1
  109. package/dist/modules/launcher/bot_supervisor.js +32 -3
  110. package/dist/modules/launcher/bot_supervisor.js.map +1 -1
  111. package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
  112. package/dist/modules/launcher/launch_modes.js +5 -4
  113. package/dist/modules/launcher/launch_modes.js.map +1 -1
  114. package/dist/modules/launcher/monolithic_runtime.d.ts +1 -1
  115. package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
  116. package/dist/modules/launcher/monolithic_runtime.js +12 -8
  117. package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
  118. package/dist/modules/launcher/status_reporting.d.ts +6 -6
  119. package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
  120. package/dist/modules/launcher/status_reporting.js +9 -6
  121. package/dist/modules/launcher/status_reporting.js.map +1 -1
  122. package/dist/modules/order/export.d.ts +1 -1
  123. package/dist/modules/order/export.js +1 -1
  124. package/dist/modules/order/grid.d.ts.map +1 -1
  125. package/dist/modules/order/grid.js +47 -12
  126. package/dist/modules/order/grid.js.map +1 -1
  127. package/dist/modules/order/logger.d.ts +23 -1
  128. package/dist/modules/order/logger.d.ts.map +1 -1
  129. package/dist/modules/order/logger.js +60 -17
  130. package/dist/modules/order/logger.js.map +1 -1
  131. package/dist/modules/order/manager.d.ts.map +1 -1
  132. package/dist/modules/order/manager.js +43 -3
  133. package/dist/modules/order/manager.js.map +1 -1
  134. package/dist/modules/order/sync_engine.js +1 -1
  135. package/dist/modules/order/utils/system.d.ts +52 -4
  136. package/dist/modules/order/utils/system.d.ts.map +1 -1
  137. package/dist/modules/order/utils/system.js +106 -14
  138. package/dist/modules/order/utils/system.js.map +1 -1
  139. package/dist/modules/order/utils/validate.d.ts +1 -1
  140. package/dist/modules/order/utils/validate.d.ts.map +1 -1
  141. package/dist/modules/order/utils/validate.js +43 -3
  142. package/dist/modules/order/utils/validate.js.map +1 -1
  143. package/dist/modules/utils/chain_logs.d.ts +11 -0
  144. package/dist/modules/utils/chain_logs.d.ts.map +1 -0
  145. package/dist/modules/utils/chain_logs.js +23 -0
  146. package/dist/modules/utils/chain_logs.js.map +1 -0
  147. package/dist/modules/utils/sanitize_key.d.ts +7 -0
  148. package/dist/modules/utils/sanitize_key.d.ts.map +1 -1
  149. package/dist/modules/utils/sanitize_key.js +15 -0
  150. package/dist/modules/utils/sanitize_key.js.map +1 -1
  151. package/dist/pm2.d.ts +11 -1
  152. package/dist/pm2.d.ts.map +1 -1
  153. package/dist/pm2.js +66 -15
  154. package/dist/pm2.js.map +1 -1
  155. package/dist/scripts/analyze-credit.d.ts +4 -0
  156. package/dist/scripts/analyze-credit.d.ts.map +1 -0
  157. package/dist/scripts/analyze-credit.js +760 -0
  158. package/dist/scripts/analyze-credit.js.map +1 -0
  159. package/dist/scripts/analyze-orders.d.ts +1 -10
  160. package/dist/scripts/analyze-orders.d.ts.map +1 -1
  161. package/dist/scripts/analyze-orders.js +11 -34
  162. package/dist/scripts/analyze-orders.js.map +1 -1
  163. package/dist/scripts/test-credit-renewal.d.ts.map +1 -1
  164. package/dist/scripts/test-credit-renewal.js +37 -5
  165. package/dist/scripts/test-credit-renewal.js.map +1 -1
  166. package/dist/scripts/tv.d.ts +11 -0
  167. package/dist/scripts/tv.d.ts.map +1 -0
  168. package/dist/scripts/tv.js +357 -0
  169. package/dist/scripts/tv.js.map +1 -0
  170. package/dist/scripts/update.js +77 -10
  171. package/dist/scripts/update.js.map +1 -1
  172. package/dist/unlock.d.ts.map +1 -1
  173. package/dist/unlock.js +20 -8
  174. package/dist/unlock.js.map +1 -1
  175. package/docs/DEXBOT_COMPARISON.md +3 -3
  176. package/docs/EVOLUTION.md +7 -6
  177. package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
  178. package/docs/MPA_CREDIT_USAGE.md +3 -1
  179. package/docs/README.md +2 -2
  180. package/docs/WORKFLOW.md +3 -1
  181. package/package.json +2 -1
  182. package/scripts/README.md +12 -0
@@ -32,6 +32,10 @@
32
32
  * limit_order_cancel:
33
33
  * op_object.fee_paying_account — account that cancelled
34
34
  * op_object.order — the order ID cancelled
35
+ *
36
+ * limit_order_update (op 77, DEXBot2-only — native in-place re-price):
37
+ * op_object.seller — account that updated the order
38
+ * op_object.order — the limit order ID updated
35
39
  */
36
40
 
37
41
 
@@ -42,6 +46,11 @@ import { kibanaSearch, DEFAULT_CONFIG as BASE_CONFIG } from '../../market_adapte
42
46
  const OP_LIMIT_ORDER_CREATE = 1;
43
47
  const OP_LIMIT_ORDER_CANCEL = 2;
44
48
  const OP_FILL_ORDER = 4;
49
+ // Native in-place order re-price. Only DEXBot2 broadcasts this (COW UPDATE
50
+ // actions via buildUpdateOrderOp); DEXBot1 (Python staggered_orders) predates
51
+ // it and can only cancel + recreate. Presence of op 77 on an account is the
52
+ // DEXBot2 fingerprint; absence means cancel-only (DEXBot1-style).
53
+ const OP_LIMIT_ORDER_UPDATE = 77;
45
54
 
46
55
  const DEFAULT_CONFIG = {
47
56
  ...BASE_CONFIG,
@@ -166,7 +175,37 @@ function buildTopFilledAccountsQuery(lookbackHours: number, topN: number = 100,
166
175
  };
167
176
  }
168
177
 
178
+ /**
179
+ * Top N accounts by limit_order_update count (seller field, op 77).
180
+ * DEXBot2 fingerprint: only DEXBot2 broadcasts native in-place re-prices
181
+ * (COW UPDATE actions). DEXBot1-style bots never emit op 77 — they cancel
182
+ * (op 2) and recreate (op 1) instead.
183
+ */
184
+ function buildTopUpdaterAccountsQuery(lookbackHours: number, topN: number = 200, minUpdates: number = 1) {
185
+ return {
186
+ size: 0,
187
+ query: {
188
+ bool: {
189
+ filter: [
190
+ { term: { operation_type: OP_LIMIT_ORDER_UPDATE } },
191
+ { range: { 'block_data.block_time': { gte: `now-${lookbackHours}h`, lte: 'now' } } },
192
+ ],
193
+ },
194
+ },
195
+ aggs: {
196
+ by_account: {
197
+ terms: {
198
+ field: 'operation_history.op_object.seller.keyword',
199
+ size: topN,
200
+ min_doc_count: minUpdates,
201
+ order: { _count: 'desc' },
202
+ },
203
+ },
204
+ },
205
+ };
206
+ }
207
+
169
208
  // ─── Exports ──────────────────────────────────────────────────────────────────
170
209
 
171
- export { DEFAULT_CONFIG, kibanaSearch, buildOrderPriceQuery, buildTopSellerAccountsQuery, buildTopCancellerAccountsQuery, buildTopFilledAccountsQuery }
210
+ export { DEFAULT_CONFIG, kibanaSearch, buildOrderPriceQuery, buildTopSellerAccountsQuery, buildTopCancellerAccountsQuery, buildTopFilledAccountsQuery, buildTopUpdaterAccountsQuery, OP_LIMIT_ORDER_UPDATE }
172
211
 
@@ -54,8 +54,11 @@ function toEpochSeconds(ts: any, fallbackIdx: any) {
54
54
  return fallbackIdx * 3600;
55
55
  }
56
56
 
57
- function writeChartFile(filePath: any, html: any) {
58
- const chartDir = path.dirname(filePath);
57
+ function toFileUrl(filePath: any): string {
58
+ return `file://${path.resolve(String(filePath))}`;
59
+ }
60
+
61
+ function writeChartFile(filePath: any, html: any) { const chartDir = path.dirname(filePath);
59
62
  if (!fs.existsSync(chartDir)) ensureDir(chartDir);
60
63
  ensureSiblingUplotAssets(chartDir);
61
64
  // Atomic write (tmp + rename, matching the production storage adapter
@@ -190,5 +193,5 @@ function bindPan(chart) {
190
193
  }
191
194
  `;
192
195
 
193
- export { escapeHtml, serializeJsonForScript, toEpochSeconds, writeChartFile, embedFunctionSources, UPLOT_SHARED_SCRIPT }
196
+ export { escapeHtml, serializeJsonForScript, toEpochSeconds, writeChartFile, toFileUrl, embedFunctionSources, UPLOT_SHARED_SCRIPT }
194
197
 
@@ -37,7 +37,7 @@ import fs from 'node:fs';
37
37
  import { pathToFileURL } from 'node:url';
38
38
  import * as KC from '../market_adapter/core/kibana_client.js';
39
39
  import * as C from '../modules/constants.js';
40
- import { loadBotMeta, loadBotSettings, computeBotKey } from './bot_key_utils.js';
40
+ import { loadBotMeta, loadBotSettings, computeBotKey, persistBotAccountId } from './bot_key_utils.js';
41
41
 
42
42
  const { kibanaSearch, DEFAULT_CONFIG: BASE_CONFIG } = KC;
43
43
 
@@ -221,8 +221,11 @@ Time range (one of):
221
221
 
222
222
  Options:
223
223
  --account <id> Override account ID (default: from bot preferredAccount)
224
- --lookup Resolve account name to ID via BitShares node
225
- --node <url> BitShares node URL for --lookup / precision resolution
224
+ --lookup Legacy: account names always resolve via BitShares node
225
+ --refresh-account Force re-resolution of preferredAccount and update the
226
+ stored accountId when it changed (default: reuse the
227
+ stored accountId with no chain lookup)
228
+ --node <url> BitShares node URL for account / precision resolution
226
229
  --increment <pct> Grid increment percent (default: from bot config or ${Number((C as any)?.DEFAULT_CONFIG?.incrementPercent ?? 0.5)})
227
230
  --tolerance <pct> Deprecated alias for --increment (kept for compat, prefer --increment)
228
231
  --per-fill Check at fill granularity (default: per-order aggregated)
@@ -262,6 +265,7 @@ function parseArgs() {
262
265
  end: null,
263
266
  account: null,
264
267
  lookup: false,
268
+ refreshAccount: false,
265
269
  node: C.NODE_MANAGEMENT.DEFAULT_NODES[0],
266
270
  perFill: false,
267
271
  includeCrossPair: false,
@@ -279,6 +283,7 @@ function parseArgs() {
279
283
  case '--end': opts.end = args[++i]; break;
280
284
  case '--account': opts.account = args[++i]; break;
281
285
  case '--lookup': opts.lookup = true; break;
286
+ case '--refresh-account': opts.refreshAccount = true; break;
282
287
  case '--node': opts.node = args[++i]; break;
283
288
  case '--per-fill': opts.perFill = true; break;
284
289
  case '--include-cross-pair': opts.includeCrossPair = true; break;
@@ -356,32 +361,63 @@ async function resolveAccountId(name: string, nodeUrl: string): Promise<string |
356
361
  }
357
362
  }
358
363
 
359
- async function resolveAccountForBot(botKey: string, overrideAccount: string | null, nodeUrl: string, doLookup: boolean): Promise<{ accountId: string; botMeta: any }> {
364
+ async function resolveAccountForBot(botKey: string, overrideAccount: string | null, nodeUrl: string, doRefresh: boolean): Promise<{ accountId: string; botMeta: any }> {
360
365
  const botMeta = loadBotMeta(botKey);
361
366
  if (!botMeta && !overrideAccount) {
362
367
  console.error(`Error: bot key '${botKey}' not found in profiles/bots.json and no --account provided.`);
363
368
  console.error(`Use --list-bots to see available keys, or pass --account <1.2.x> explicitly.`);
364
369
  process.exit(1);
365
370
  }
366
- let accountId = overrideAccount ?? botMeta?.preferredAccount ?? null;
367
- if (!accountId) {
371
+ // An explicit --account always wins and is never persisted.
372
+ if (overrideAccount) {
373
+ if (/^1\.2\.\d+$/.test(String(overrideAccount))) {
374
+ return { accountId: String(overrideAccount), botMeta };
375
+ }
376
+ console.log(` Resolving account name '${overrideAccount}'...`);
377
+ const resolvedOverride = await resolveAccountId(String(overrideAccount), nodeUrl);
378
+ if (!resolvedOverride) {
379
+ console.error(`Error: failed to resolve --account '${overrideAccount}' to 1.2.x`);
380
+ process.exit(1);
381
+ }
382
+ console.log(` → ${resolvedOverride}`);
383
+ return { accountId: String(resolvedOverride), botMeta };
384
+ }
385
+ const prefRaw = botMeta?.preferredAccount != null ? String(botMeta.preferredAccount) : null;
386
+ const prefIsId = !!prefRaw && /^1\.2\.\d+$/.test(prefRaw);
387
+ const storedId = /^1\.2\.\d+$/.test(String(botMeta?.accountId ?? '')) ? String(botMeta.accountId) : null;
388
+ // An explicit 1.2.x preferredAccount is authoritative: the stored accountId
389
+ // is a derived cache and must never override it. Self-heal the cache.
390
+ if (prefIsId) {
391
+ if (storedId && storedId !== prefRaw && persistBotAccountId(botKey, prefRaw as string)) {
392
+ console.log(` Updated stale stored accountId ${storedId} → ${prefRaw} from preferredAccount`);
393
+ }
394
+ return { accountId: prefRaw as string, botMeta };
395
+ }
396
+ if (!prefRaw) {
368
397
  console.error(`Error: bot '${botKey}' has no preferredAccount and no --account provided.`);
369
398
  process.exit(1);
370
399
  }
371
- // If account looks like a name, resolve it
372
- if (!/^1\.2\.\d+$/.test(String(accountId))) {
373
- if (doLookup || !String(accountId).startsWith('1.2.')) {
374
- console.log(` Resolving account name '${accountId}'...`);
375
- const resolved = await resolveAccountId(String(accountId), nodeUrl);
376
- if (!resolved) {
377
- console.error(`Error: failed to resolve account name '${accountId}' to 1.2.x`);
378
- process.exit(1);
379
- }
380
- console.log(` → ${resolved}`);
381
- accountId = resolved;
382
- }
400
+ // Name + fresh-enough cache + no refresh requested: offline-friendly fast path.
401
+ if (storedId && !doRefresh) {
402
+ console.log(` Using stored accountId ${storedId} from profiles/bots.json (no lookup needed; pass --refresh-account to re-verify)`);
403
+ return { accountId: storedId, botMeta };
404
+ }
405
+ // No cache, or --refresh-account: resolve the name on-chain.
406
+ console.log(` Resolving account name '${prefRaw}'...`);
407
+ const resolved = await resolveAccountId(prefRaw, nodeUrl);
408
+ if (!resolved) {
409
+ console.error(`Error: failed to resolve account name '${prefRaw}' to 1.2.x`);
410
+ process.exit(1);
411
+ }
412
+ console.log(` → ${resolved}`);
413
+ if (!storedId && persistBotAccountId(botKey, resolved)) {
414
+ console.log(` Stored accountId ${resolved} in profiles/bots.json for '${botKey}'`);
415
+ } else if (storedId && storedId !== resolved && persistBotAccountId(botKey, resolved)) {
416
+ console.log(` Updated stored accountId ${storedId} → ${resolved} in profiles/bots.json for '${botKey}'`);
417
+ } else if (storedId === resolved) {
418
+ console.log(` Stored accountId ${resolved} confirmed up to date`);
383
419
  }
384
- return { accountId: String(accountId), botMeta };
420
+ return { accountId: String(resolved), botMeta };
385
421
  }
386
422
 
387
423
  // ─── Asset precision resolution ───────────────────────────────────────────────
@@ -829,7 +865,7 @@ async function main() {
829
865
  console.log(`\nGrid correction check — bot-key: ${opts.botKey}`);
830
866
  console.log(`Range: ${label}`);
831
867
 
832
- const { accountId, botMeta } = await resolveAccountForBot(opts.botKey, opts.account, opts.node, opts.lookup);
868
+ const { accountId, botMeta } = await resolveAccountForBot(opts.botKey, opts.account, opts.node, opts.refreshAccount);
833
869
  console.log(`Account: ${accountId}${botMeta ? ` (${botMeta.assetA}/${botMeta.assetB})` : ''}`);
834
870
  const incrementPercent = resolveIncrementPercent(botMeta, opts.incrementPercent);
835
871
  console.log(`Increment: ${incrementPercent}% (halfInc ${incrementPercent/2}%)${opts.incrementPercent == null && botMeta?.incrementPercent != null ? ' — from bot config' : opts.incrementPercent != null ? ' — from --increment' : ' — default'}`);
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * BOT ACCOUNT-ID BACKFILL
6
+ *
7
+ * Resolves every bot's preferredAccount name to its chain account ID and
8
+ * stamps `accountId` next to `preferredAccount` in profiles/bots.json, so
9
+ * analysis tools and scripts can skip the chain lookup on later runs.
10
+ * Nothing is ever written by hand — all IDs in the file come from this
11
+ * script, the bot editor, or the analysis tools themselves.
12
+ *
13
+ * Usage:
14
+ * node dist/analysis/resolve_bot_accounts.js [--bot-key <key>] [--refresh]
15
+ * [--timeout-ms <n>] [--dry-run] [--json]
16
+ *
17
+ * Exit code 0 = all bot accounts resolved (or nothing to do),
18
+ * 1 = one or more resolutions failed.
19
+ *
20
+ * Short-lived batch tool: suppresses chain INFO logging and disconnects the
21
+ * shared client on exit. Run standalone only — never import/call from a
22
+ * long-running process holding a live connection.
23
+ */
24
+
25
+ import { loadBotSettings, computeBotKey, persistBotAccountId } from './bot_key_utils.js';
26
+ import { setGlobalConsoleLevel, getGlobalConsoleLevel } from '../modules/order/logger.js';
27
+ import { sleep } from '../modules/order/utils/system.js';
28
+ import { getErrorMessage } from '../modules/utils/errors.js';
29
+
30
+ const DEFAULT_TIMEOUT_MS = 20000;
31
+
32
+ function printHelp() {
33
+ console.log(`\
34
+ Usage: node dist/analysis/resolve_bot_accounts.js [options]
35
+
36
+ Resolve bot preferredAccount names to chain IDs and store them as accountId
37
+ in profiles/bots.json (byte-preserving; comments and formatting survive).
38
+
39
+ Options:
40
+ --bot-key <key> Only process this bot key or bot name (default: all bots)
41
+ --refresh Re-resolve even when a stored accountId exists
42
+ --timeout-ms <n> Per-account chain lookup timeout (default: ${DEFAULT_TIMEOUT_MS})
43
+ --dry-run Report what would change without writing anything
44
+ --json Append a machine-readable summary
45
+ --help, -h Show this help
46
+ `);
47
+ }
48
+
49
+ function parseArgs() {
50
+ const args = process.argv.slice(2);
51
+ if (args.includes('--help') || args.includes('-h')) { printHelp(); process.exit(0); }
52
+ const opts: any = {
53
+ botKey: null,
54
+ refresh: false,
55
+ timeoutMs: DEFAULT_TIMEOUT_MS,
56
+ dryRun: false,
57
+ json: false,
58
+ };
59
+ for (let i = 0; i < args.length; i++) {
60
+ switch (args[i]) {
61
+ case '--bot-key': opts.botKey = args[++i]; break;
62
+ case '--refresh': opts.refresh = true; break;
63
+ case '--timeout-ms': opts.timeoutMs = parseInt(args[++i], 10); break;
64
+ case '--dry-run': opts.dryRun = true; break;
65
+ case '--json': opts.json = true; break;
66
+ default:
67
+ console.error(`Unknown option: ${args[i]}`);
68
+ process.exit(1);
69
+ }
70
+ }
71
+ if (!Number.isFinite(opts.timeoutMs) || opts.timeoutMs <= 0) {
72
+ console.error('Error: --timeout-ms must be a positive number.');
73
+ process.exit(1);
74
+ }
75
+ return opts;
76
+ }
77
+
78
+ async function resolveWithTimeout(chainOrders: any, ref: string, timeoutMs: number): Promise<{ id: string | null; reason: string }> {
79
+ const timeoutErr: any = new Error(`lookup timed out after ${timeoutMs}ms`);
80
+ timeoutErr.code = 'ACCOUNT_LOOKUP_TIMEOUT';
81
+ try {
82
+ const id = await Promise.race([
83
+ chainOrders.resolveAccountId(ref),
84
+ sleep(timeoutMs).then(() => { throw timeoutErr; }),
85
+ ]);
86
+ if (id && /^1\.2\.\d+$/.test(String(id))) return { id: String(id), reason: 'resolved' };
87
+ return { id: null, reason: 'not-found' };
88
+ } catch (err: any) {
89
+ return { id: null, reason: err && err.code === 'ACCOUNT_LOOKUP_TIMEOUT' ? 'timeout' : 'error' };
90
+ }
91
+ }
92
+
93
+ async function main() {
94
+ const opts = parseArgs();
95
+ const settings = loadBotSettings();
96
+ const entries = Array.isArray(settings?.bots) ? settings.bots : [];
97
+ if (!entries.length) {
98
+ console.log('No bots in profiles/bots.json — nothing to do.');
99
+ process.exit(0);
100
+ }
101
+
102
+ let targets = entries.map((bot: any, index: number) => ({ bot, index, key: computeBotKey(bot, index) }));
103
+ if (opts.botKey) {
104
+ const want = String(opts.botKey).toLowerCase();
105
+ targets = targets.filter((t: any) => t.key === want || String(t.bot?.name ?? '').toLowerCase() === want);
106
+ if (!targets.length) {
107
+ console.error(`Error: bot '${opts.botKey}' not found in profiles/bots.json.`);
108
+ process.exit(1);
109
+ }
110
+ }
111
+
112
+ // Suppress chain INFO spam for the whole batch; restored in finally.
113
+ // (setSuppressConnectionLog is read lazily so this file never drags the
114
+ // chain stack in when it cannot be loaded.)
115
+ let chainClient: any = null;
116
+ let prevSuppress = false;
117
+ let prevGlobalLevel: string | null = null;
118
+ let suppressionArmed = false;
119
+ let chainOrders: any = null;
120
+ try {
121
+ chainClient = await import('../modules/bitshares_client.js');
122
+ prevSuppress = chainClient.isSuppressConnectionLog();
123
+ prevGlobalLevel = getGlobalConsoleLevel();
124
+ suppressionArmed = true;
125
+ chainClient.setSuppressConnectionLog(true);
126
+ setGlobalConsoleLevel('warn');
127
+ chainOrders = await import('../modules/chain_orders.js');
128
+ } catch (err: any) {
129
+ console.error(`Error: could not load the chain stack (${getErrorMessage(err)}).`);
130
+ process.exit(1);
131
+ }
132
+
133
+ const results: any[] = [];
134
+ try {
135
+ for (const t of targets) {
136
+ const name = t.bot?.name ?? t.key;
137
+ const prefRaw = t.bot?.preferredAccount != null ? String(t.bot.preferredAccount) : '';
138
+ const stored = /^1\.2\.\d+$/.test(String(t.bot?.accountId ?? '')) ? String(t.bot.accountId) : null;
139
+ if (!prefRaw) {
140
+ results.push({ key: t.key, name, status: 'skipped', detail: 'no preferredAccount' });
141
+ console.log(`- ${t.key}: skipped (no preferredAccount)`);
142
+ continue;
143
+ }
144
+ if (/^1\.2\.\d+$/.test(prefRaw)) {
145
+ if (stored !== prefRaw) {
146
+ const wrote = opts.dryRun ? true : persistBotAccountId(t.key, prefRaw);
147
+ const status = !wrote ? 'failed' : (opts.dryRun ? 'would-update' : 'updated');
148
+ results.push({ key: t.key, name, status, detail: wrote ? `preferredAccount is authoritative (${prefRaw})` : 'could not write profiles/bots.json' });
149
+ console.log(`- ${t.key}: ${!wrote ? 'FAILED to write' : (opts.dryRun ? 'would update' : 'updated')} stored accountId → ${prefRaw} (preferredAccount is authoritative)`);
150
+ } else {
151
+ results.push({ key: t.key, name, status: 'cached', detail: prefRaw });
152
+ console.log(`- ${t.key}: cached (${prefRaw})`);
153
+ }
154
+ continue;
155
+ }
156
+ if (stored && !opts.refresh) {
157
+ results.push({ key: t.key, name, status: 'cached', detail: stored });
158
+ console.log(`- ${t.key}: cached (${stored})`);
159
+ continue;
160
+ }
161
+ const r = await resolveWithTimeout(chainOrders, prefRaw, opts.timeoutMs);
162
+ if (r.id) {
163
+ const changed = stored !== r.id;
164
+ const wrote = !changed || opts.dryRun ? true : persistBotAccountId(t.key, r.id);
165
+ const status = !wrote ? 'failed' : (opts.dryRun ? (changed ? 'would-update' : 'unchanged') : (changed ? (stored ? 'updated' : 'stored') : 'unchanged'));
166
+ results.push({ key: t.key, name, status, detail: wrote ? r.id : 'could not write profiles/bots.json' });
167
+ console.log(`- ${t.key}: '${prefRaw}' → ${r.id}${!wrote ? ' (FAILED to store)' : (changed ? (opts.dryRun ? ' (would store)' : (stored ? ' (updated)' : ' (stored)')) : ' (unchanged)')}`);
168
+ } else {
169
+ const why = r.reason === 'timeout' ? `nodes unreachable (timed out after ${opts.timeoutMs}ms)` : 'not found on the blockchain';
170
+ results.push({ key: t.key, name, status: 'failed', detail: why });
171
+ console.log(`- ${t.key}: FAILED to resolve '${prefRaw}' (${why})`);
172
+ }
173
+ }
174
+ } finally {
175
+ if (chainClient) {
176
+ try { await chainClient.disconnectClient(); } catch (_) { /* best-effort cleanup */ }
177
+ if (suppressionArmed) {
178
+ try { chainClient.setSuppressConnectionLog(prevSuppress); } catch (_) { /* restore-only */ }
179
+ }
180
+ }
181
+ if (suppressionArmed) {
182
+ try { setGlobalConsoleLevel(prevGlobalLevel); } catch (_) { /* restore-only */ }
183
+ }
184
+ }
185
+
186
+ const failed = results.filter((r: any) => r.status === 'failed');
187
+ const skipped = results.filter((r: any) => r.status === 'skipped').length;
188
+ const ok = results.length - failed.length - skipped;
189
+ console.log(`\nDone: ${ok} resolved, ${skipped} skipped, ${failed.length} failed${opts.dryRun ? ' (dry run, nothing written)' : ''}.`);
190
+ if (opts.json) {
191
+ console.log(JSON.stringify({ dryRun: opts.dryRun, results }, null, 2));
192
+ }
193
+ process.exit(failed.length ? 1 : 0);
194
+ }
195
+
196
+ main().catch((err: any) => {
197
+ console.error('Fatal:', getErrorMessage(err));
198
+ process.exit(1);
199
+ });
@@ -5,6 +5,7 @@ import fs from 'node:fs';
5
5
  import { pathToFileURL } from 'node:url';
6
6
  import * as KC from '../market_adapter/core/kibana_client.js';
7
7
  import * as C from '../modules/constants.js';
8
+ import { findBotKeyByAccountRef, getStoredBotAccountId, persistBotAccountId } from './bot_key_utils.js';
8
9
 
9
10
  const { kibanaSearch, DEFAULT_CONFIG: BASE_CONFIG } = KC;
10
11
 
@@ -194,14 +195,15 @@ Analyzes filled orders for a BitShares account, computing realized PnL
194
195
  via FIFO or sequential (LIFO) inventory tracking.
195
196
 
196
197
  Arguments:
197
- accountId BitShares account ID (1.2.x) or name (with --lookup)
198
+ accountId BitShares account ID (1.2.x) or name (auto-resolved in the background)
198
199
 
199
200
  Options:
200
201
  --start <iso> Start time (ISO 8601, e.g. 2025-01-01 or 2025-01-01T00:00:00Z)
201
202
  --end <iso> End time (ISO 8601)
202
203
  --hours <n> Lookback hours from now (alternative to --start/--end)
203
204
  --asset <assetId> Filter to one base asset (e.g. 1.3.113 for bitUSD)
204
- --lookup Resolve account name to ID via BitShares node
205
+ --lookup Legacy (no-op): account names always resolve automatically
206
+ --refresh-account Force re-resolution and update the stored accountId
205
207
  --node <url> BitShares node URL (default: first healthy from built-in pool)
206
208
  --csv <file> Export trade list as CSV
207
209
  --json <file> Export full analysis as JSON
@@ -233,6 +235,7 @@ function parseArgs() {
233
235
  end: null,
234
236
  asset: null,
235
237
  lookup: false,
238
+ refreshAccount: false,
236
239
  node: C.NODE_MANAGEMENT.DEFAULT_NODES[0],
237
240
  csv: null,
238
241
  json: null,
@@ -249,6 +252,7 @@ function parseArgs() {
249
252
  case '--end': opts.end = args[++i]; break;
250
253
  case '--asset': opts.asset = args[++i]; break;
251
254
  case '--lookup': opts.lookup = true; break;
255
+ case '--refresh-account': opts.refreshAccount = true; break;
252
256
  case '--node': opts.node = args[++i]; break;
253
257
  case '--csv': opts.csv = args[++i]; break;
254
258
  case '--json': opts.json = args[++i]; break;
@@ -1329,13 +1333,37 @@ async function run() {
1329
1333
  const opts = parseArgs();
1330
1334
  let accountId = opts.accountId;
1331
1335
 
1332
- if (opts.lookup) {
1333
- const resolved = await resolveAccountId(accountId, opts.node);
1334
- if (!resolved) {
1335
- console.error(` Could not resolve "${accountId}" to an account ID`);
1336
- process.exit(1);
1336
+ if (!/^1\.2\.\d+$/.test(String(accountId))) {
1337
+ // Background resolution: the Kibana query below filters on the 1.2.x
1338
+ // account_id field, so a name must always resolve first (a raw name
1339
+ // would silently return zero fills).
1340
+ // Stored ID first: when the name belongs to a bot in profiles/bots.json
1341
+ // and its stamped accountId still matches, no chain lookup is needed.
1342
+ let matchedKey: string | null = null;
1343
+ let stored: string | null = null;
1344
+ try {
1345
+ const match = findBotKeyByAccountRef(accountId);
1346
+ if (match) {
1347
+ matchedKey = match.botKey;
1348
+ stored = getStoredBotAccountId(match.botKey, accountId);
1349
+ }
1350
+ } catch (_) {
1351
+ // bots.json issues must never break resolution; fall through to chain.
1352
+ }
1353
+ if (stored && !opts.refreshAccount) {
1354
+ console.log(` Using stored accountId ${stored} from profiles/bots.json (no lookup needed; pass --refresh-account to re-verify)`);
1355
+ accountId = stored;
1356
+ } else {
1357
+ const resolved = await resolveAccountId(accountId, opts.node);
1358
+ if (!resolved) {
1359
+ console.error(` Could not resolve "${accountId}" to an account ID`);
1360
+ process.exit(1);
1361
+ }
1362
+ accountId = resolved;
1363
+ if (matchedKey && persistBotAccountId(matchedKey, resolved)) {
1364
+ console.log(` Stored accountId ${resolved} in profiles/bots.json`);
1365
+ }
1337
1366
  }
1338
- accountId = resolved;
1339
1367
  }
1340
1368
 
1341
1369
  // Build time range
@@ -2,6 +2,31 @@
2
2
 
3
3
  This exporter generates a standalone HTML chart in the `analysis/charts/` folder using the local `uPlot`-based TradingView-style renderer.
4
4
 
5
+ ## Recommended: `dexbot tv` (One Step)
6
+
7
+ `dexbot tv` fetches the candles itself (chunked Kibana scans for pools, order-book fallback for pairs) and then renders through this exporter — no manual fetch/export steps needed:
8
+
9
+ ```bash
10
+ # Bot chart with AMA overlay (bot key from profiles/bots.json, default: 3 months)
11
+ dexbot tv <bot>
12
+
13
+ # Any pool by bare or full ID
14
+ dexbot tv 133
15
+ dexbot tv 1.19.133
16
+
17
+ # Any pair (pool-first, order-book fallback)
18
+ dexbot tv TOKENA/TOKENB
19
+
20
+ # Options
21
+ dexbot tv <bot> --month 6 # months of 1h candles (default: 3)
22
+ dexbot tv <bot> --chart analysis/charts/custom.html
23
+ dexbot tv <bot> --scale linear # log (default) or linear
24
+ ```
25
+
26
+ Output: `analysis/charts/tv_<bot|pool_<id>|<a>_<b>>_1h_<N>m.html`, with a clickable `file://` link printed on completion. Unknown targets fail fast with the list of known bot keys.
27
+
28
+ The sections below cover manual usage (explicit candle files, direct runner flags).
29
+
5
30
  ## What It Produces
6
31
 
7
32
  - Log-scale price chart
@@ -11,7 +11,7 @@ import { getErrorMessage } from '../../modules/utils/errors.js';
11
11
  import { toIntervalLabel } from '../../market_adapter/interval_utils.js';
12
12
  import { loadBotMeta } from '../bot_key_utils.js';
13
13
  import { resolveSource, listAvailableBots } from '../resolve_source.js';
14
- import { writeChartFile } from '../chart_utils.js';
14
+ import { writeChartFile, toFileUrl } from '../chart_utils.js';
15
15
  import { PATHS } from '../../modules/paths.js';
16
16
 
17
17
 
@@ -148,7 +148,7 @@ async function main() {
148
148
 
149
149
  writeChartFile(config.chartFile, html);
150
150
 
151
- if (!config.quiet) console.log(`[TradingView] ✓ Chart saved to ${config.chartFile}`);
151
+ if (!config.quiet) console.log(`\n[TradingView] ✓ Chart saved. Open chart: (${toFileUrl(config.chartFile)})`);
152
152
  } catch (err: any) {
153
153
  console.error(`[TradingView] Error: ${getErrorMessage(err)}`);
154
154
  process.exit(1);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trend-detection",
3
- "version": "1.4.25",
3
+ "version": "1.5.0",
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": {
@@ -9,6 +9,7 @@ import { loadSettingsFile, resolveRawBotEntries, saveSettingsFile, normalizeBotE
9
9
  import { normalizeMode, detectMode, setPreferredMode, describeModeChoice } from './launcher_mode_detector.js';
10
10
  import { normalizeRoot, resolveRuntimeScript, normalizeProfileDir } from './launcher_paths.js';
11
11
  import { getErrorMessage } from '../../modules/utils/errors.js';
12
+ import { isSameBotName } from '../../modules/utils/sanitize_key.js';
12
13
 
13
14
  const storage = getStorage();
14
15
 
@@ -174,7 +175,7 @@ async function launcherReset(botName: string | null, options: Record<string, any
174
175
  const { config } = loadSettingsFile(PROFILES_BOTS_FILE);
175
176
  const entries = normalizeBotEntries(resolveRawBotEntries(config));
176
177
 
177
- const targets = botName ? entries.filter((b: any) => b.name === botName) : entries.filter((b: any) => b.active !== false);
178
+ const targets = botName ? entries.filter((b: any) => isSameBotName(b.name, botName)) : entries.filter((b: any) => b.active !== false);
178
179
 
179
180
  if (botName && targets.length === 0) {
180
181
  throw new Error(`Bot '${botName}' not found in ${PROFILES_BOTS_FILE}`);
@@ -235,7 +236,7 @@ async function launcherDisable(botName: string | null, options: Record<string, a
235
236
  };
236
237
  }
237
238
 
238
- const match = entries.find((b: any) => b.name === botName);
239
+ const match = entries.find((b: any) => isSameBotName(b.name, botName));
239
240
  if (!match) {
240
241
  throw new Error(`Bot '${botName}' not found in ${PROFILES_BOTS_FILE}`);
241
242
  }
@@ -268,7 +269,7 @@ async function launcherPm2Start(botName: string | null, options: Record<string,
268
269
  const { config } = loadSettingsFile(PROFILES_BOTS_FILE);
269
270
  const entries = normalizeBotEntries(resolveRawBotEntries(config));
270
271
 
271
- const targets = botName ? entries.filter((b: any) => b.name === botName) : entries.filter((b: any) => b.active !== false);
272
+ const targets = botName ? entries.filter((b: any) => isSameBotName(b.name, botName)) : entries.filter((b: any) => b.active !== false);
272
273
 
273
274
  if (botName && targets.length === 0) {
274
275
  throw new Error(`Bot '${botName}' not found or not active in ${PROFILES_BOTS_FILE}`);
@@ -287,7 +288,7 @@ async function launcherPm2Start(botName: string | null, options: Record<string,
287
288
  // Filter apps if specific bot requested
288
289
  let appsToStart = apps;
289
290
  if (botName) {
290
- appsToStart = apps.filter((app: any) => app.name === botName);
291
+ appsToStart = apps.filter((app: any) => isSameBotName(app.name, botName));
291
292
  if (appsToStart.length === 0) {
292
293
  throw new Error(`No PM2 app found for bot '${botName}'`);
293
294
  }
@@ -3,6 +3,7 @@
3
3
  import { PATHS } from '../../modules/paths.js';
4
4
  import CreditRuntime from '../../modules/credit_runtime.js';
5
5
  import { getErrorMessage } from '../../modules/utils/errors.js';
6
+ import { isSameBotName } from '../../modules/utils/sanitize_key.js';
6
7
 
7
8
 
8
9
  function createCreditRuntimeAdapter(infra: any, options: Record<string, any> = {}) {
@@ -55,7 +56,7 @@ function createCreditRuntimeAdapter(infra: any, options: Record<string, any> = {
55
56
  try {
56
57
  const bundle = await infra.profiles.loadBundle();
57
58
  const match = bundle.bots.find(
58
- (b: Record<string, any>) => b.botKey === botRef || b.name === botRef,
59
+ (b: Record<string, any>) => isSameBotName(b.botKey, botRef) || isSameBotName(b.name, botRef),
59
60
  );
60
61
  return match || null;
61
62
  } catch {
@@ -10,6 +10,7 @@ import { acquireFileLock } from '../../market_adapter/utils/file_lock.js';
10
10
  import { assertNoDuplicateBotKeys } from '../../modules/bot_settings.js';
11
11
  import { clone } from './utils.js';
12
12
  import { createBotKey, sanitizeKey } from '../../modules/account_orders.js';
13
+ import { isSameBotName } from '../../modules/utils/sanitize_key.js';
13
14
  const storage = getStorage();
14
15
 
15
16
  import type { BotSettings, ProfileOptions, ClawProfileBundle } from './types.js';
@@ -815,10 +816,10 @@ function matchBotIdentifier(bot: any, identifier: any) {
815
816
  }
816
817
 
817
818
  if (typeof identifier === 'object') {
818
- if (identifier.botKey && bot.botKey === identifier.botKey) {
819
+ if (identifier.botKey && (bot.botKey === identifier.botKey || isSameBotName(bot.botKey, identifier.botKey))) {
819
820
  return true;
820
821
  }
821
- if (identifier.name && bot.name === identifier.name) {
822
+ if (identifier.name && isSameBotName(bot.name, identifier.name)) {
822
823
  return true;
823
824
  }
824
825
  if (identifier.assetA && identifier.assetB && bot.assetA === identifier.assetA && bot.assetB === identifier.assetB) {
@@ -839,7 +840,7 @@ function matchBotIdentifier(bot: any, identifier: any) {
839
840
  return false;
840
841
  }
841
842
 
842
- if (bot.botKey === value || bot.name === value) {
843
+ if (bot.botKey === value || isSameBotName(bot.botKey, value) || isSameBotName(bot.name, value)) {
843
844
  return true;
844
845
  }
845
846
 
package/claw/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dexbot2-claw",
3
- "version": "1.4.25",
3
+ "version": "1.5.0",
4
4
  "private": true,
5
5
  "description": "Claw bridge and runtime integration layer for BitShares and DEXBot2.",
6
6
  "type": "module",