dexbot 1.5.3 → 1.6.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 +62 -0
- package/README.md +4 -3
- package/analysis/README.md +1 -2
- package/analysis/ama_fitting/fetch_lp_candles.ts +1 -9
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/analyze_derivatives.ts +1 -1
- package/analysis/bot_usage/discover_bot_accounts.ts +12 -6
- package/analysis/resolve_source.ts +9 -2
- package/analysis/tradingview/README.md +29 -11
- package/analysis/tradingview/analyze_tradingview.ts +92 -3
- package/analysis/tradingview/tradingview_uplot_chart_generator.ts +1120 -182
- package/analysis/trend_detection/package.json +1 -1
- package/claw/docs/DEXBOT2_TUNING_CHEAT_SHEET.md +1 -1
- package/claw/modules/dexbot_profiles.ts +22 -2
- 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/skills/launcher-ops/references/launcher-workflow.md +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/claw/tests/test_dexbot_profiles.ts +65 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.js +1 -9
- package/dist/analysis/ama_fitting/fetch_lp_candles.js.map +1 -1
- package/dist/analysis/analyze_derivatives.js +1 -1
- package/dist/analysis/bot_usage/discover_bot_accounts.js +11 -6
- package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +2 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
- package/dist/analysis/resolve_source.d.ts +1 -0
- package/dist/analysis/resolve_source.d.ts.map +1 -1
- package/dist/analysis/resolve_source.js +6 -2
- package/dist/analysis/resolve_source.js.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.d.ts +5 -0
- package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.js +112 -3
- package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts +5 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +1118 -182
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -1
- package/dist/bot.js +1 -1
- package/dist/bot.js.map +1 -1
- package/dist/dexbot.js +3 -3
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/ama_signal_runner.js +2 -2
- package/dist/market_adapter/candle_utils.d.ts +6 -0
- package/dist/market_adapter/candle_utils.d.ts.map +1 -1
- package/dist/market_adapter/candle_utils.js +6 -0
- package/dist/market_adapter/candle_utils.js.map +1 -1
- package/dist/market_adapter/core/kibana_candles.d.ts +17 -1
- package/dist/market_adapter/core/kibana_candles.d.ts.map +1 -1
- package/dist/market_adapter/core/kibana_candles.js +101 -19
- package/dist/market_adapter/core/kibana_candles.js.map +1 -1
- package/dist/market_adapter/core/kibana_client.d.ts +2 -0
- package/dist/market_adapter/core/kibana_client.d.ts.map +1 -1
- package/dist/market_adapter/core/kibana_client.js +31 -4
- package/dist/market_adapter/core/kibana_client.js.map +1 -1
- package/dist/market_adapter/core/market_adapter_service.d.ts.map +1 -1
- package/dist/market_adapter/core/market_adapter_service.js +0 -6
- package/dist/market_adapter/core/market_adapter_service.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_book_data.d.ts +14 -0
- package/dist/market_adapter/inputs/fetch_book_data.d.ts.map +1 -0
- package/dist/market_adapter/inputs/fetch_book_data.js +138 -0
- package/dist/market_adapter/inputs/fetch_book_data.js.map +1 -0
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +16 -2
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +109 -281
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts +87 -0
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts.map +1 -0
- package/dist/market_adapter/inputs/kibana_feed_source.js +570 -0
- package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -0
- package/dist/market_adapter/inputs/kibana_source.d.ts.map +1 -1
- package/dist/market_adapter/inputs/kibana_source.js +10 -5
- package/dist/market_adapter/inputs/kibana_source.js.map +1 -1
- package/dist/market_adapter/inputs/window_cache.d.ts +166 -0
- package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -0
- package/dist/market_adapter/inputs/window_cache.js +500 -0
- package/dist/market_adapter/inputs/window_cache.js.map +1 -0
- package/dist/market_adapter/interval_utils.d.ts +8 -1
- package/dist/market_adapter/interval_utils.d.ts.map +1 -1
- package/dist/market_adapter/interval_utils.js +14 -1
- package/dist/market_adapter/interval_utils.js.map +1 -1
- package/dist/modules/account_bots.d.ts +1 -0
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +21 -5
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/account_orders.d.ts +25 -4
- package/dist/modules/account_orders.d.ts.map +1 -1
- package/dist/modules/account_orders.js +67 -4
- package/dist/modules/account_orders.js.map +1 -1
- package/dist/modules/bitshares-native/chain_client.d.ts +2 -0
- package/dist/modules/bitshares-native/chain_client.d.ts.map +1 -1
- package/dist/modules/bitshares-native/chain_client.js +3 -1
- package/dist/modules/bitshares-native/chain_client.js.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.js +13 -3
- package/dist/modules/bitshares-native/subscriptions.js.map +1 -1
- package/dist/modules/bitshares-native/transport.d.ts +2 -0
- package/dist/modules/bitshares-native/transport.d.ts.map +1 -1
- package/dist/modules/bitshares-native/transport.js +122 -28
- package/dist/modules/bitshares-native/transport.js.map +1 -1
- package/dist/modules/bitshares_client.d.ts.map +1 -1
- package/dist/modules/bitshares_client.js +59 -8
- 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 +11 -0
- package/dist/modules/bot_settings.js.map +1 -1
- package/dist/modules/constants.d.ts +13 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +68 -4
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +8 -0
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +57 -2
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +26 -1
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +157 -28
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +41 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +135 -5
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +79 -12
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +554 -46
- 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 +13 -0
- package/dist/modules/dexbot_startup_runtime.js.map +1 -1
- package/dist/modules/dexbot_state_recovery.d.ts +15 -1
- package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
- package/dist/modules/dexbot_state_recovery.js +36 -7
- 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/node_connect_policy.d.ts +52 -0
- package/dist/modules/node_connect_policy.d.ts.map +1 -0
- package/dist/modules/node_connect_policy.js +35 -0
- package/dist/modules/node_connect_policy.js.map +1 -0
- package/dist/modules/node_manager.d.ts +32 -0
- package/dist/modules/node_manager.d.ts.map +1 -1
- package/dist/modules/node_manager.js +76 -9
- package/dist/modules/node_manager.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +30 -0
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +176 -3
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/export.d.ts.map +1 -1
- package/dist/modules/order/export.js +1 -0
- package/dist/modules/order/export.js.map +1 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +143 -15
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +5 -3
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +182 -6
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +64 -2
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +231 -14
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/strategy.d.ts +3 -0
- package/dist/modules/order/strategy.d.ts.map +1 -1
- package/dist/modules/order/strategy.js +79 -7
- 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 +16 -1
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +11 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +24 -1
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +258 -13
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +662 -12
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +36 -0
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +102 -6
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +6 -2
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/paths.d.ts +2 -0
- package/dist/modules/paths.d.ts.map +1 -1
- package/dist/modules/paths.js +1 -0
- package/dist/modules/paths.js.map +1 -1
- package/dist/modules/runtime_settings.d.ts +13 -0
- package/dist/modules/runtime_settings.d.ts.map +1 -1
- package/dist/modules/runtime_settings.js +20 -0
- package/dist/modules/runtime_settings.js.map +1 -1
- package/dist/modules/utils/errors.d.ts +16 -0
- package/dist/modules/utils/errors.d.ts.map +1 -1
- package/dist/modules/utils/errors.js +31 -0
- package/dist/modules/utils/errors.js.map +1 -1
- package/dist/pm2.js +7 -7
- package/dist/scripts/analyze-credit.d.ts.map +1 -1
- package/dist/scripts/analyze-credit.js +13 -3
- package/dist/scripts/analyze-credit.js.map +1 -1
- package/dist/scripts/analyze-orders.js +2 -2
- package/dist/scripts/analyze-orders.js.map +1 -1
- package/dist/scripts/diagnose-kibana-candles.d.ts.map +1 -1
- package/dist/scripts/diagnose-kibana-candles.js +0 -1
- package/dist/scripts/diagnose-kibana-candles.js.map +1 -1
- package/dist/scripts/test-credit-renewal.js +1 -1
- package/dist/scripts/test-credit-renewal.js.map +1 -1
- package/dist/scripts/tv.d.ts +24 -2
- package/dist/scripts/tv.d.ts.map +1 -1
- package/dist/scripts/tv.js +190 -65
- package/dist/scripts/tv.js.map +1 -1
- package/docs/BITSHARES_ONBOARDING.md +9 -2
- package/docs/COW_INVARIANTS.md +18 -1
- package/docs/DEXBOT_COMPARISON.md +3 -3
- package/docs/EVOLUTION.md +8 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +21 -9
- package/docs/GRID_RECALCULATION.md +5 -6
- package/docs/GRID_RECONCILE.md +12 -2
- package/docs/MPA_CREDIT_USAGE.md +2 -2
- package/docs/README.md +5 -6
- package/docs/architecture.md +0 -58
- package/docs/developer_guide.md +2 -1
- package/docs/docker.md +1 -1
- package/market_adapter/README.md +4 -0
- package/modules/README.md +1 -1
- package/package.json +1 -1
- package/scripts/README.md +5 -3
- package/tests/README.md +2 -2
|
@@ -1048,7 +1048,7 @@ async function autoCancelOneUnmatchedOrphan(bot) {
|
|
|
1048
1048
|
}
|
|
1049
1049
|
const target = unmatched.find((u) => u && u.reason === 'price-drift-orphan');
|
|
1050
1050
|
if (!target) {
|
|
1051
|
-
return { cancelled: false, reason: 'no-price-drift-orphan', message: 'no price-drift orphan to cancel;
|
|
1051
|
+
return { cancelled: false, reason: 'no-price-drift-orphan', message: 'no price-drift orphan to cancel; remaining unmatched orders need no cancellation (adoptable or deferred holds)' };
|
|
1052
1052
|
}
|
|
1053
1053
|
const orderId = target.id || target.orderId || target.chainOrderId;
|
|
1054
1054
|
if (!orderId) {
|
|
@@ -2200,6 +2200,57 @@ function resolveRefillBoundaryHold(workingBoundary, committedBoundary, skippedUp
|
|
|
2200
2200
|
heldRefillSlotIds
|
|
2201
2201
|
};
|
|
2202
2202
|
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Track consecutive boundary-hold batches on the manager (ops visibility).
|
|
2205
|
+
*
|
|
2206
|
+
* A single hold is normal maker discipline: the guard vetoed stale-priced
|
|
2207
|
+
* refills, so the committed boundary stays instead of advancing past
|
|
2208
|
+
* stranded rail holes. A growing run means the grid is trailing the market
|
|
2209
|
+
* (plans keep pricing refills against a racing guard pivot, typically while
|
|
2210
|
+
* fill batches run on backlogged state) and only fresh fills unstick it —
|
|
2211
|
+
* worth escalating so it cannot hide inside per-batch warns.
|
|
2212
|
+
*
|
|
2213
|
+
* Also records a hold signature (`_lastHeldPlanSignature`) for the
|
|
2214
|
+
* identical-held-plan suppression in `performSafeRebalance`, and the caller
|
|
2215
|
+
* escalates a long run to a guard-aware structural re-center. A fill-less
|
|
2216
|
+
* re-plan from unchanged master re-derives the identical plan and re-hits
|
|
2217
|
+
* the identical guard blocks, so it is suppressed rather than re-broadcast;
|
|
2218
|
+
* the heal path is a fresh fill-driven plan or the re-center.
|
|
2219
|
+
* @param {any} manager - Order manager (mutable tracking fields)
|
|
2220
|
+
* @param {boolean} held - Whether this batch held the boundary
|
|
2221
|
+
* @param {any} keptBoundary - Committed boundary that was kept
|
|
2222
|
+
* @param {any} plannedBoundary - Boundary the plan wanted
|
|
2223
|
+
* @param {string[]} heldSlotIds - Refill slots skipped this batch
|
|
2224
|
+
* @returns {number} Consecutive-hold count after this batch (0 when clear)
|
|
2225
|
+
*/
|
|
2226
|
+
function trackBoundaryHold(manager, held, keptBoundary, plannedBoundary, heldSlotIds) {
|
|
2227
|
+
if (!manager)
|
|
2228
|
+
return 0;
|
|
2229
|
+
const prev = Number(manager._consecutiveBoundaryHolds) || 0;
|
|
2230
|
+
const consecutive = held ? prev + 1 : 0;
|
|
2231
|
+
manager._consecutiveBoundaryHolds = consecutive;
|
|
2232
|
+
if (held) {
|
|
2233
|
+
manager._lastBoundaryHoldInfo = {
|
|
2234
|
+
at: Date.now(),
|
|
2235
|
+
kept: keptBoundary,
|
|
2236
|
+
planned: plannedBoundary,
|
|
2237
|
+
slots: Array.isArray(heldSlotIds) ? [...heldSlotIds] : [],
|
|
2238
|
+
};
|
|
2239
|
+
// Signature for the identical-held-plan suppression in
|
|
2240
|
+
// performSafeRebalance: a fill-less replan with the same boundary,
|
|
2241
|
+
// pivot and fill timestamp can only reproduce this hold.
|
|
2242
|
+
manager._lastHeldPlanSignature = {
|
|
2243
|
+
boundaryIdx: keptBoundary,
|
|
2244
|
+
pivot: manager._lastFilledPrice ?? null,
|
|
2245
|
+
fillsAt: manager._lastFilledAt ?? 0,
|
|
2246
|
+
wire: Array.isArray(heldSlotIds) ? [...heldSlotIds] : [],
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
else {
|
|
2250
|
+
manager._lastHeldPlanSignature = null;
|
|
2251
|
+
}
|
|
2252
|
+
return consecutive;
|
|
2253
|
+
}
|
|
2203
2254
|
/**
|
|
2204
2255
|
* Restore skipped update slots in the working grid to master state.
|
|
2205
2256
|
* @param {import('./dexbot_class.js').DEXBot} bot
|
|
@@ -2533,8 +2584,13 @@ async function runPreBroadcastGuards(bot, cowResult) {
|
|
|
2533
2584
|
const unmatchedChainOrders = Array.isArray(bot.manager?._lastUnmatchedChainOrders)
|
|
2534
2585
|
? bot.manager._lastUnmatchedChainOrders
|
|
2535
2586
|
: [];
|
|
2587
|
+
// Out-of-grid holds are permanent by design (live orders held outside
|
|
2588
|
+
// the frozen rail): they can never be adopted and collide with nothing,
|
|
2589
|
+
// so they must not block CREATES — otherwise one dip-protection hold
|
|
2590
|
+
// freezes the whole grid. Only adoptable/cancellable orphans block.
|
|
2591
|
+
const blockingUnmatched = unmatchedChainOrders.filter((u) => !orderUtils.isNonBlockingUnmatchedOrder(u));
|
|
2536
2592
|
const pendingBroadcasts = getPendingBroadcasts(bot);
|
|
2537
|
-
if (hasCreateActions && (
|
|
2593
|
+
if (hasCreateActions && (blockingUnmatched.length > 0 || pendingBroadcasts.length > 0)) {
|
|
2538
2594
|
if (pendingBroadcasts.length > 0) {
|
|
2539
2595
|
bot.manager.logger.log(`[COW] Rejecting CREATE batch: ${pendingBroadcasts.length} pending broadcast(s) from a prior uncertain ` +
|
|
2540
2596
|
`broadcast. Running recovery before placing replacement orders.`, 'error');
|
|
@@ -2562,11 +2618,11 @@ async function runPreBroadcastGuards(bot, cowResult) {
|
|
|
2562
2618
|
}
|
|
2563
2619
|
};
|
|
2564
2620
|
}
|
|
2565
|
-
const unmatchedSample =
|
|
2621
|
+
const unmatchedSample = blockingUnmatched
|
|
2566
2622
|
.slice(0, 3)
|
|
2567
2623
|
.map((o) => formatUnmatchedChainOrderForLog(o))
|
|
2568
2624
|
.join(' | ');
|
|
2569
|
-
bot.manager.logger.log(`[COW] ${
|
|
2625
|
+
bot.manager.logger.log(`[COW] ${blockingUnmatched.length} unmatched chain order(s) blocking CREATES ` +
|
|
2570
2626
|
(unmatchedSample ? `(${unmatchedSample})` : '') +
|
|
2571
2627
|
` — adopting via sync instead of cancelling`, 'info');
|
|
2572
2628
|
try {
|
|
@@ -2662,6 +2718,19 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2662
2718
|
// frozen for every downstream commit path (success + uncertain-catch).
|
|
2663
2719
|
// workingBoundary itself stays untouched (audit trail).
|
|
2664
2720
|
let effectiveBoundary = workingBoundary;
|
|
2721
|
+
// True when the refill hold pinned the committed boundary over the plan's
|
|
2722
|
+
// target. Hoisted out of the broadcast try block: the uncertain-broadcast
|
|
2723
|
+
// catch commits too, and a held boundary must keep the owed fill crawls
|
|
2724
|
+
// there as well (see _commitWorkingGrid pending-crawl bookkeeping).
|
|
2725
|
+
let boundaryHeld = false;
|
|
2726
|
+
// Consecutive-hold re-center tuning (see the escalation block below).
|
|
2727
|
+
const boundaryHoldTiming = constantsModule?.TIMING || {};
|
|
2728
|
+
const holdResyncThreshold = Number(boundaryHoldTiming.BOUNDARY_HOLD_RESYNC_THRESHOLD) > 0
|
|
2729
|
+
? Number(boundaryHoldTiming.BOUNDARY_HOLD_RESYNC_THRESHOLD)
|
|
2730
|
+
: 4;
|
|
2731
|
+
const holdResyncCooldownMs = Number(boundaryHoldTiming.BOUNDARY_HOLD_RESYNC_COOLDOWN_MS) > 0
|
|
2732
|
+
? Number(boundaryHoldTiming.BOUNDARY_HOLD_RESYNC_COOLDOWN_MS)
|
|
2733
|
+
: 5 * 60 * 1000;
|
|
2665
2734
|
if (bot.config.dryRun) {
|
|
2666
2735
|
const cancelCount = actions.filter((a) => a.type === COW_ACTIONS.CANCEL).length;
|
|
2667
2736
|
const createCount = actions.filter((a) => a.type === COW_ACTIONS.CREATE).length;
|
|
@@ -2775,6 +2844,16 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2775
2844
|
bot._markGridActivity('batch start');
|
|
2776
2845
|
bot.manager._setRebalanceState(REBALANCE_STATES.BROADCASTING);
|
|
2777
2846
|
bot.manager.startBroadcasting();
|
|
2847
|
+
// P3 — freeze the guard pivot once per batch: per-action refreshes
|
|
2848
|
+
// mutated the pivot mid-batch (02:03 pivots drifted 0.001523→0.001529
|
|
2849
|
+
// across 20 checks), so early actions were judged against a different
|
|
2850
|
+
// pivot than later ones. The batch summary still reports whether this
|
|
2851
|
+
// freeze moved the pivot under the plan.
|
|
2852
|
+
try {
|
|
2853
|
+
if (refreshLastFillPivotFromQueue(bot))
|
|
2854
|
+
lastFillGuardPivotRefreshed = true;
|
|
2855
|
+
}
|
|
2856
|
+
catch { /* best-effort */ }
|
|
2778
2857
|
for (const action of actions) {
|
|
2779
2858
|
if (action.type === COW_ACTIONS.CANCEL) {
|
|
2780
2859
|
try {
|
|
@@ -2861,13 +2940,13 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2861
2940
|
const isCorrectionCreate = actionOrigin === 'spread-correction'
|
|
2862
2941
|
|| (actionOrigin == null && batchOrigin === 'spread-correction');
|
|
2863
2942
|
if (!isCorrectionCreate) {
|
|
2864
|
-
const { check, refreshed } = runLastFillGuardCheck(bot, createPrice, order.size, order.type, lastFillGuardStats);
|
|
2943
|
+
const { check, refreshed } = runLastFillGuardCheck(bot, createPrice, order.size, order.type, lastFillGuardStats, true);
|
|
2865
2944
|
if (refreshed)
|
|
2866
2945
|
lastFillGuardPivotRefreshed = true;
|
|
2867
2946
|
if (check.blocked) {
|
|
2868
2947
|
lastFillGuardStats.skipped++;
|
|
2869
2948
|
const dir = order.type === ORDER_TYPES.BUY ? 'above' : 'below';
|
|
2870
|
-
bot.manager.logger.log(`[LAST-FILL-GUARD] Skipping ${order.type} CREATE for ${order.id} at ${Format.formatPrice6(createPrice)}: ${dir} last filled ${Format.formatPrice6(check.pivot)} (halfInc ${check.halfInc}% thr ${Format.formatPrice6(check.threshold)}); re-planned after market moves`, '
|
|
2949
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] Skipping ${order.type} CREATE for ${order.id} at ${Format.formatPrice6(createPrice)}: ${dir} last filled ${Format.formatPrice6(check.pivot)} (halfInc ${check.halfInc}% thr ${Format.formatPrice6(check.threshold)}); re-planned after market moves`, 'debug');
|
|
2871
2950
|
if (order.id)
|
|
2872
2951
|
skippedCreateSlotIds.add(order.id);
|
|
2873
2952
|
continue;
|
|
@@ -2938,6 +3017,15 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2938
3017
|
}
|
|
2939
3018
|
const rotationSizeValidation = validateOrderSizeForExecution(bot, newSize, orderType, action.order, newSize);
|
|
2940
3019
|
if (!rotationSizeValidation.isValid) {
|
|
3020
|
+
// Record like every sibling skip site: the skip set
|
|
3021
|
+
// feeds both the working-grid restore and the
|
|
3022
|
+
// boundary hold (a skipped refill must not let the
|
|
3023
|
+
// committed boundary advance past its empty slot).
|
|
3024
|
+
skippedUpdateCount++;
|
|
3025
|
+
if (action.id)
|
|
3026
|
+
skippedUpdateSlotIds.add(action.id);
|
|
3027
|
+
if (action.newGridId)
|
|
3028
|
+
skippedUpdateSlotIds.add(action.newGridId);
|
|
2941
3029
|
bot.manager.logger.log(`Skipping rotation update ${action.id} -> ${action.newGridId}: ${rotationSizeValidation.reason}`, 'warn');
|
|
2942
3030
|
continue;
|
|
2943
3031
|
}
|
|
@@ -3087,7 +3175,7 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
3087
3175
|
lastFillGuardStats.bypassed++;
|
|
3088
3176
|
}
|
|
3089
3177
|
else {
|
|
3090
|
-
const { check, refreshed } = runLastFillGuardCheck(bot, newPrice, newSize, orderType, lastFillGuardStats);
|
|
3178
|
+
const { check, refreshed } = runLastFillGuardCheck(bot, newPrice, newSize, orderType, lastFillGuardStats, true);
|
|
3091
3179
|
if (refreshed)
|
|
3092
3180
|
lastFillGuardPivotRefreshed = true;
|
|
3093
3181
|
if (check.blocked) {
|
|
@@ -3098,7 +3186,7 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
3098
3186
|
if (action.newGridId)
|
|
3099
3187
|
skippedUpdateSlotIds.add(action.newGridId);
|
|
3100
3188
|
const dir = orderType === ORDER_TYPES.BUY ? 'above' : 'below';
|
|
3101
|
-
bot.manager.logger.log(`[LAST-FILL-GUARD] Skipping ${orderType} UPDATE for ${action.id} -> ${action.newGridId} at ${Format.formatPrice6(newPrice)}: ${dir} last filled ${Format.formatPrice6(check.pivot)} (halfInc ${check.halfInc}% thr ${Format.formatPrice6(check.threshold)})`, '
|
|
3189
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] Skipping ${orderType} UPDATE for ${action.id} -> ${action.newGridId} at ${Format.formatPrice6(newPrice)}: ${dir} last filled ${Format.formatPrice6(check.pivot)} (halfInc ${check.halfInc}% thr ${Format.formatPrice6(check.threshold)})`, 'debug');
|
|
3102
3190
|
continue;
|
|
3103
3191
|
}
|
|
3104
3192
|
lastFillGuardStats.passed++;
|
|
@@ -3214,10 +3302,10 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
3214
3302
|
}
|
|
3215
3303
|
// LAST-FILL GUARD (fallback variant): this CREATE
|
|
3216
3304
|
// replaces a rotation UPDATE at a repriced level,
|
|
3217
|
-
// so it obeys the same guard
|
|
3218
|
-
// pivot — no origin bypass, same as rotations.
|
|
3305
|
+
// so it obeys the same guard against the frozen
|
|
3306
|
+
// batch pivot — no origin bypass, same as rotations.
|
|
3219
3307
|
try {
|
|
3220
|
-
const { check: fbCheck, refreshed: fbRefreshed } = runLastFillGuardCheck(bot, fbPrice, fbSize, fbType, lastFillGuardStats);
|
|
3308
|
+
const { check: fbCheck, refreshed: fbRefreshed } = runLastFillGuardCheck(bot, fbPrice, fbSize, fbType, lastFillGuardStats, true);
|
|
3221
3309
|
if (fbRefreshed)
|
|
3222
3310
|
lastFillGuardPivotRefreshed = true;
|
|
3223
3311
|
if (fbCheck.blocked) {
|
|
@@ -3226,7 +3314,7 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
3226
3314
|
bot.manager.logger.log(`[LAST-FILL-GUARD] Skipping CREATE fallback for ${targetSlotId} at ` +
|
|
3227
3315
|
`${Format.formatPrice6(fbPrice)}: ${fbDir} last filled ` +
|
|
3228
3316
|
`${Format.formatPrice6(fbCheck.pivot)} (halfInc ${fbCheck.halfInc}% thr ` +
|
|
3229
|
-
`${Format.formatPrice6(fbCheck.threshold)}); re-planned after market moves`, '
|
|
3317
|
+
`${Format.formatPrice6(fbCheck.threshold)}); re-planned after market moves`, 'debug');
|
|
3230
3318
|
continue;
|
|
3231
3319
|
}
|
|
3232
3320
|
lastFillGuardStats.passed++;
|
|
@@ -3268,10 +3356,10 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
3268
3356
|
// are what incident reconstruction needs. Origin folds in here as
|
|
3269
3357
|
// mode=bypassed(<origin>); there is no second source of truth.
|
|
3270
3358
|
// Cold (guard off) is warn, not info — a disabled guard must say so.
|
|
3271
|
-
// pivotRefreshed surfaces whether
|
|
3272
|
-
//
|
|
3273
|
-
//
|
|
3274
|
-
//
|
|
3359
|
+
// pivotRefreshed surfaces whether the batch-start freeze picked up a
|
|
3360
|
+
// queued fill (the refresh itself stays debug). The pivot is frozen
|
|
3361
|
+
// for the whole batch, so every action was checked against the same
|
|
3362
|
+
// pivot printed here.
|
|
3275
3363
|
try {
|
|
3276
3364
|
const totalGuarded = lastFillGuardStats.checked + lastFillGuardStats.bypassed;
|
|
3277
3365
|
if (totalGuarded > 0) {
|
|
@@ -3312,10 +3400,42 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
3312
3400
|
// the committed boundary; intersect-only, grid still commits.
|
|
3313
3401
|
const refillHold = resolveRefillBoundaryHold(workingBoundary, bot.manager.boundaryIdx, skippedUpdateSlotIds, clampedUpdateSlotIds, cowResult?.refillSlotIds, skippedCreateSlotIds);
|
|
3314
3402
|
effectiveBoundary = refillHold.effectiveBoundary;
|
|
3403
|
+
boundaryHeld = refillHold.heldRefillSlotIds.length > 0;
|
|
3315
3404
|
if (refillHold.heldRefillSlotIds.length > 0) {
|
|
3316
3405
|
bot.manager.logger.log(`[COW] Boundary hold: ${refillHold.heldRefillSlotIds.length} refill slot(s) skipped ` +
|
|
3317
3406
|
`(${refillHold.heldRefillSlotIds.join(', ')}) — keeping ${bot.manager.boundaryIdx} over planned ${workingBoundary}`, 'warn');
|
|
3318
3407
|
}
|
|
3408
|
+
// Consecutive-hold tracking (INV-COW-007 visibility): escalate a
|
|
3409
|
+
// growing run — the grid is trailing the market and only fresh
|
|
3410
|
+
// fills unstick it. Cleared automatically on the first clean batch.
|
|
3411
|
+
const consecutiveHolds = trackBoundaryHold(bot.manager, refillHold.heldRefillSlotIds.length > 0, bot.manager.boundaryIdx, workingBoundary, refillHold.heldRefillSlotIds);
|
|
3412
|
+
if (consecutiveHolds >= 3) {
|
|
3413
|
+
bot.manager.logger.log(`[COW] Boundary held ${consecutiveHolds} consecutive batches ` +
|
|
3414
|
+
`(keeping ${bot.manager.boundaryIdx} over planned ${workingBoundary}). ` +
|
|
3415
|
+
`Grid is trailing the market — refills re-price once the guard pivot settles; ` +
|
|
3416
|
+
`heals on the next fill-driven plan. Investigate only if the run keeps growing without new fills.`, 'warn');
|
|
3417
|
+
}
|
|
3418
|
+
// Guard-aware re-center escalation (INV-COW-007 heal path): a run of
|
|
3419
|
+
// holds carrying fresh fills means the grid is trailing the market and
|
|
3420
|
+
// will not heal from fill-less replans (they re-derive the identical
|
|
3421
|
+
// veto). Request a structural resync that re-derives centers on the
|
|
3422
|
+
// live pivot; the cooldown prevents resync storms. requestStructuralGridResync
|
|
3423
|
+
// re-defers while this batch is still in flight, so it runs in a clean context.
|
|
3424
|
+
if (boundaryHeld && consecutiveHolds >= holdResyncThreshold) {
|
|
3425
|
+
const freshFills = Array.isArray(cowResult?.fills) && cowResult.fills.length > 0;
|
|
3426
|
+
const lastResyncAt = Number(bot.manager._lastBoundaryHoldResyncAt) || 0;
|
|
3427
|
+
if (freshFills && (Date.now() - lastResyncAt) >= holdResyncCooldownMs) {
|
|
3428
|
+
bot.manager._lastBoundaryHoldResyncAt = Date.now();
|
|
3429
|
+
bot.manager.logger.log(`[COW] Boundary held ${consecutiveHolds} consecutive batches with fresh fills; ` +
|
|
3430
|
+
`requesting guard-aware structural re-center (cooldown ${Math.round(holdResyncCooldownMs / 1000)}s)`, 'warn');
|
|
3431
|
+
try {
|
|
3432
|
+
void bot.manager.requestStructuralGridResync?.('boundary-hold-trailing-market', { reason: 'boundary-hold-trailing-market' });
|
|
3433
|
+
}
|
|
3434
|
+
catch (err) {
|
|
3435
|
+
bot.manager.logger.log(`[COW] Structural re-center request failed (non-fatal): ${getErrorMessage(err)}`, 'warn');
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3319
3439
|
if (operations.length === 0) {
|
|
3320
3440
|
// Pop the working grid: in the re-plan recursion the fresh plan's
|
|
3321
3441
|
// grid was pushed by performSafeRebalance, and nothing downstream
|
|
@@ -3454,7 +3574,7 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
3454
3574
|
// options.result, so a later throw in this frame (e.g.
|
|
3455
3575
|
// processBatchResults after a successful commit) cannot pop a
|
|
3456
3576
|
// second time for the same grid in the batch catch below.
|
|
3457
|
-
const commitOk = await bot.manager._commitWorkingGrid(workingGrid, workingIndexes, effectiveBoundary, { skipRecalc: true, result: cowResult });
|
|
3577
|
+
const commitOk = await bot.manager._commitWorkingGrid(workingGrid, workingIndexes, effectiveBoundary, { skipRecalc: true, result: cowResult, boundaryHeld });
|
|
3458
3578
|
if (!commitOk) {
|
|
3459
3579
|
// Master changed during broadcast (e.g. a fill landed and was
|
|
3460
3580
|
// processed concurrently) so the commit was refused. The batch
|
|
@@ -3544,7 +3664,7 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
3544
3664
|
// _commitWorkingGrid pops on every settle path and clears
|
|
3545
3665
|
// the push marker via options.result, so a later throw here
|
|
3546
3666
|
// must not pop again in the batch catch below.
|
|
3547
|
-
const pollCommitOk = await bot.manager._commitWorkingGrid(workingGrid, workingIndexes, effectiveBoundary, { skipRecalc: true, result: cowResult });
|
|
3667
|
+
const pollCommitOk = await bot.manager._commitWorkingGrid(workingGrid, workingIndexes, effectiveBoundary, { skipRecalc: true, result: cowResult, boundaryHeld });
|
|
3548
3668
|
if (!pollCommitOk) {
|
|
3549
3669
|
// Master moved while polling — same recovery as the
|
|
3550
3670
|
// refused-commit path: adopt from chain, keep pending
|
|
@@ -3878,24 +3998,32 @@ async function recoverRefusedCommit(bot, chainOrders, logPrefix, adoptOpts, cont
|
|
|
3878
3998
|
return successReturn ?? { executed: false, hadRotation: false, commitRefused: true, ...extraReturn };
|
|
3879
3999
|
}
|
|
3880
4000
|
/**
|
|
3881
|
-
* Run the last-fill guard probe: refresh the pivot from
|
|
3882
|
-
* read the durable pivot, and evaluate
|
|
3883
|
-
* the identical probe pattern in the
|
|
3884
|
-
* CREATE-fallback guards (origin bypasses and
|
|
3885
|
-
* call sites, which differ per action kind).
|
|
4001
|
+
* Run the last-fill guard probe: optionally refresh the pivot from
|
|
4002
|
+
* still-queued fills, read the durable pivot, and evaluate
|
|
4003
|
+
* isLastFillGuardBlocked. Consolidates the identical probe pattern in the
|
|
4004
|
+
* CREATE, UPDATE-rotation, and CREATE-fallback guards (origin bypasses and
|
|
4005
|
+
* skip logging stay at the call sites, which differ per action kind).
|
|
4006
|
+
* Batch callers pass skipRefresh=true: the batch-start freeze owns refreshes
|
|
4007
|
+
* so every action in a batch is judged against the same pivot.
|
|
3886
4008
|
* @param {Object} bot
|
|
3887
4009
|
* @param {number} price - Target order price
|
|
3888
4010
|
* @param {number} size - Order size
|
|
3889
4011
|
* @param {string} type - ORDER_TYPES.BUY/SELL
|
|
3890
4012
|
* @param {Object} stats - lastFillGuardStats tracker (checked++ here)
|
|
4013
|
+
* @param {boolean} [skipRefresh=false] - Skip the queued-fill pivot refresh
|
|
3891
4014
|
* @returns {{check: Object, refreshed: boolean}}
|
|
3892
4015
|
*/
|
|
3893
|
-
function runLastFillGuardCheck(bot, price, size, type, stats) {
|
|
4016
|
+
function runLastFillGuardCheck(bot, price, size, type, stats, skipRefresh = false) {
|
|
3894
4017
|
let refreshed = false;
|
|
3895
|
-
|
|
3896
|
-
|
|
4018
|
+
// The batch-start freeze (see broadcast loop head) owns pivot refreshes;
|
|
4019
|
+
// per-action refreshes are disabled so every action in a batch is judged
|
|
4020
|
+
// against the same pivot. Kept opt-in for non-batch callers.
|
|
4021
|
+
if (!skipRefresh) {
|
|
4022
|
+
try {
|
|
4023
|
+
refreshed = !!refreshLastFillPivotFromQueue(bot);
|
|
4024
|
+
}
|
|
4025
|
+
catch { /* best-effort */ }
|
|
3897
4026
|
}
|
|
3898
|
-
catch { /* best-effort */ }
|
|
3899
4027
|
const lastPrice = bot.manager?._lastFilledPrice;
|
|
3900
4028
|
const lastType = bot.manager?._lastFilledType;
|
|
3901
4029
|
const inc = resolveLastFillGuardIncrement(bot);
|
|
@@ -4145,7 +4273,7 @@ async function processBatchResults(bot, result, opContexts) {
|
|
|
4145
4273
|
updateOperationCount
|
|
4146
4274
|
};
|
|
4147
4275
|
}
|
|
4148
|
-
export { isLastFillGuardBlocked, refreshLastFillPivotFromQueue, buildOutsideInPairGroupsForOrders, buildOutsideInPairGroupsForCreateEntries, extractOperationResults, findMissingCreateResultContexts, markMissingCreateResultsAsStructuralBlocker, formatUnmatchedChainOrderForLog, recordPendingBroadcast, clearPendingBroadcasts, clearPendingBroadcastsForSlots, popPushedWorkingGrid, buildChainOrderFingerprint, normalizeChainOrderForPendingMatch, findChainOrderForSlot, reconcileAfterUncertainBroadcast, reconcileAfterUncertainBroadcastImpl, autoCancelOneUnmatchedOrphan, shouldExecuteCreatePairMode, executeWithRetryOnUncertain, executeChunkedWithRetryOnUncertain, formatPartialBroadcastSummary, executeOperationsWithStrategy, validateOperationFunds, resolveIdealSizeForValidation, validateOrderSizeForExecution, buildActionsFromPlan, buildCowResultFromPlan, restoreSkippedUpdateSlotsInWorkingGrid, applyRotationTransitionsToWorkingGrid, pollChainForConfirmation, updateOrdersOnChainBatchCOW, processBatchResults, adoptPlacedBatchFromChain, resolveRefillBoundaryHold, toRefillSlotIdSet };
|
|
4276
|
+
export { isLastFillGuardBlocked, refreshLastFillPivotFromQueue, buildOutsideInPairGroupsForOrders, buildOutsideInPairGroupsForCreateEntries, extractOperationResults, findMissingCreateResultContexts, markMissingCreateResultsAsStructuralBlocker, formatUnmatchedChainOrderForLog, recordPendingBroadcast, clearPendingBroadcasts, clearPendingBroadcastsForSlots, popPushedWorkingGrid, buildChainOrderFingerprint, normalizeChainOrderForPendingMatch, findChainOrderForSlot, reconcileAfterUncertainBroadcast, reconcileAfterUncertainBroadcastImpl, autoCancelOneUnmatchedOrphan, shouldExecuteCreatePairMode, executeWithRetryOnUncertain, executeChunkedWithRetryOnUncertain, formatPartialBroadcastSummary, executeOperationsWithStrategy, validateOperationFunds, resolveIdealSizeForValidation, validateOrderSizeForExecution, buildActionsFromPlan, buildCowResultFromPlan, restoreSkippedUpdateSlotsInWorkingGrid, applyRotationTransitionsToWorkingGrid, pollChainForConfirmation, updateOrdersOnChainBatchCOW, processBatchResults, adoptPlacedBatchFromChain, resolveRefillBoundaryHold, toRefillSlotIdSet, trackBoundaryHold };
|
|
4149
4277
|
// Exported for regression tests (issue #23 sibling): the uncertain-broadcast
|
|
4150
4278
|
// discard path must never drop a placement silently when master lost the slot.
|
|
4151
4279
|
export { restoreDiscardedCreates };
|
|
@@ -4179,6 +4307,7 @@ export default {
|
|
|
4179
4307
|
restoreSkippedUpdateSlotsInWorkingGrid,
|
|
4180
4308
|
resolveRefillBoundaryHold,
|
|
4181
4309
|
toRefillSlotIdSet,
|
|
4310
|
+
trackBoundaryHold,
|
|
4182
4311
|
applyRotationTransitionsToWorkingGrid,
|
|
4183
4312
|
pollChainForConfirmation,
|
|
4184
4313
|
updateOrdersOnChainBatchCOW,
|