dexbot 1.6.1 → 1.6.2

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 (91) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/analysis/ama_fitting/package.json +1 -1
  3. package/analysis/tradingview/tradingview_uplot_chart_generator.ts +19 -5
  4. package/analysis/trend_detection/package.json +1 -1
  5. package/claw/package.json +1 -1
  6. package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
  7. package/claw/runtimes/openclaw-plugin/package.json +1 -1
  8. package/claw/tests/test_claw_mcp_transport.ts +2 -2
  9. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -1
  10. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +19 -5
  11. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -1
  12. package/dist/market_adapter/inputs/fetch_book_data.js +2 -2
  13. package/dist/market_adapter/inputs/fetch_book_data.js.map +1 -1
  14. package/dist/market_adapter/inputs/fetch_lp_data.d.ts +22 -3
  15. package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
  16. package/dist/market_adapter/inputs/fetch_lp_data.js +15 -15
  17. package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
  18. package/dist/market_adapter/inputs/kibana_feed_source.js +2 -2
  19. package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
  20. package/dist/market_adapter/inputs/window_cache.d.ts +54 -8
  21. package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
  22. package/dist/market_adapter/inputs/window_cache.js +328 -87
  23. package/dist/market_adapter/inputs/window_cache.js.map +1 -1
  24. package/dist/modules/bitshares-native/subscriptions.d.ts +1 -1
  25. package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -1
  26. package/dist/modules/bitshares-native/subscriptions.js +18 -4
  27. package/dist/modules/bitshares-native/subscriptions.js.map +1 -1
  28. package/dist/modules/bitshares_client.d.ts.map +1 -1
  29. package/dist/modules/bitshares_client.js +10 -0
  30. package/dist/modules/bitshares_client.js.map +1 -1
  31. package/dist/modules/constants.d.ts +7 -3
  32. package/dist/modules/constants.d.ts.map +1 -1
  33. package/dist/modules/constants.js +44 -20
  34. package/dist/modules/constants.js.map +1 -1
  35. package/dist/modules/credit_runtime.d.ts.map +1 -1
  36. package/dist/modules/credit_runtime.js +24 -6
  37. package/dist/modules/credit_runtime.js.map +1 -1
  38. package/dist/modules/dexbot_class.d.ts +31 -6
  39. package/dist/modules/dexbot_class.d.ts.map +1 -1
  40. package/dist/modules/dexbot_class.js +55 -11
  41. package/dist/modules/dexbot_class.js.map +1 -1
  42. package/dist/modules/dexbot_cow_runtime.d.ts +57 -6
  43. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  44. package/dist/modules/dexbot_cow_runtime.js +474 -38
  45. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  46. package/dist/modules/dexbot_fill_runtime.d.ts +28 -1
  47. package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
  48. package/dist/modules/dexbot_fill_runtime.js +116 -3
  49. package/dist/modules/dexbot_fill_runtime.js.map +1 -1
  50. package/dist/modules/dexbot_maintenance_runtime.d.ts +46 -0
  51. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  52. package/dist/modules/dexbot_maintenance_runtime.js +187 -5
  53. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  54. package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
  55. package/dist/modules/dexbot_startup_runtime.js +6 -1
  56. package/dist/modules/dexbot_startup_runtime.js.map +1 -1
  57. package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
  58. package/dist/modules/dexbot_state_recovery.js +16 -3
  59. package/dist/modules/dexbot_state_recovery.js.map +1 -1
  60. package/dist/modules/order/grid.d.ts +3 -1
  61. package/dist/modules/order/grid.d.ts.map +1 -1
  62. package/dist/modules/order/grid.js +47 -6
  63. package/dist/modules/order/grid.js.map +1 -1
  64. package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
  65. package/dist/modules/order/grid_reconcile_internal.js +39 -4
  66. package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
  67. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  68. package/dist/modules/order/sync_engine.js +183 -10
  69. package/dist/modules/order/sync_engine.js.map +1 -1
  70. package/dist/modules/order/utils/math.d.ts +60 -14
  71. package/dist/modules/order/utils/math.d.ts.map +1 -1
  72. package/dist/modules/order/utils/math.js +87 -20
  73. package/dist/modules/order/utils/math.js.map +1 -1
  74. package/dist/modules/order/utils/order.d.ts +51 -1
  75. package/dist/modules/order/utils/order.d.ts.map +1 -1
  76. package/dist/modules/order/utils/order.js +118 -1
  77. package/dist/modules/order/utils/order.js.map +1 -1
  78. package/dist/modules/utils/errors.d.ts +22 -0
  79. package/dist/modules/utils/errors.d.ts.map +1 -1
  80. package/dist/modules/utils/errors.js +30 -0
  81. package/dist/modules/utils/errors.js.map +1 -1
  82. package/docs/COPY_ON_WRITE_MASTER_PLAN.md +1 -1
  83. package/docs/DEXBOT_COMPARISON.md +3 -7
  84. package/docs/EVOLUTION.md +8 -7
  85. package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +4 -9
  86. package/docs/LIFECYCLE.md +2 -2
  87. package/docs/README.md +9 -1
  88. package/docs/architecture.md +7 -7
  89. package/docs/developer_guide.md +4 -4
  90. package/market_adapter/README.md +1 -1
  91. package/package.json +1 -1
@@ -64,6 +64,7 @@ function liveWindowIdSet(...args) { return require('./order/utils/order').liveWi
64
64
  function resolveLiveReserveEdgeAnchorPrice(...args) { return require('./order/utils/order').resolveLiveReserveEdgeAnchorPrice(...args); }
65
65
  function formatUnmatchedChainOrder(...args) { return require('./order/utils/order').formatUnmatchedChainOrder(...args); }
66
66
  function isNonBlockingUnmatchedOrder(...args) { return require('./order/utils/order').isNonBlockingUnmatchedOrder(...args); }
67
+ function isStrandedHoldOrder(...args) { return require('./order/utils/order').isStrandedHoldOrder(...args); }
67
68
  function getSideBudget(...args) { return require('./order/utils/order').getSideBudget(...args); }
68
69
  function getActiveOrdersTotal(config) { return require('./order/utils/order').getActiveOrdersTotal(config); }
69
70
  function correctAllPriceMismatches(...args) { return require('./order/utils/order').correctAllPriceMismatches(...args); }
@@ -1459,21 +1460,201 @@ async function performPeriodicGridChecks(bot) {
1459
1460
  * total (funds stay locked until an operator clears them) without re-logging
1460
1461
  * the same count every tick.
1461
1462
  *
1463
+ * The detail line names WHY each hold is held and how far it sits from the
1464
+ * grid, because "held" alone is ambiguous between deliberate deferral and a
1465
+ * stuck bot. A slow re-warn (rate-limited) additionally distinguishes a hold
1466
+ * that is still being actioned from one that has been static for a long time.
1467
+ *
1462
1468
  * @param {import('./dexbot_class.js').DEXBot} bot
1463
1469
  */
1464
1470
  function logDeferredHoldSummary(bot) {
1465
1471
  const unmatched = Array.isArray(bot.manager?._lastUnmatchedChainOrders)
1466
1472
  ? bot.manager._lastUnmatchedChainOrders
1467
1473
  : [];
1468
- const held = unmatched.filter((u) => isNonBlockingUnmatchedOrder(u)).length;
1474
+ const heldOrders = unmatched.filter((u) => isNonBlockingUnmatchedOrder(u));
1475
+ const held = heldOrders.length;
1469
1476
  if (held === 0) {
1470
- bot._lastHeldChainOrderCount = 0;
1477
+ bot._lastHeldChainOrderSignature = '';
1478
+ bot._lastHeldChainOrderWarnAt = 0;
1479
+ bot._lastHeldChainOrderSignatureSince = 0;
1480
+ bot._strandedHoldSince = new Map();
1471
1481
  return;
1472
1482
  }
1473
- if (held === bot._lastHeldChainOrderCount)
1483
+ // Per-order age tracking for the stranded subset.
1484
+ //
1485
+ // The whole-set signature below includes every entry's reason, so an
1486
+ // UNRELATED hold flapping in and out (e.g. a per-sync
1487
+ // `broadcast-active-deferred`) changes the signature and restarts the age
1488
+ // clock every cycle. A genuinely stranded order then never reaches the
1489
+ // escalation age -- it is starved forever by churn it has nothing to do
1490
+ // with. Verified by simulation: with the whole-set clock, a stranded
1491
+ // order plus a 6-hourly flap reported `CLOCK RESET` on every single tick
1492
+ // and never accumulated age.
1493
+ //
1494
+ // So age is tracked per stranded order identity (id@price/size), keyed off
1495
+ // when THAT order was first seen stranded. Unrelated churn cannot touch it.
1496
+ if (!(bot._strandedHoldSince instanceof Map))
1497
+ bot._strandedHoldSince = new Map();
1498
+ const strandedHoldSince = bot._strandedHoldSince;
1499
+ const stranded = heldOrders.filter((u) => isStrandedHoldOrder(u));
1500
+ const seenKeys = new Set();
1501
+ const now = Date.now();
1502
+ let oldestStrandedMs = 0;
1503
+ for (const u of stranded) {
1504
+ const key = `${u?.chainOrderId ?? '?'}@${u?.price ?? '?'}/${u?.size ?? '?'}:${u?.reason ?? '?'}`;
1505
+ seenKeys.add(key);
1506
+ if (!strandedHoldSince.has(key))
1507
+ strandedHoldSince.set(key, now);
1508
+ oldestStrandedMs = Math.max(oldestStrandedMs, now - Number(strandedHoldSince.get(key)));
1509
+ }
1510
+ // Drop entries that are no longer stranded, so a slot that clears and is
1511
+ // later re-held starts a fresh age rather than inheriting the old one.
1512
+ for (const key of Array.from(strandedHoldSince.keys())) {
1513
+ if (!seenKeys.has(key))
1514
+ strandedHoldSince.delete(key);
1515
+ }
1516
+ const signature = heldOrders
1517
+ .map((u) => `${u?.chainOrderId ?? '?'}@${u?.price ?? '?'}/${u?.size ?? '?'}:${u?.reason ?? '?'}`)
1518
+ .sort()
1519
+ .join(',');
1520
+ // A count-only change gate hid same-count churn (one hold clearing while
1521
+ // another appeared looked like "no change" and was never logged). The
1522
+ // signature covers membership and terms, so any real change re-logs once.
1523
+ if (signature !== bot._lastHeldChainOrderSignature) {
1524
+ bot._lastHeldChainOrderSignature = signature;
1525
+ bot._lastHeldChainOrderWarnAt = Date.now();
1526
+ // Restart the whole-set logging clock: the signature changed, so this
1527
+ // is a new hold situation and the running total is worth re-logging.
1528
+ // NOTE: this clock drives LOGGING ONLY. Escalation below uses the
1529
+ // per-order stranded ages, because a signature change caused by
1530
+ // unrelated churn must not suppress escalation -- returning early here
1531
+ // (as an earlier revision did) meant any flapping hold skipped the
1532
+ // escalation call entirely, so a genuinely stranded order never
1533
+ // escalated at all.
1534
+ bot._lastHeldChainOrderSignatureSince = Date.now();
1535
+ bot._log?.(`[HOLD] ${held} deferred chain order(s) held outside the active pipeline ` +
1536
+ `(funds stay locked until cleared): ${describeDeferredHolds(bot, heldOrders)}`, 'warn');
1537
+ // Escalation is NOT gated on an unchanged signature: a stranded order
1538
+ // whose age has crossed the threshold must escalate even if an
1539
+ // unrelated transient hold is flapping in and out alongside it.
1540
+ considerDeferredHoldEscalation(bot, stranded, oldestStrandedMs);
1474
1541
  return;
1475
- bot._lastHeldChainOrderCount = held;
1476
- bot._log?.(`[HOLD] ${held} deferred chain order(s) held outside the active pipeline (funds stay locked until cleared)`, 'warn');
1542
+ }
1543
+ // The signature is unchanged.
1544
+ if (!(Number(bot._lastHeldChainOrderSignatureSince) > 0)) {
1545
+ bot._lastHeldChainOrderSignatureSince = Date.now();
1546
+ }
1547
+ // Escalate on the STRANDED subset's own age, not the whole held set's.
1548
+ considerDeferredHoldEscalation(bot, stranded, oldestStrandedMs);
1549
+ // Unchanged set: re-warn slowly so an indefinitely-held order is visibly
1550
+ // still held rather than silently indistinguishable from a stalled loop.
1551
+ const warnEvery = Number(TIMING.STALE_TOTALS_WARN_RATE_LIMIT_MS) || 60000;
1552
+ const sinceWarn = Date.now() - (Number(bot._lastHeldChainOrderWarnAt) || 0);
1553
+ if (sinceWarn >= warnEvery) {
1554
+ bot._lastHeldChainOrderWarnAt = Date.now();
1555
+ // Age from the SIGNATURE-STABLE clock, not `_lastUnmatchedChainOrdersAt`
1556
+ // (which refresh on every observing sync and would report "~0m" for a
1557
+ // hold that has really been stuck for hours).
1558
+ const heldSince = Number(bot._lastHeldChainOrderSignatureSince) || 0;
1559
+ const ageMin = heldSince > 0 ? Math.round((Date.now() - heldSince) / 60000) : 0;
1560
+ bot._log?.(`[HOLD] still holding ${held} deferred chain order(s) for ~${ageMin}m ` +
1561
+ `with no change (funds stay locked until cleared): ${describeDeferredHolds(bot, heldOrders)}`, 'warn');
1562
+ }
1563
+ }
1564
+ /**
1565
+ * Escalate an INDEFINITELY-HELD deferred hold to a structural resync.
1566
+ *
1567
+ * Out-of-rail orphans hold locked funds and are never auto-cancelled per cycle.
1568
+ * That default is correct -- cancelling on ambiguous evidence is irreversible --
1569
+ * but it left "held indefinitely" with no exit. A hold that survives unchanged
1570
+ * for `DEFERRED_HOLD_ESCALATE_MS` is no longer ambiguous evidence: the grid has
1571
+ * had ample opportunity to resolve it and has not.
1572
+ *
1573
+ * The structural resync is the appropriate exit because the full reset's
1574
+ * reconcile is update-first: unmatched chain orders are price-updated onto rail
1575
+ * slots (emitting the rail's genesis level, so the reconcile-update guard does
1576
+ * not block it) and only true surplus is cancelled. Funds are released without
1577
+ * introducing any new cancellation policy.
1578
+ *
1579
+ * Fire-and-forget; the resync is itself debounced and batch-in-flight aware.
1580
+ */
1581
+ function considerDeferredHoldEscalation(bot, strandedOrders, strandedMs) {
1582
+ const escalateMs = Number(TIMING?.DEFERRED_HOLD_ESCALATE_MS) > 0
1583
+ ? Number(TIMING.DEFERRED_HOLD_ESCALATE_MS)
1584
+ : 24 * 60 * 60 * 1000;
1585
+ const held = strandedOrders.length;
1586
+ if (held === 0)
1587
+ return;
1588
+ // Age is supplied by the caller: the oldest STRANDED order's age, measured
1589
+ // from when that order was first seen stranded (per-order clock). It is
1590
+ // deliberately NOT the whole held-set signature age, which unrelated
1591
+ // flapping holds reset every cycle and which would starve escalation.
1592
+ const heldMs = Number(strandedMs) || 0;
1593
+ if (heldMs < escalateMs)
1594
+ return;
1595
+ if (typeof bot?.manager?.requestStructuralGridResync !== 'function') {
1596
+ bot?._log?.(`[HOLD] ${held} deferred chain order(s) held unchanged for ${Math.round(heldMs / 3600000)}h ` +
1597
+ `but requestStructuralGridResync is unavailable; funds stay locked`, 'error');
1598
+ return;
1599
+ }
1600
+ const cooldownMs = Number(TIMING?.DEFERRED_HOLD_RESYNC_COOLDOWN_MS) > 0
1601
+ ? Number(TIMING.DEFERRED_HOLD_RESYNC_COOLDOWN_MS)
1602
+ : 6 * 60 * 60 * 1000;
1603
+ const now = Date.now();
1604
+ const lastAt = Number(bot._lastDeferredHoldResyncAt) || 0;
1605
+ if (now - lastAt < cooldownMs)
1606
+ return;
1607
+ bot._lastDeferredHoldResyncAt = now;
1608
+ const heldHours = Math.round(heldMs / 3600000);
1609
+ bot?._log?.(`[HOLD] ${held} deferred chain order(s) held unchanged for ~${heldHours}h; ` +
1610
+ `requesting structural resync to resolve the hold (reconcile is update-first, ` +
1611
+ `so funds are released by price-updating onto rail slots) — ` +
1612
+ `${describeDeferredHolds(bot, strandedOrders)}`, 'error');
1613
+ try {
1614
+ const res = bot.manager.requestStructuralGridResync('deferred-hold-stale', {
1615
+ reason: `${held} deferred chain order(s) held unchanged for ~${heldHours}h`,
1616
+ heldCount: held,
1617
+ heldMs,
1618
+ });
1619
+ res?.catch?.((err) => {
1620
+ bot.manager?.logger?.log?.(`[HOLD] Structural resync for stale hold failed: ${getErrorMessage(err)}`, 'error');
1621
+ });
1622
+ }
1623
+ catch (err) {
1624
+ bot.manager?.logger?.log?.(`[HOLD] Structural resync for stale hold failed: ${getErrorMessage(err)}`, 'error');
1625
+ }
1626
+ }
1627
+ /**
1628
+ * Render the per-order detail for a deferred-hold summary: side, price, size,
1629
+ * why it is held, and distance from the nearest grid bound when known.
1630
+ *
1631
+ * Distance is expressed as a percentage of the bound so the operator can tell
1632
+ * a near-miss (a few bps outside, likely a rounding/drift artifact) from a
1633
+ * deliberately-placed far order (dip protection after a grid reset).
1634
+ */
1635
+ function describeDeferredHolds(bot, heldOrders) {
1636
+ const genesis = bot.manager?._genesis;
1637
+ const levels = Array.isArray(genesis?.priceLevels) ? genesis.priceLevels : [];
1638
+ const lower = levels.length > 0 ? Number(levels[0]) : NaN;
1639
+ const upper = levels.length > 0 ? Number(levels[levels.length - 1]) : NaN;
1640
+ return heldOrders
1641
+ .map((u) => {
1642
+ const side = u?.type === ORDER_TYPES.SELL ? 'sell' : u?.type === ORDER_TYPES.BUY ? 'buy' : 'unknown';
1643
+ const price = Number(u?.price);
1644
+ const size = u?.size != null ? u.size : '?';
1645
+ const reason = u?.reason || 'unspecified';
1646
+ const parts = [`${u?.chainOrderId ?? '?'} ${side} price=${Number.isFinite(price) ? price : '?'} size=${size} reason=${reason}`];
1647
+ if (Number.isFinite(price)) {
1648
+ if (Number.isFinite(lower) && price < lower) {
1649
+ parts.push(`below-grid by ${(((lower - price) / lower) * 100).toFixed(4)}% (bound ${lower})`);
1650
+ }
1651
+ else if (Number.isFinite(upper) && price > upper) {
1652
+ parts.push(`above-grid by ${(((price - upper) / upper) * 100).toFixed(4)}% (bound ${upper})`);
1653
+ }
1654
+ }
1655
+ return parts.join(' ');
1656
+ })
1657
+ .join(' | ');
1477
1658
  }
1478
1659
  /**
1479
1660
  * Check if the continuous open-orders sync loop is enabled.
@@ -2954,5 +3135,6 @@ export default {
2954
3135
  markGridActivity,
2955
3136
  getMetrics,
2956
3137
  syncOpenOrdersAndProcessFills,
3138
+ _internalDeferredHold: { logDeferredHoldSummary, describeDeferredHolds, considerDeferredHoldEscalation },
2957
3139
  };
2958
3140
  //# sourceMappingURL=dexbot_maintenance_runtime.js.map