dexbot 1.4.24 → 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.
- package/CHANGELOG.md +67 -0
- package/README.md +7 -2
- package/analysis/README.md +61 -5
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/analyze_derivatives.ts +1 -1
- package/analysis/bot_key_utils.ts +272 -3
- package/analysis/bot_usage/discover_bot_accounts.ts +64 -9
- package/analysis/bot_usage/kibana_bot_queries.ts +40 -1
- package/analysis/chart_utils.ts +6 -3
- package/analysis/grid_correction_check.ts +932 -0
- package/analysis/resolve_bot_accounts.ts +199 -0
- package/analysis/trade_profitability.ts +36 -8
- package/analysis/tradingview/README.md +25 -0
- package/analysis/tradingview/analyze_tradingview.ts +2 -2
- package/analysis/trend_detection/package.json +1 -1
- package/claw/modules/claw_launcher.ts +5 -4
- package/claw/modules/credit_runtime_adapter.ts +2 -1
- package/claw/modules/dexbot_profiles.ts +4 -3
- 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/analyze_derivatives.js +1 -1
- package/dist/analysis/bot_key_utils.d.ts +28 -1
- package/dist/analysis/bot_key_utils.d.ts.map +1 -1
- package/dist/analysis/bot_key_utils.js +385 -3
- package/dist/analysis/bot_key_utils.js.map +1 -1
- package/dist/analysis/bot_usage/discover_bot_accounts.js +60 -10
- package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +45 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js +39 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
- package/dist/analysis/chart_utils.d.ts +2 -1
- package/dist/analysis/chart_utils.d.ts.map +1 -1
- package/dist/analysis/chart_utils.js +4 -1
- package/dist/analysis/chart_utils.js.map +1 -1
- package/dist/analysis/grid_correction_check.d.ts +90 -0
- package/dist/analysis/grid_correction_check.d.ts.map +1 -0
- package/dist/analysis/grid_correction_check.js +901 -0
- package/dist/analysis/grid_correction_check.js.map +1 -0
- package/dist/analysis/resolve_bot_accounts.d.ts +3 -0
- package/dist/analysis/resolve_bot_accounts.d.ts.map +1 -0
- package/dist/analysis/resolve_bot_accounts.js +216 -0
- package/dist/analysis/resolve_bot_accounts.js.map +1 -0
- package/dist/analysis/trade_profitability.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.js +40 -8
- package/dist/analysis/trade_profitability.js.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.js +2 -2
- package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +2 -1
- package/dist/bot.js.map +1 -1
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +134 -22
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/ama_signal_runner.js +2 -2
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +2 -1
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +4 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +3 -2
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/modules/account_bots.d.ts +42 -1
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +221 -22
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/account_orders.d.ts +7 -10
- package/dist/modules/account_orders.d.ts.map +1 -1
- package/dist/modules/account_orders.js +26 -2
- package/dist/modules/account_orders.js.map +1 -1
- package/dist/modules/bitshares_client.d.ts +4 -1
- package/dist/modules/bitshares_client.d.ts.map +1 -1
- package/dist/modules/bitshares_client.js +12 -4
- package/dist/modules/bitshares_client.js.map +1 -1
- package/dist/modules/bot_settings.d.ts.map +1 -1
- package/dist/modules/bot_settings.js +2 -1
- package/dist/modules/bot_settings.js.map +1 -1
- package/dist/modules/chain_orders.d.ts.map +1 -1
- package/dist/modules/chain_orders.js +21 -2
- package/dist/modules/chain_orders.js.map +1 -1
- package/dist/modules/cli_colors.d.ts +39 -0
- package/dist/modules/cli_colors.d.ts.map +1 -0
- package/dist/modules/cli_colors.js +42 -0
- package/dist/modules/cli_colors.js.map +1 -0
- package/dist/modules/config.d.ts.map +1 -1
- package/dist/modules/config.js +27 -0
- package/dist/modules/config.js.map +1 -1
- package/dist/modules/constants.d.ts +14 -1
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +66 -11
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_pricing.d.ts +61 -0
- package/dist/modules/credit_pricing.d.ts.map +1 -0
- package/dist/modules/credit_pricing.js +237 -0
- package/dist/modules/credit_pricing.js.map +1 -0
- package/dist/modules/credit_runtime.d.ts +1 -0
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +49 -107
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +25 -0
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +82 -12
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +39 -17
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +875 -134
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +11 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +108 -1
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +29 -14
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +279 -63
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_startup_runtime.d.ts +1 -0
- package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_startup_runtime.js +58 -7
- 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 +55 -3
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/fund_registry.d.ts +1 -1
- package/dist/modules/fund_registry.js +1 -1
- package/dist/modules/graceful_shutdown.d.ts +12 -1
- package/dist/modules/graceful_shutdown.d.ts.map +1 -1
- package/dist/modules/graceful_shutdown.js +39 -1
- package/dist/modules/graceful_shutdown.js.map +1 -1
- package/dist/modules/key_store.d.ts.map +1 -1
- package/dist/modules/key_store.js +38 -1
- package/dist/modules/key_store.js.map +1 -1
- package/dist/modules/launcher/adapter_requirement.d.ts +38 -0
- package/dist/modules/launcher/adapter_requirement.d.ts.map +1 -0
- package/dist/modules/launcher/adapter_requirement.js +141 -0
- package/dist/modules/launcher/adapter_requirement.js.map +1 -0
- package/dist/modules/launcher/bot_supervisor.d.ts +2 -2
- package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -1
- package/dist/modules/launcher/bot_supervisor.js +32 -3
- package/dist/modules/launcher/bot_supervisor.js.map +1 -1
- package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
- package/dist/modules/launcher/launch_modes.js +5 -4
- package/dist/modules/launcher/launch_modes.js.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +12 -8
- package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
- package/dist/modules/launcher/status_reporting.d.ts +6 -6
- package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
- package/dist/modules/launcher/status_reporting.js +9 -6
- package/dist/modules/launcher/status_reporting.js.map +1 -1
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +20 -3
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/export.d.ts +1 -1
- package/dist/modules/order/export.js +1 -1
- package/dist/modules/order/grid.d.ts +1 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +335 -43
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts +4 -2
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +242 -222
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +11 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +132 -6
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/logger.d.ts +23 -1
- package/dist/modules/order/logger.d.ts.map +1 -1
- package/dist/modules/order/logger.js +60 -17
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts +68 -14
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +281 -52
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/strategy.d.ts.map +1 -1
- package/dist/modules/order/strategy.js +25 -8
- package/dist/modules/order/strategy.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +262 -167
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +47 -5
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +175 -11
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +16 -15
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +199 -32
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/slot.d.ts +8 -0
- package/dist/modules/order/utils/slot.d.ts.map +1 -0
- package/dist/modules/order/utils/slot.js +16 -0
- package/dist/modules/order/utils/slot.js.map +1 -0
- package/dist/modules/order/utils/system.d.ts +52 -4
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +109 -16
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/validate.d.ts +6 -9
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +107 -85
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/order/working_grid.d.ts +5 -0
- package/dist/modules/order/working_grid.d.ts.map +1 -1
- package/dist/modules/order/working_grid.js +21 -0
- package/dist/modules/order/working_grid.js.map +1 -1
- package/dist/modules/paths.d.ts +4 -2
- package/dist/modules/paths.d.ts.map +1 -1
- package/dist/modules/paths.js +79 -24
- package/dist/modules/paths.js.map +1 -1
- package/dist/modules/utils/chain_logs.d.ts +11 -0
- package/dist/modules/utils/chain_logs.d.ts.map +1 -0
- package/dist/modules/utils/chain_logs.js +23 -0
- package/dist/modules/utils/chain_logs.js.map +1 -0
- package/dist/modules/utils/sanitize_key.d.ts +7 -0
- package/dist/modules/utils/sanitize_key.d.ts.map +1 -1
- package/dist/modules/utils/sanitize_key.js +15 -0
- package/dist/modules/utils/sanitize_key.js.map +1 -1
- package/dist/pm2.d.ts +11 -1
- package/dist/pm2.d.ts.map +1 -1
- package/dist/pm2.js +66 -15
- package/dist/pm2.js.map +1 -1
- package/dist/scripts/analyze-credit.d.ts +4 -0
- package/dist/scripts/analyze-credit.d.ts.map +1 -0
- package/dist/scripts/analyze-credit.js +760 -0
- package/dist/scripts/analyze-credit.js.map +1 -0
- package/dist/scripts/analyze-orders.d.ts +1 -10
- package/dist/scripts/analyze-orders.d.ts.map +1 -1
- package/dist/scripts/analyze-orders.js +34 -36
- package/dist/scripts/analyze-orders.js.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.d.ts +2 -5
- package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.js +83 -1
- package/dist/scripts/generate_market_adapter_whitelist.js.map +1 -1
- package/dist/scripts/test-credit-renewal.d.ts.map +1 -1
- package/dist/scripts/test-credit-renewal.js +37 -5
- package/dist/scripts/test-credit-renewal.js.map +1 -1
- package/dist/scripts/tv.d.ts +11 -0
- package/dist/scripts/tv.d.ts.map +1 -0
- package/dist/scripts/tv.js +357 -0
- package/dist/scripts/tv.js.map +1 -0
- package/dist/scripts/update.js +77 -10
- package/dist/scripts/update.js.map +1 -1
- package/dist/unlock.d.ts.map +1 -1
- package/dist/unlock.js +20 -8
- package/dist/unlock.js.map +1 -1
- package/docs/DEXBOT_COMPARISON.md +11 -11
- package/docs/EVOLUTION.md +10 -8
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/GRID_RECALCULATION.md +6 -0
- package/docs/MPA_CREDIT_USAGE.md +3 -1
- package/docs/README.md +2 -2
- package/docs/WORKFLOW.md +3 -1
- package/market_adapter/README.md +10 -0
- package/package.json +3 -1
- package/scripts/README.md +16 -0
|
@@ -27,9 +27,9 @@ import SyncEngine from './sync_engine.js';
|
|
|
27
27
|
import { calculateCurrentSpread, checkSpreadCondition, checkGridHealth } from './grid.js';
|
|
28
28
|
import * as Format from './format.js';
|
|
29
29
|
import { ORDER_TYPES, ORDER_STATES, REBALANCE_STATES, DEFAULT_CONFIG, TIMING, LOG_LEVEL, PIPELINE_TIMING, COW_PERFORMANCE, COW_ACTIONS } from '../constants.js';
|
|
30
|
-
import { getMinOrderSize, computeChainFundTotals, hasValidAccountTotals, resolveConfigValueWithRegistry, isExplicitZeroAllocation, floatToBlockchainInt, validateBoundaryCommit, resolveGapSlots
|
|
30
|
+
import { getMinOrderSize, computeChainFundTotals, hasValidAccountTotals, resolveConfigValueWithRegistry, isExplicitZeroAllocation, floatToBlockchainInt, validateBoundaryCommit, resolveGapSlots } from './utils/math.js';
|
|
31
31
|
import { validateOrder, validateGridForPersistence, validateWorkingGridFunds, checkFundDrift, reconcileGrid, optimizeRebalanceActions, projectTargetToWorkingGrid, buildStateUpdates, buildAbortedResult, buildSuccessResult, evaluateCommit } from './utils/validate.js';
|
|
32
|
-
import { resolveSpreadOrderSide, parseSlotIndex } from './utils/order.js';
|
|
32
|
+
import { resolveSpreadOrderSide, parseSlotIndex, parseChainOrder } from './utils/order.js';
|
|
33
33
|
import { getErrorMessage } from '../utils/errors.js';
|
|
34
34
|
const { toFiniteNumber } = Format;
|
|
35
35
|
// ===============================================================================
|
|
@@ -154,7 +154,9 @@ class COWRebalanceEngine {
|
|
|
154
154
|
if (reconcileResult.aborted) {
|
|
155
155
|
return buildAbortedResult(reconcileResult.reason);
|
|
156
156
|
}
|
|
157
|
-
const optimizedActions = optimizeRebalanceActions(reconcileResult.actions, masterGrid
|
|
157
|
+
const optimizedActions = optimizeRebalanceActions(reconcileResult.actions, masterGrid, {
|
|
158
|
+
logger: (msg, level) => this.logger?.log(msg, level)
|
|
159
|
+
});
|
|
158
160
|
// Stale-placement guard: drop placements crossing the plan's own
|
|
159
161
|
// boundary (see stalePlacementDropReason) — defer them to the next
|
|
160
162
|
// cycle instead of filling immediately.
|
|
@@ -198,6 +200,25 @@ class COWRebalanceEngine {
|
|
|
198
200
|
this.logger?.log(`[COW] Rebalance planning took ${duration}ms`, 'warn');
|
|
199
201
|
}
|
|
200
202
|
this.logger?.log(`[COW] Plan: Actions=${optimizedActions.length}, StateUpdates=${stateUpdates.length}`, 'info');
|
|
203
|
+
// Per-action plan detail. UPDATE/ROTATE ops are the dangerous ones —
|
|
204
|
+
// their target size/price must be visible in production logs without
|
|
205
|
+
// on-chain archaeology (a mis-sized update op is only diagnosable from
|
|
206
|
+
// the chain afterwards otherwise).
|
|
207
|
+
for (const a of optimizedActions) {
|
|
208
|
+
if (!a || !a.type || !a.id)
|
|
209
|
+
continue;
|
|
210
|
+
const size = Number.isFinite(Number(a.newSize))
|
|
211
|
+
? Number(a.newSize)
|
|
212
|
+
: Number(a.order?.size);
|
|
213
|
+
const price = Number.isFinite(Number(a.newPrice))
|
|
214
|
+
? Number(a.newPrice)
|
|
215
|
+
: Number(a.order?.price);
|
|
216
|
+
const rotationSuffix = (a.newGridId && a.newGridId !== a.id) ? ` -> ${a.newGridId}` : '';
|
|
217
|
+
const line = `[COW] Plan action: ${a.type} ${a.id}${rotationSuffix} ` +
|
|
218
|
+
`(orderId=${a.orderId || 'none'}, size=${Number.isFinite(size) ? Format.formatAmount(size) : 'n/a'}, ` +
|
|
219
|
+
`price=${Number.isFinite(price) ? Format.formatPrice6(price) : 'n/a'})`;
|
|
220
|
+
this.logger?.log(line, a.type === COW_ACTIONS.UPDATE ? 'info' : 'debug');
|
|
221
|
+
}
|
|
201
222
|
return buildSuccessResult({
|
|
202
223
|
actions: optimizedActions,
|
|
203
224
|
stateUpdates,
|
|
@@ -225,6 +246,7 @@ class OrderManager {
|
|
|
225
246
|
_endOfBootstrapValidationDone;
|
|
226
247
|
_broadcastingFlag;
|
|
227
248
|
_broadcastingStartedAt;
|
|
249
|
+
_shuttingDown;
|
|
228
250
|
_illegalStateSignal;
|
|
229
251
|
_accountingFailureSignal;
|
|
230
252
|
_recoveryStateValue;
|
|
@@ -333,6 +355,13 @@ class OrderManager {
|
|
|
333
355
|
_orphanFillsCreditedAt;
|
|
334
356
|
_pendingRecovery;
|
|
335
357
|
_recentFillKeysSnapshot;
|
|
358
|
+
_lastFilledBuyPrice;
|
|
359
|
+
_lastFilledSellPrice;
|
|
360
|
+
_lastFilledPrice;
|
|
361
|
+
_lastFilledType;
|
|
362
|
+
// anchor fields removed
|
|
363
|
+
// Note: dedupe lives inside the anchor object (`_seenKeys`), not here.
|
|
364
|
+
// Kept for backwards compat if external code checks existence; not used.
|
|
336
365
|
_metrics;
|
|
337
366
|
_currentWorkingGridStack;
|
|
338
367
|
_cowEngine;
|
|
@@ -363,6 +392,7 @@ class OrderManager {
|
|
|
363
392
|
this._endOfBootstrapValidationDone = false;
|
|
364
393
|
this._broadcastingFlag = 0;
|
|
365
394
|
this._broadcastingStartedAt = 0;
|
|
395
|
+
this._shuttingDown = false;
|
|
366
396
|
this._illegalStateSignal = null;
|
|
367
397
|
this._accountingFailureSignal = null;
|
|
368
398
|
this._recoveryStateValue = {
|
|
@@ -437,6 +467,10 @@ class OrderManager {
|
|
|
437
467
|
this._pendingRecovery = null;
|
|
438
468
|
this._recentFillKeysSnapshot = null;
|
|
439
469
|
this._lastStaleTotalsWarnAt = {};
|
|
470
|
+
this._lastFilledBuyPrice = null;
|
|
471
|
+
this._lastFilledSellPrice = null;
|
|
472
|
+
this._lastFilledPrice = null;
|
|
473
|
+
this._lastFilledType = null;
|
|
440
474
|
this._metrics = {
|
|
441
475
|
fundRecalcCount: 0,
|
|
442
476
|
lockAcquisitions: 0,
|
|
@@ -545,6 +579,23 @@ class OrderManager {
|
|
|
545
579
|
isBroadcastingActive() {
|
|
546
580
|
return this._broadcastingFlag > 0;
|
|
547
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Whether the owning bot has begun shutting down. Mirrors the
|
|
584
|
+
* bot-level `_shuttingDown` flag (set via setShuttingDown() from
|
|
585
|
+
* DEXBot._shutdownImpl) so manager-level wait loops can bail out
|
|
586
|
+
* instead of proceeding with broadcasts after shutdown completed.
|
|
587
|
+
* @returns {boolean}
|
|
588
|
+
*/
|
|
589
|
+
isShuttingDown() {
|
|
590
|
+
return this._shuttingDown === true;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* @param {boolean} value
|
|
594
|
+
* @returns {void}
|
|
595
|
+
*/
|
|
596
|
+
setShuttingDown(value) {
|
|
597
|
+
this._shuttingDown = value === true;
|
|
598
|
+
}
|
|
548
599
|
/**
|
|
549
600
|
* Auto-clears a stale broadcast flag after 120s so a hung
|
|
550
601
|
* broadcast cannot permanently block rebalancing.
|
|
@@ -611,9 +662,14 @@ class OrderManager {
|
|
|
611
662
|
* @returns {void}
|
|
612
663
|
*/
|
|
613
664
|
startBroadcasting() {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
665
|
+
// Refresh the stale-watchdog timestamp on EVERY increment, not just
|
|
666
|
+
// the 0→1 transition: with refcounted holders (e.g. structural
|
|
667
|
+
// Phase-2 reconcile + an inner COW batch) measuring only from the
|
|
668
|
+
// first holder's start lets the 120s watchdog hard-reset while a
|
|
669
|
+
// later holder is still legitimately active. Each new holder
|
|
670
|
+
// restarts the window; the commit-refusal backstop bounds any
|
|
671
|
+
// pathological stream of short holders.
|
|
672
|
+
this._broadcastingStartedAt = Date.now();
|
|
617
673
|
this._broadcastingFlag++;
|
|
618
674
|
this.logger?.log?.('[BROADCAST] Flag incremented — fill processing will be deferred until stopBroadcasting()', 'debug');
|
|
619
675
|
}
|
|
@@ -626,6 +682,11 @@ class OrderManager {
|
|
|
626
682
|
}
|
|
627
683
|
if (this._broadcastingFlag === 0) {
|
|
628
684
|
this._broadcastingStartedAt = 0;
|
|
685
|
+
// Runtime hook (wired by the bot): the fill consumer defers while
|
|
686
|
+
// this flag is held and its defer branch returns without
|
|
687
|
+
// rescheduling, so fills enqueued during a long region would
|
|
688
|
+
// starve indefinitely. Fire once when the last region ends.
|
|
689
|
+
this._onBroadcastRegionEnd?.();
|
|
629
690
|
}
|
|
630
691
|
}
|
|
631
692
|
/**
|
|
@@ -1372,71 +1433,187 @@ class OrderManager {
|
|
|
1372
1433
|
`Boundary writes should only happen inside _commitWorkingGrid.`, 'warn');
|
|
1373
1434
|
}
|
|
1374
1435
|
this.boundaryIdx = newIdx;
|
|
1436
|
+
this._logBoundaryDebug('set', newIdx);
|
|
1375
1437
|
}
|
|
1376
1438
|
/**
|
|
1377
1439
|
* Restore boundaryIdx outside the COW commit path (startup, recovery,
|
|
1378
1440
|
* disk-load). This is the same write as _setBoundary but without the
|
|
1379
1441
|
* lock-hold warning — loadGrid / initializeGrid are single-threaded at
|
|
1380
1442
|
* startup and the boundary is being restored from a known-good snapshot,
|
|
1381
|
-
* not committed alongside order mutations.
|
|
1443
|
+
* not committed alongside order mutations. Accepts null to clear the
|
|
1444
|
+
* boundary after a rejected persisted snapshot (P4) so re-derivation
|
|
1445
|
+
* (P3) starts from a clean state.
|
|
1382
1446
|
*/
|
|
1383
1447
|
_restoreBoundary(newIdx) {
|
|
1384
1448
|
this.boundaryIdx = newIdx;
|
|
1449
|
+
this._logBoundaryDebug('restore', newIdx);
|
|
1385
1450
|
}
|
|
1386
1451
|
/**
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
* (structuralResyncRequested) so the maintenance loop reconciles against
|
|
1394
|
-
* the chain. Purpose is any-writer detection — if a path other than the
|
|
1395
|
-
* promotion clamp commits a boundary that invades the opposite rail or
|
|
1396
|
-
* strands placed orders inside the spread, this fires.
|
|
1397
|
-
*/
|
|
1398
|
-
_assertGapBandIntactPostCommit() {
|
|
1452
|
+
* Observability (fix #4, docs/CONSOLIDATED_ORPHAN_FIX_SUMMARY.md §2): emit the live
|
|
1453
|
+
* boundary whenever roles are (re)assigned, so a silent boundary crawl that
|
|
1454
|
+
* re-rolls slots into the wrong rail is diagnosable in real time. Debug-level
|
|
1455
|
+
* only; no behavioral effect.
|
|
1456
|
+
*/
|
|
1457
|
+
_logBoundaryDebug(source, newIdx) {
|
|
1399
1458
|
try {
|
|
1400
|
-
const
|
|
1401
|
-
|
|
1459
|
+
const self = this;
|
|
1460
|
+
const boundary = newIdx == null ? self.boundaryIdx : newIdx;
|
|
1461
|
+
if (boundary == null)
|
|
1402
1462
|
return;
|
|
1403
|
-
const
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1463
|
+
const gapSlots = typeof self._gapSlots === 'number' ? self._gapSlots : 0;
|
|
1464
|
+
const sellStart = boundary + 1 + gapSlots;
|
|
1465
|
+
const countActive = (type) => {
|
|
1466
|
+
let n = 0;
|
|
1467
|
+
for (const o of (self.orders?.values?.() || [])) {
|
|
1468
|
+
if (o && o.type === type && o.orderId)
|
|
1469
|
+
n++;
|
|
1470
|
+
}
|
|
1471
|
+
return n;
|
|
1472
|
+
};
|
|
1473
|
+
this.logger?.log?.(`[BOUNDARY] ${source} boundary=${boundary} sellStart=${sellStart} gapSlots=${gapSlots} ` +
|
|
1474
|
+
`spreadCount=${self.currentSpreadCount ?? self.initialSpreadCount ?? '?'} ` +
|
|
1475
|
+
`activeBuy=${countActive(ORDER_TYPES.BUY)} activeSell=${countActive(ORDER_TYPES.SELL)}`, 'debug');
|
|
1476
|
+
}
|
|
1477
|
+
catch {
|
|
1478
|
+
/* observability only — never throw from logging */
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Record last filled price for side-gated guard: only the side of the most recent fill is gated
|
|
1483
|
+
* (BUY after BUY must be lower, SELL after SELL must be higher). Spread-correction bypasses.
|
|
1484
|
+
* Cold start (null) => disabled. Per-type fields kept for observability/back-compat.
|
|
1485
|
+
* @param {Array} fills
|
|
1486
|
+
*/
|
|
1487
|
+
recordLastFilledPrices(fills) {
|
|
1488
|
+
if (!Array.isArray(fills) || fills.length === 0)
|
|
1489
|
+
return;
|
|
1490
|
+
let recorded = 0;
|
|
1491
|
+
let lastKind = 'unknown';
|
|
1492
|
+
let lastPriceSrc = 'direct';
|
|
1493
|
+
for (const f of fills) {
|
|
1494
|
+
if (!f || (f.type !== ORDER_TYPES.BUY && f.type !== ORDER_TYPES.SELL))
|
|
1495
|
+
continue;
|
|
1496
|
+
let price = Number(f.price ?? f.order?.price);
|
|
1497
|
+
let priceSrc = 'direct';
|
|
1498
|
+
if (!Number.isFinite(price) || price <= 0) {
|
|
1499
|
+
try {
|
|
1500
|
+
const slot = f.id ? this.orders?.get?.(f.id) : null;
|
|
1501
|
+
price = slot ? Number(slot.price) : null;
|
|
1502
|
+
if (Number.isFinite(price) && price > 0)
|
|
1503
|
+
priceSrc = 'slot-fallback';
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
price = null;
|
|
1507
|
+
}
|
|
1411
1508
|
}
|
|
1412
|
-
if (
|
|
1413
|
-
|
|
1509
|
+
if (!Number.isFinite(price) || price <= 0)
|
|
1510
|
+
continue;
|
|
1511
|
+
this._lastFilledPrice = price;
|
|
1512
|
+
this._lastFilledType = f.type;
|
|
1513
|
+
if (f.type === ORDER_TYPES.BUY)
|
|
1514
|
+
this._lastFilledBuyPrice = price;
|
|
1515
|
+
else if (f.type === ORDER_TYPES.SELL)
|
|
1516
|
+
this._lastFilledSellPrice = price;
|
|
1517
|
+
recorded++;
|
|
1518
|
+
lastKind = f?.isPartial === true ? 'partial' : (f?.isPartial === false ? 'full' : 'unknown');
|
|
1519
|
+
lastPriceSrc = priceSrc;
|
|
1520
|
+
}
|
|
1521
|
+
// One line per fill batch (chunk): the pivot mutations above are otherwise
|
|
1522
|
+
// silent. src records the kind of the pivot-setting fill (full vs
|
|
1523
|
+
// partial — partial pivots behave differently downstream) and whether
|
|
1524
|
+
// its price came from the fill itself or the invisible slot fallback.
|
|
1525
|
+
if (recorded > 0) {
|
|
1526
|
+
try {
|
|
1527
|
+
const fmt = (v) => (v == null || !Number.isFinite(Number(v)) ? 'none' : Format.formatPrice6(Number(v)));
|
|
1528
|
+
this.logger?.log?.(`[FILL-PIVOT] pivot=${fmt(this._lastFilledPrice)}(${this._lastFilledType}) ` +
|
|
1529
|
+
`buy=${fmt(this._lastFilledBuyPrice)} sell=${fmt(this._lastFilledSellPrice)} ` +
|
|
1530
|
+
`n=${recorded} src=${lastKind}/${lastPriceSrc}`, 'info');
|
|
1531
|
+
}
|
|
1532
|
+
catch { /* logging is best-effort */ }
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Seed last-filled guard from the live book at startup. Book-derived
|
|
1537
|
+
* (survives restart), analogous to the deleted MarketAnchor book-seed.
|
|
1538
|
+
* Closes the in-memory-only window where LAST-FILL-GUARD would be
|
|
1539
|
+
* disabled until the first fill arrives.
|
|
1540
|
+
* @param {Array} chainOpenOrders - raw chain open orders (from readOpenOrdersGuarded)
|
|
1541
|
+
*/
|
|
1542
|
+
seedLastFilledPricesFromBook(chainOpenOrders) {
|
|
1543
|
+
// Already armed — silent (the guard has a live pivot).
|
|
1544
|
+
if (this._lastFilledPrice != null)
|
|
1545
|
+
return;
|
|
1546
|
+
if (this._lastFilledType != null)
|
|
1547
|
+
return;
|
|
1548
|
+
// Only seed when cold (no fills yet) — don't overwrite a live value.
|
|
1549
|
+
if (this._lastFilledBuyPrice != null && this._lastFilledSellPrice != null)
|
|
1550
|
+
return;
|
|
1551
|
+
if (!Array.isArray(chainOpenOrders) || chainOpenOrders.length === 0) {
|
|
1552
|
+
try {
|
|
1553
|
+
this.logger?.log?.(`[LAST-FILL-GUARD] Book seed skipped: no chain orders; guard remains DISABLED (cold) until the first fill`, 'warn');
|
|
1414
1554
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1555
|
+
catch { }
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
try {
|
|
1559
|
+
let maxBuy = null;
|
|
1560
|
+
let minSell = null;
|
|
1561
|
+
for (const o of chainOpenOrders) {
|
|
1562
|
+
const parsed = parseChainOrder(o, this.assets);
|
|
1563
|
+
if (!parsed)
|
|
1564
|
+
continue;
|
|
1565
|
+
const price = Number(parsed.price);
|
|
1566
|
+
if (!Number.isFinite(price) || price <= 0)
|
|
1418
1567
|
continue;
|
|
1419
|
-
if (
|
|
1420
|
-
|
|
1421
|
-
|
|
1568
|
+
if (parsed.type === ORDER_TYPES.BUY) {
|
|
1569
|
+
if (maxBuy == null || price > maxBuy)
|
|
1570
|
+
maxBuy = price;
|
|
1571
|
+
}
|
|
1572
|
+
else if (parsed.type === ORDER_TYPES.SELL) {
|
|
1573
|
+
if (minSell == null || price < minSell)
|
|
1574
|
+
minSell = price;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
if (maxBuy != null && this._lastFilledBuyPrice == null)
|
|
1578
|
+
this._lastFilledBuyPrice = maxBuy;
|
|
1579
|
+
if (minSell != null && this._lastFilledSellPrice == null)
|
|
1580
|
+
this._lastFilledSellPrice = minSell;
|
|
1581
|
+
if (this._lastFilledPrice == null) {
|
|
1582
|
+
if (maxBuy != null && minSell != null)
|
|
1583
|
+
this._lastFilledPrice = (maxBuy + minSell) / 2;
|
|
1584
|
+
else if (maxBuy != null)
|
|
1585
|
+
this._lastFilledPrice = maxBuy;
|
|
1586
|
+
else if (minSell != null)
|
|
1587
|
+
this._lastFilledPrice = minSell;
|
|
1588
|
+
}
|
|
1589
|
+
if (this._lastFilledType == null) {
|
|
1590
|
+
if (maxBuy != null && minSell != null) {
|
|
1591
|
+
// Seed is ambiguous (no real fill yet) — leave type null so guard stays disabled until first fill
|
|
1592
|
+
this._lastFilledType = null;
|
|
1422
1593
|
}
|
|
1594
|
+
else if (maxBuy != null)
|
|
1595
|
+
this._lastFilledType = ORDER_TYPES.BUY;
|
|
1596
|
+
else if (minSell != null)
|
|
1597
|
+
this._lastFilledType = ORDER_TYPES.SELL;
|
|
1423
1598
|
}
|
|
1424
|
-
if (
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
if (this._recoveryState) {
|
|
1428
|
-
this._recoveryState = {
|
|
1429
|
-
...this._recoveryState,
|
|
1430
|
-
lastFailureAt: Date.now(),
|
|
1431
|
-
structuralResyncRequested: true
|
|
1432
|
-
};
|
|
1599
|
+
if (maxBuy != null || minSell != null) {
|
|
1600
|
+
try {
|
|
1601
|
+
this.logger?.log?.(`[LAST-FILL-GUARD] Seeded from book: lastBuy=${maxBuy} lastSell=${minSell} lastPrice=${this._lastFilledPrice} lastType=${this._lastFilledType}`, 'info');
|
|
1433
1602
|
}
|
|
1603
|
+
catch { }
|
|
1604
|
+
}
|
|
1605
|
+
// A startup that ends with the guard disabled must say so: the
|
|
1606
|
+
// ambiguous seed (both sides present, no real fill yet) leaves
|
|
1607
|
+
// _lastFilledType null by design, and that hole is otherwise only
|
|
1608
|
+
// inferable by the absence of any log line.
|
|
1609
|
+
if (this._lastFilledPrice == null || this._lastFilledType == null) {
|
|
1610
|
+
try {
|
|
1611
|
+
this.logger?.log?.(`[LAST-FILL-GUARD] Book seed left guard DISABLED (cold): lastBuy=${maxBuy} lastSell=${minSell} lastPrice=${this._lastFilledPrice} lastType=${this._lastFilledType}; guard arms on the first fill`, 'warn');
|
|
1612
|
+
}
|
|
1613
|
+
catch { }
|
|
1434
1614
|
}
|
|
1435
1615
|
}
|
|
1436
|
-
catch
|
|
1437
|
-
// Detector must never break the commit path.
|
|
1438
|
-
this.logger.log(`[COW] Gap-band post-commit check failed: ${getErrorMessage(err)}`, 'warn');
|
|
1439
|
-
}
|
|
1616
|
+
catch { }
|
|
1440
1617
|
}
|
|
1441
1618
|
/**
|
|
1442
1619
|
* @returns {Object|null} The consumed signal or null
|
|
@@ -1555,13 +1732,66 @@ class OrderManager {
|
|
|
1555
1732
|
dustThresholdPercent: this.config?.gridLimits?.PARTIAL_DUST_THRESHOLD_PERCENTAGE
|
|
1556
1733
|
});
|
|
1557
1734
|
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Wait (bounded) until any non-COW broadcast/placement activity — the
|
|
1737
|
+
* refcounted _broadcastingFlag — has released. Startup/structural
|
|
1738
|
+
* reconcile Phase-2 placement now holds this flag across its create/update
|
|
1739
|
+
* groups; launching a fill-driven COW rebalance on top of it mutates the
|
|
1740
|
+
* master mid-broadcast, forcing a commit refusal and leaving a
|
|
1741
|
+
* previous-generation order set on chain as duplicate-price orphans
|
|
1742
|
+
* that block placements until drained.
|
|
1743
|
+
*
|
|
1744
|
+
* The wait is capped: after the timeout the rebalance proceeds anyway —
|
|
1745
|
+
* the existing commit-refusal + chain-adoption machinery remains the
|
|
1746
|
+
* backstop, so the worst case equals the pre-guard behavior.
|
|
1747
|
+
*
|
|
1748
|
+
* @param {number} [timeoutMs]
|
|
1749
|
+
* @returns {Promise<{waitedMs: number, timedOut: boolean}>}
|
|
1750
|
+
*/
|
|
1751
|
+
async _awaitBroadcastIdle(timeoutMs = 30000) {
|
|
1752
|
+
if (this._shuttingDown) {
|
|
1753
|
+
return { waitedMs: 0, timedOut: false, shutdown: true };
|
|
1754
|
+
}
|
|
1755
|
+
if (!this.isBroadcastingActive())
|
|
1756
|
+
return { waitedMs: 0, timedOut: false, shutdown: false };
|
|
1757
|
+
const startedAt = Date.now();
|
|
1758
|
+
let logged = false;
|
|
1759
|
+
while (this.isBroadcastingActive()) {
|
|
1760
|
+
if (this._shuttingDown) {
|
|
1761
|
+
this.logger?.log?.(`[SAFE-REBALANCE] Broadcast wait aborted: shutdown in progress after ${Date.now() - startedAt}ms`, 'warn');
|
|
1762
|
+
return { waitedMs: Date.now() - startedAt, timedOut: false, shutdown: true };
|
|
1763
|
+
}
|
|
1764
|
+
const waited = Date.now() - startedAt;
|
|
1765
|
+
if (waited >= timeoutMs) {
|
|
1766
|
+
this.logger?.log?.(`[SAFE-REBALANCE] Broadcast still active after ${waited}ms; proceeding (commit-refusal + chain adoption remains the backstop)`, 'warn');
|
|
1767
|
+
return { waitedMs: waited, timedOut: true, shutdown: false };
|
|
1768
|
+
}
|
|
1769
|
+
if (!logged) {
|
|
1770
|
+
this.logger?.log?.('[SAFE-REBALANCE] Deferring: broadcast/placement activity active (startup/structural reconcile or another batch)', 'info');
|
|
1771
|
+
logged = true;
|
|
1772
|
+
}
|
|
1773
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1774
|
+
}
|
|
1775
|
+
return { waitedMs: Date.now() - startedAt, timedOut: false, shutdown: false };
|
|
1776
|
+
}
|
|
1558
1777
|
/**
|
|
1559
1778
|
* @param {Array} [fills] - Fill events triggering rebalance
|
|
1560
1779
|
* @param {Set<string>} [excludeIds] - Order IDs to exclude
|
|
1780
|
+
* @param {Object} [options]
|
|
1781
|
+
* @param {boolean} [options.skipBroadcastWait] - Set by the COW re-plan path,
|
|
1782
|
+
* which runs INSIDE its own startBroadcasting() region and must not wait
|
|
1783
|
+
* on the flag it holds itself.
|
|
1561
1784
|
* @returns {Promise<any>}
|
|
1562
1785
|
*/
|
|
1563
|
-
async performSafeRebalance(fills = [], excludeIds = new Set()) {
|
|
1786
|
+
async performSafeRebalance(fills = [], excludeIds = new Set(), options = {}) {
|
|
1564
1787
|
this.logger.log("[SAFE-REBALANCE] Starting with COW...", "info");
|
|
1788
|
+
if (!options?.skipBroadcastWait) {
|
|
1789
|
+
const idle = await this._awaitBroadcastIdle();
|
|
1790
|
+
if (idle.shutdown || this._shuttingDown) {
|
|
1791
|
+
this.logger.log('[SAFE-REBALANCE] Aborting rebalance: shutdown in progress', 'warn');
|
|
1792
|
+
return buildAbortedResult('Shutdown in progress — safe rebalance aborted');
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1565
1795
|
return await this._gridLock.acquire(async () => {
|
|
1566
1796
|
return await this._applySafeRebalanceCOW(fills, excludeIds);
|
|
1567
1797
|
});
|
|
@@ -1719,7 +1949,6 @@ class OrderManager {
|
|
|
1719
1949
|
releaseStackEntry();
|
|
1720
1950
|
return false;
|
|
1721
1951
|
}
|
|
1722
|
-
this._assertGapBandIntactPostCommit();
|
|
1723
1952
|
try {
|
|
1724
1953
|
if (!skipRecalc) {
|
|
1725
1954
|
// If the last accounting failure was due to stale accountTotals,
|