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.
- package/CHANGELOG.md +20 -0
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/tradingview/tradingview_uplot_chart_generator.ts +19 -5
- package/analysis/trend_detection/package.json +1 -1
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +19 -5
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_book_data.js +2 -2
- package/dist/market_adapter/inputs/fetch_book_data.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +22 -3
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +15 -15
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js +2 -2
- package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
- package/dist/market_adapter/inputs/window_cache.d.ts +54 -8
- package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
- package/dist/market_adapter/inputs/window_cache.js +328 -87
- package/dist/market_adapter/inputs/window_cache.js.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.d.ts +1 -1
- package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.js +18 -4
- package/dist/modules/bitshares-native/subscriptions.js.map +1 -1
- package/dist/modules/bitshares_client.d.ts.map +1 -1
- package/dist/modules/bitshares_client.js +10 -0
- package/dist/modules/bitshares_client.js.map +1 -1
- package/dist/modules/constants.d.ts +7 -3
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +44 -20
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +24 -6
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +31 -6
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +55 -11
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +57 -6
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +474 -38
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +28 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +116 -3
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +46 -0
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +187 -5
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_startup_runtime.js +6 -1
- package/dist/modules/dexbot_startup_runtime.js.map +1 -1
- package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
- package/dist/modules/dexbot_state_recovery.js +16 -3
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/order/grid.d.ts +3 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +47 -6
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +39 -4
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +183 -10
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +60 -14
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +87 -20
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +51 -1
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +118 -1
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/utils/errors.d.ts +22 -0
- package/dist/modules/utils/errors.d.ts.map +1 -1
- package/dist/modules/utils/errors.js +30 -0
- package/dist/modules/utils/errors.js.map +1 -1
- package/docs/COPY_ON_WRITE_MASTER_PLAN.md +1 -1
- package/docs/DEXBOT_COMPARISON.md +3 -7
- package/docs/EVOLUTION.md +8 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +4 -9
- package/docs/LIFECYCLE.md +2 -2
- package/docs/README.md +9 -1
- package/docs/architecture.md +7 -7
- package/docs/developer_guide.md +4 -4
- package/market_adapter/README.md +1 -1
- 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
|
|
1474
|
+
const heldOrders = unmatched.filter((u) => isNonBlockingUnmatchedOrder(u));
|
|
1475
|
+
const held = heldOrders.length;
|
|
1469
1476
|
if (held === 0) {
|
|
1470
|
-
bot.
|
|
1477
|
+
bot._lastHeldChainOrderSignature = '';
|
|
1478
|
+
bot._lastHeldChainOrderWarnAt = 0;
|
|
1479
|
+
bot._lastHeldChainOrderSignatureSince = 0;
|
|
1480
|
+
bot._strandedHoldSince = new Map();
|
|
1471
1481
|
return;
|
|
1472
1482
|
}
|
|
1473
|
-
|
|
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
|
-
|
|
1476
|
-
|
|
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
|