dexbot 1.4.25 → 1.5.1
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 +54 -0
- package/README.md +6 -2
- package/analysis/README.md +14 -7
- 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 +56 -20
- 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.map +1 -1
- package/dist/analysis/grid_correction_check.js +60 -20
- package/dist/analysis/grid_correction_check.js.map +1 -1
- 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 +73 -17
- 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 +168 -17
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/account_orders.d.ts +7 -1
- package/dist/modules/account_orders.d.ts.map +1 -1
- package/dist/modules/account_orders.js +34 -1
- 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/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/constants.d.ts +2 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +27 -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 +3 -1
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +22 -6
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +36 -2
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +434 -20
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +15 -16
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +69 -42
- 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 +12 -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 +7 -0
- 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/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 +34 -0
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +182 -5
- 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.map +1 -1
- package/dist/modules/order/grid.js +68 -28
- 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 +72 -0
- 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 +41 -1
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +186 -17
- 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 +17 -2
- 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 +59 -9
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +42 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +91 -1
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +52 -1
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +137 -7
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +66 -4
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +156 -16
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/validate.d.ts +30 -3
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +206 -11
- package/dist/modules/order/utils/validate.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 +11 -34
- package/dist/scripts/analyze-orders.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 +3 -3
- package/docs/EVOLUTION.md +8 -6
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +3 -1
- package/docs/GRID_RECONCILE.md +1 -0
- package/docs/LOGGING.md +3 -0
- package/docs/MPA_CREDIT_USAGE.md +3 -1
- package/docs/README.md +2 -2
- package/docs/WORKFLOW.md +3 -1
- package/package.json +2 -1
- package/scripts/README.md +12 -0
|
@@ -14,11 +14,41 @@ import { BroadcastUncertainError as BroadcastUncertainErrorBinding } from './dex
|
|
|
14
14
|
const BroadcastUncertainError = BroadcastUncertainErrorBinding;
|
|
15
15
|
import * as orderUtils from './order/utils/order.js';
|
|
16
16
|
import { sleep } from './order/utils/system.js';
|
|
17
|
-
const { buildCreateOrderArgs, buildCreateOpFingerprint, extractBatchOperationResults, formatUnmatchedChainOrder, convertToSpreadPlaceholder, buildOutsideInPairGroups, isOrderPlaced, } = orderUtils;
|
|
17
|
+
const { buildCreateOrderArgs, buildCreateOpFingerprint, extractBatchOperationResults, formatUnmatchedChainOrder, convertToSpreadPlaceholder, toRailHolePlaceholder, buildOutsideInPairGroups, isOrderPlaced, } = orderUtils;
|
|
18
18
|
import * as validate from './order/utils/validate.js';
|
|
19
|
-
const { validateCreateTargetSlots, evaluateCommit, hasExecutableActions } = validate;
|
|
19
|
+
const { validateCreateTargetSlots, evaluateCommit, hasExecutableActions, stampGapEvacuationRotation } = validate;
|
|
20
20
|
import * as math from './order/utils/math.js';
|
|
21
|
-
const { validateOrderSize, findCrossedOrder, priceSlotEqual } = math;
|
|
21
|
+
const { validateOrderSize, findCrossedOrder, priceSlotEqual, isEvacuationRotationAllowed, getSellStartIdx, getPrecisionByOrderType, isSlotIndexInGapBand } = math;
|
|
22
|
+
/**
|
|
23
|
+
* Re-verify a B-stamped gap-evacuation rotation against the LIVE committed
|
|
24
|
+
* geometry (boundaryIdx/_gapSlots at execution time). The stamp freezes
|
|
25
|
+
* plan-build geometry, but a boundary commit can land between plan-build and
|
|
26
|
+
* execution; when the live geometry is finite and the source is no longer
|
|
27
|
+
* in-band (or the dest no longer rail), the stamp is stale and the rotation
|
|
28
|
+
* must be re-proven by the unstamped live probe instead of bypassing the
|
|
29
|
+
* last-fill guard. Non-finite live geometry cannot disprove the stamp and
|
|
30
|
+
* keeps the plan-build authority (fail-open to the stamp, never to the guard).
|
|
31
|
+
*/
|
|
32
|
+
export function isEvacuationStampStillValid(liveBoundary, liveGapSlots, sourceId, destId, orderType) {
|
|
33
|
+
const b = Number(liveBoundary);
|
|
34
|
+
const g = Number(liveGapSlots);
|
|
35
|
+
if (orderType !== ORDER_TYPES.BUY && orderType !== ORDER_TYPES.SELL)
|
|
36
|
+
return false;
|
|
37
|
+
if (!Number.isFinite(b) || !Number.isFinite(g) || g < 0)
|
|
38
|
+
return true;
|
|
39
|
+
const srcIdx = parseSlotIndex(sourceId);
|
|
40
|
+
const dstIdx = parseSlotIndex(destId);
|
|
41
|
+
if (srcIdx === null || srcIdx === undefined || dstIdx === null || dstIdx === undefined)
|
|
42
|
+
return false;
|
|
43
|
+
if (!isSlotIndexInGapBand(srcIdx, b, g))
|
|
44
|
+
return false;
|
|
45
|
+
if (isSlotIndexInGapBand(dstIdx, b, g))
|
|
46
|
+
return false;
|
|
47
|
+
const sellStartIdx = getSellStartIdx(b, g);
|
|
48
|
+
const dstInRail = orderType === ORDER_TYPES.SELL ? Number(dstIdx) >= sellStartIdx : Number(dstIdx) <= b;
|
|
49
|
+
return dstInRail;
|
|
50
|
+
}
|
|
51
|
+
import { parseSlotIndex } from './order/utils/slot.js';
|
|
22
52
|
function hasSlotPriceCollision(items, targetPrice, precision, excludeId, predicate) {
|
|
23
53
|
for (const it of items) {
|
|
24
54
|
if (predicate && !predicate(it))
|
|
@@ -1445,7 +1475,14 @@ function validateOrderSizeForExecution(bot, size, type, orderLike = null, fallba
|
|
|
1445
1475
|
* LAST-FILL-GUARD helper — pivot ± halfIncrement (replaces price-tolerance).
|
|
1446
1476
|
* last fill @x with increment i: BUY < x*(1 - i/2/100), SELL > x*(1 + i/2/100)
|
|
1447
1477
|
* e.g. x=1000, i=0.5% => BUY < 997.5, SELL > 1002.5
|
|
1448
|
-
* Cold (pivot null or lastType null) => disabled. Spread
|
|
1478
|
+
* Cold (pivot null or lastType null) => disabled. Spread-correction CREATES
|
|
1479
|
+
* bypass per-action (see broadcast sites); gap-evacuation rotation UPDATEs
|
|
1480
|
+
* bypass only via the violation-reducing allowance (origin='gap-evacuation',
|
|
1481
|
+
* UPDATE-only, frozen B-stamp or live-proven isEvacuationRotationAllowed) —
|
|
1482
|
+
* see the UPDATE rotation guard block.
|
|
1483
|
+
* The pivot is the latest fill of either side and is never expired: it stays
|
|
1484
|
+
* the durable mark (last sold level floors new sells; buy fills pull it down
|
|
1485
|
+
* and re-open the sell side, buy-below-sell is never gated).
|
|
1449
1486
|
* @param {number} price - Target order price
|
|
1450
1487
|
* @param {number} size - Order size (unused, kept for compat)
|
|
1451
1488
|
* @param {string} type - ORDER_TYPES.BUY/SELL
|
|
@@ -1478,6 +1515,123 @@ function isLastFillGuardBlocked(price, _size, type, lastPrice, lastType, increme
|
|
|
1478
1515
|
return { blocked: true, pivot, halfInc, threshold: sellThreshold };
|
|
1479
1516
|
return { blocked: false, pivot: null, halfInc, threshold: null };
|
|
1480
1517
|
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Refresh the LAST-FILL guard pivot from fills that arrived while the current
|
|
1520
|
+
* batch was being planned or broadcast but have not been ingested yet.
|
|
1521
|
+
*
|
|
1522
|
+
* The pivot (_lastFilledPrice) is recorded per fill-processing batch, but a
|
|
1523
|
+
* multi-chunk COW broadcast spans tens of seconds — fills detected mid-cycle
|
|
1524
|
+
* sit in _incomingFillQueue until the cycle ends, so ops built for later
|
|
1525
|
+
* chunks would be checked against a stale pivot (production incident: a chunk
|
|
1526
|
+
* planned with a pre-crash pivot placed asks 0.6% below the true latest fill
|
|
1527
|
+
* that was already queued). Peek-only: never drains the queue, the owning
|
|
1528
|
+
* fill cycle still processes every entry. Best-effort: returns false when no
|
|
1529
|
+
* queued fill yields a usable side + price.
|
|
1530
|
+
* @param {import('./dexbot_class.js').DEXBot} bot
|
|
1531
|
+
* @returns {boolean} True when the pivot was refreshed from queued fills
|
|
1532
|
+
*/
|
|
1533
|
+
function refreshLastFillPivotFromQueue(bot) {
|
|
1534
|
+
try {
|
|
1535
|
+
const queue = bot?._incomingFillQueue;
|
|
1536
|
+
if (!Array.isArray(queue) || queue.length === 0)
|
|
1537
|
+
return false;
|
|
1538
|
+
const mgr = bot?.manager;
|
|
1539
|
+
if (!mgr || !mgr.orders)
|
|
1540
|
+
return false;
|
|
1541
|
+
const assets = mgr.assets;
|
|
1542
|
+
const findSlotByOrderId = (orderId) => {
|
|
1543
|
+
if (!orderId)
|
|
1544
|
+
return null;
|
|
1545
|
+
try {
|
|
1546
|
+
for (const o of mgr.orders.values()) {
|
|
1547
|
+
if (o?.orderId === orderId)
|
|
1548
|
+
return o;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
catch { /* ignore iteration errors */ }
|
|
1552
|
+
return null;
|
|
1553
|
+
};
|
|
1554
|
+
let latest = null;
|
|
1555
|
+
for (const fill of queue) {
|
|
1556
|
+
const fillOp = fill?.op?.[1] || fill;
|
|
1557
|
+
const orderId = fillOp?.order_id || fill?.orderId;
|
|
1558
|
+
let type = null;
|
|
1559
|
+
let price = null;
|
|
1560
|
+
// Prefer the grid slot: a limit fill executes at (or better than)
|
|
1561
|
+
// its slot price, which is exactly the guard's price convention.
|
|
1562
|
+
const slot = findSlotByOrderId(orderId);
|
|
1563
|
+
if (slot && (slot.type === ORDER_TYPES.BUY || slot.type === ORDER_TYPES.SELL)) {
|
|
1564
|
+
type = slot.type;
|
|
1565
|
+
const slotPrice = Number(slot.price);
|
|
1566
|
+
if (Number.isFinite(slotPrice) && slotPrice > 0)
|
|
1567
|
+
price = slotPrice;
|
|
1568
|
+
}
|
|
1569
|
+
// Fall back to fill economics (B/A convention, mirroring
|
|
1570
|
+
// _computeFillContext's pays-asset side resolution, including
|
|
1571
|
+
// SPREAD slots carrying on-chain orders).
|
|
1572
|
+
if ((type == null || price == null) && fillOp?.pays && fillOp?.receives && assets?.assetA && assets?.assetB) {
|
|
1573
|
+
try {
|
|
1574
|
+
const paysId = fillOp.pays.asset_id;
|
|
1575
|
+
const paysAmt = Number(fillOp.pays.amount);
|
|
1576
|
+
const recvAmt = Number(fillOp.receives.amount);
|
|
1577
|
+
if (Number.isFinite(paysAmt) && paysAmt > 0 && Number.isFinite(recvAmt) && recvAmt > 0) {
|
|
1578
|
+
const paysFloat = (base, precision) => base / Math.pow(10, precision);
|
|
1579
|
+
if (paysId === assets.assetA.id) {
|
|
1580
|
+
type = ORDER_TYPES.SELL;
|
|
1581
|
+
price = paysFloat(recvAmt, assets.assetB.precision) / paysFloat(paysAmt, assets.assetA.precision);
|
|
1582
|
+
}
|
|
1583
|
+
else if (paysId === assets.assetB.id) {
|
|
1584
|
+
type = ORDER_TYPES.BUY;
|
|
1585
|
+
price = paysFloat(paysAmt, assets.assetB.precision) / paysFloat(recvAmt, assets.assetA.precision);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
catch { /* best-effort */ }
|
|
1590
|
+
}
|
|
1591
|
+
if ((type === ORDER_TYPES.BUY || type === ORDER_TYPES.SELL)
|
|
1592
|
+
&& Number.isFinite(price) && price > 0) {
|
|
1593
|
+
latest = { price: price, type };
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
if (!latest)
|
|
1597
|
+
return false;
|
|
1598
|
+
mgr._lastFilledPrice = latest.price;
|
|
1599
|
+
mgr._lastFilledType = latest.type;
|
|
1600
|
+
if (latest.type === ORDER_TYPES.BUY) {
|
|
1601
|
+
mgr._lastFilledBuyPrice = latest.price;
|
|
1602
|
+
}
|
|
1603
|
+
else {
|
|
1604
|
+
mgr._lastFilledSellPrice = latest.price;
|
|
1605
|
+
}
|
|
1606
|
+
try {
|
|
1607
|
+
mgr.logger?.log?.(`[LAST-FILL-GUARD] Pivot refreshed from ${queue.length} pending queued fill(s): ` +
|
|
1608
|
+
`${latest.type} @${Format.formatPrice6(latest.price)}`, 'debug');
|
|
1609
|
+
}
|
|
1610
|
+
catch { /* ignore logging errors */ }
|
|
1611
|
+
return true;
|
|
1612
|
+
}
|
|
1613
|
+
catch {
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Resolve the grid increment percent for the LAST-FILL guard in one place so
|
|
1619
|
+
* every check site and the batch summary use (and print) the same value.
|
|
1620
|
+
* Falls back to the default 0.5 when unset/invalid.
|
|
1621
|
+
* @param {import('./dexbot_class.js').DEXBot} bot
|
|
1622
|
+
* @returns {number} Positive increment percent
|
|
1623
|
+
*/
|
|
1624
|
+
function resolveLastFillGuardIncrement(bot) {
|
|
1625
|
+
const raw = Number(bot?.manager?.config?.incrementPercent
|
|
1626
|
+
?? bot?.config?.incrementPercent
|
|
1627
|
+
?? constantsModule?.DEFAULT_CONFIG?.incrementPercent
|
|
1628
|
+
?? 0.5);
|
|
1629
|
+
if (Number.isFinite(raw) && raw > 0)
|
|
1630
|
+
return raw;
|
|
1631
|
+
return Number(constantsModule?.DEFAULT_CONFIG?.incrementPercent) > 0
|
|
1632
|
+
? Number(constantsModule?.DEFAULT_CONFIG?.incrementPercent)
|
|
1633
|
+
: 0.5;
|
|
1634
|
+
}
|
|
1481
1635
|
/**
|
|
1482
1636
|
* Build COW actions array from a simple plan object.
|
|
1483
1637
|
* @param {import('./dexbot_class.js').DEXBot} bot
|
|
@@ -1489,6 +1643,54 @@ function buildActionsFromPlan(_bot, plan) {
|
|
|
1489
1643
|
? { ordersToPlace: plan }
|
|
1490
1644
|
: (plan || {});
|
|
1491
1645
|
const { ordersToPlace = [], ordersToRotate = [], ordersToUpdate = [], ordersToCancel = [] } = normalizedPlan;
|
|
1646
|
+
// Per-action origin: guard bypasses are scoped per action (not per batch),
|
|
1647
|
+
// so a future rotation entry merged into a correction plan cannot silently
|
|
1648
|
+
// inherit the spread-correction bypass. Actions built outside this helper
|
|
1649
|
+
// carry no origin and default to guarded (safe default).
|
|
1650
|
+
const planOrigin = normalizedPlan?.origin;
|
|
1651
|
+
// B-stamp inputs: the plan-level boundary + gap width frozen AT
|
|
1652
|
+
// PLAN-BUILD TIME. The stamp carries this geometry so the execution
|
|
1653
|
+
// guard can bypass on it — and re-verifies it against the LIVE
|
|
1654
|
+
// committed geometry at execution time (isEvacuationStampStillValid),
|
|
1655
|
+
// downgrading stale stamps to the live probe.
|
|
1656
|
+
const frozenBoundaryRaw = Number(normalizedPlan?.boundaryIdx ?? _bot?.manager?.boundaryIdx);
|
|
1657
|
+
const frozenGapRaw = Number(normalizedPlan?.gapSlots ?? _bot?.manager?._gapSlots);
|
|
1658
|
+
const hasFrozenGeometry = Number.isFinite(frozenBoundaryRaw) && Number.isFinite(frozenGapRaw) && frozenGapRaw >= 0;
|
|
1659
|
+
const withOrigin = (action, sourceMaster = null) => {
|
|
1660
|
+
if (!planOrigin)
|
|
1661
|
+
return action;
|
|
1662
|
+
// Origin only rides UPDATEs (where the guard reads it). Gap-plan
|
|
1663
|
+
// CREATEs carry a provably dead origin — the spread-correction
|
|
1664
|
+
// CREATE check falls back to the batch origin, so dropping the
|
|
1665
|
+
// per-action origin there is safe. Other plan origins keep riding
|
|
1666
|
+
// non-UPDATE actions unchanged.
|
|
1667
|
+
if (action?.type !== COW_ACTIONS.UPDATE) {
|
|
1668
|
+
return planOrigin === 'gap-evacuation' ? { ...action } : { ...action, origin: planOrigin };
|
|
1669
|
+
}
|
|
1670
|
+
if (planOrigin !== 'gap-evacuation')
|
|
1671
|
+
return { ...action, origin: planOrigin };
|
|
1672
|
+
if (!hasFrozenGeometry)
|
|
1673
|
+
return { ...action, origin: planOrigin };
|
|
1674
|
+
// Route through the real stampler: geometry (source in-band, dest
|
|
1675
|
+
// rail), bit-exact non-growing size (side precision) and outward
|
|
1676
|
+
// repricing must all PROVE — a plan-level origin alone never grants
|
|
1677
|
+
// the bypass. Without a source master the proof is impossible, so
|
|
1678
|
+
// the action stays unstamped (the live probe at execution can still
|
|
1679
|
+
// re-prove it from the master grid).
|
|
1680
|
+
const base = { ...action, origin: planOrigin };
|
|
1681
|
+
if (!sourceMaster)
|
|
1682
|
+
return { ...base, origin: undefined };
|
|
1683
|
+
// The stampler wants the ORDER type (SELL/BUY), which rides on
|
|
1684
|
+
// action.order.type — action.type is the COW action kind ('update').
|
|
1685
|
+
const rotOrderType = action?.order?.type ?? action?.type;
|
|
1686
|
+
const proved = stampGapEvacuationRotation(base, sourceMaster, action.newPrice, action.newSize, rotOrderType, frozenBoundaryRaw, frozenGapRaw, _bot?.manager?.assets);
|
|
1687
|
+
// Proof refused: strip the origin so the action is exactly an
|
|
1688
|
+
// unstamped rotation (never an origin claim without a stamp).
|
|
1689
|
+
if (!proved?.evacBoundary && !proved?.evacGapSlots) {
|
|
1690
|
+
proved.origin = undefined;
|
|
1691
|
+
}
|
|
1692
|
+
return proved;
|
|
1693
|
+
};
|
|
1492
1694
|
const actions = [];
|
|
1493
1695
|
for (const o of ordersToCancel) {
|
|
1494
1696
|
if (o?.orderId) {
|
|
@@ -1509,7 +1711,7 @@ function buildActionsFromPlan(_bot, plan) {
|
|
|
1509
1711
|
const orderType = r?.type || oldOrder?.type;
|
|
1510
1712
|
if (!id || !orderId || !newGridId || !orderType || !Number.isFinite(newPrice) || !(newSize > 0))
|
|
1511
1713
|
continue;
|
|
1512
|
-
actions.push({
|
|
1714
|
+
actions.push(withOrigin({
|
|
1513
1715
|
type: COW_ACTIONS.UPDATE,
|
|
1514
1716
|
id,
|
|
1515
1717
|
orderId,
|
|
@@ -1522,7 +1724,7 @@ function buildActionsFromPlan(_bot, plan) {
|
|
|
1522
1724
|
price: newPrice,
|
|
1523
1725
|
size: newSize
|
|
1524
1726
|
}
|
|
1525
|
-
});
|
|
1727
|
+
}, oldOrder));
|
|
1526
1728
|
}
|
|
1527
1729
|
for (const o of ordersToUpdate) {
|
|
1528
1730
|
const partialOrder = o?.partialOrder || o;
|
|
@@ -1534,7 +1736,7 @@ function buildActionsFromPlan(_bot, plan) {
|
|
|
1534
1736
|
: Number(partialOrder?.size || 0);
|
|
1535
1737
|
if (!id || !orderId)
|
|
1536
1738
|
continue;
|
|
1537
|
-
actions.push({
|
|
1739
|
+
actions.push(withOrigin({
|
|
1538
1740
|
type: COW_ACTIONS.UPDATE,
|
|
1539
1741
|
id,
|
|
1540
1742
|
orderId,
|
|
@@ -1546,12 +1748,12 @@ function buildActionsFromPlan(_bot, plan) {
|
|
|
1546
1748
|
type: orderType,
|
|
1547
1749
|
size: newSize
|
|
1548
1750
|
}
|
|
1549
|
-
});
|
|
1751
|
+
}));
|
|
1550
1752
|
}
|
|
1551
1753
|
for (const o of ordersToPlace) {
|
|
1552
1754
|
if (!o?.id)
|
|
1553
1755
|
continue;
|
|
1554
|
-
actions.push({ type: COW_ACTIONS.CREATE, id: o.id, order: o });
|
|
1756
|
+
actions.push(withOrigin({ type: COW_ACTIONS.CREATE, id: o.id, order: o }));
|
|
1555
1757
|
}
|
|
1556
1758
|
return actions;
|
|
1557
1759
|
}
|
|
@@ -1573,12 +1775,31 @@ function buildCowResultFromPlan(bot, plan) {
|
|
|
1573
1775
|
? requestedBoundary
|
|
1574
1776
|
: bot.manager.boundaryIdx;
|
|
1575
1777
|
const actions = buildActionsFromPlan(bot, plan);
|
|
1778
|
+
// Rail-aware hole projection (Phase 2): a cleared in-rail slot stays a
|
|
1779
|
+
// rail-typed VIRTUAL hole with its booked size preserved (rotation
|
|
1780
|
+
// sources keep the remainder the guard/plan must see); only true
|
|
1781
|
+
// gap-band slots become side-neutral SPREAD holes.
|
|
1782
|
+
const toWorkingHole = (slot) => {
|
|
1783
|
+
try {
|
|
1784
|
+
const idx = parseSlotIndex(slot?.id);
|
|
1785
|
+
const b = Number(workingBoundary);
|
|
1786
|
+
const gapSlots = Number(bot?.manager?._gapSlots);
|
|
1787
|
+
if (idx !== null && idx !== undefined && Number.isFinite(b) && Number.isFinite(gapSlots) && gapSlots >= 0) {
|
|
1788
|
+
if (Number(idx) <= b)
|
|
1789
|
+
return toRailHolePlaceholder(slot, ORDER_TYPES.BUY);
|
|
1790
|
+
if (Number(idx) >= getSellStartIdx(b, gapSlots))
|
|
1791
|
+
return toRailHolePlaceholder(slot, ORDER_TYPES.SELL);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
catch { /* fall through to SPREAD */ }
|
|
1795
|
+
return convertToSpreadPlaceholder(slot);
|
|
1796
|
+
};
|
|
1576
1797
|
for (const action of actions) {
|
|
1577
1798
|
if (action.type === COW_ACTIONS.CANCEL) {
|
|
1578
1799
|
const current = workingGrid.get(action.id);
|
|
1579
1800
|
if (!current)
|
|
1580
1801
|
continue;
|
|
1581
|
-
workingGrid.set(action.id,
|
|
1802
|
+
workingGrid.set(action.id, toWorkingHole(current));
|
|
1582
1803
|
}
|
|
1583
1804
|
else if (action.type === COW_ACTIONS.CREATE) {
|
|
1584
1805
|
if (!action.id || !action.order)
|
|
@@ -1596,7 +1817,7 @@ function buildCowResultFromPlan(bot, plan) {
|
|
|
1596
1817
|
if (action.newGridId && action.newGridId !== action.id) {
|
|
1597
1818
|
const current = workingGrid.get(action.id);
|
|
1598
1819
|
if (current) {
|
|
1599
|
-
workingGrid.set(action.id,
|
|
1820
|
+
workingGrid.set(action.id, toWorkingHole(current));
|
|
1600
1821
|
}
|
|
1601
1822
|
const targetId = action.newGridId;
|
|
1602
1823
|
const targetCurrent = workingGrid.get(targetId) || { id: targetId };
|
|
@@ -1657,7 +1878,11 @@ function applyRotationTransitionsToWorkingGrid(bot, workingGrid, executedContext
|
|
|
1657
1878
|
continue;
|
|
1658
1879
|
const { rotation } = ctx;
|
|
1659
1880
|
const { oldOrder, newGridId, newPrice, newSize, type } = rotation;
|
|
1660
|
-
// Source slot → VIRTUAL (if it's a different slot)
|
|
1881
|
+
// Source slot → VIRTUAL (if it's a different slot). Phase 2: the
|
|
1882
|
+
// source stays a RAIL-TYPED hole with its booked size preserved —
|
|
1883
|
+
// only state/orderId/rawOnChain are cleared, never type or size
|
|
1884
|
+
// (no SPREAD retype, no zeroing). In-rail sources must remain
|
|
1885
|
+
// visible to candidate-selection and evacuation geometry.
|
|
1661
1886
|
if (oldOrder?.id && oldOrder.id !== newGridId) {
|
|
1662
1887
|
const sourceSlot = workingGrid.get(oldOrder.id);
|
|
1663
1888
|
if (sourceSlot && sourceSlot.orderId) {
|
|
@@ -2343,6 +2568,14 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2343
2568
|
const opContexts = [];
|
|
2344
2569
|
const skippedUpdateSlotIds = new Set();
|
|
2345
2570
|
let skippedUpdateCount = 0;
|
|
2571
|
+
// Per-batch LAST-FILL-GUARD disposition counters. Per-action pass lines
|
|
2572
|
+
// would spam big batches, so the guard emits one batch summary instead
|
|
2573
|
+
// (see the summary after the action loop below).
|
|
2574
|
+
const lastFillGuardStats = { checked: 0, passed: 0, skipped: 0, bypassed: 0 };
|
|
2575
|
+
// Whether any guard check in this batch refreshed the pivot from
|
|
2576
|
+
// still-queued fills — reported in the batch summary so a pivot change
|
|
2577
|
+
// that altered a guard decision is visible at info, not just debug.
|
|
2578
|
+
let lastFillGuardPivotRefreshed = false;
|
|
2346
2579
|
// Slots whose size-update op was broadcast with a post-fill-clamped
|
|
2347
2580
|
// target: the working grid still holds the planned (larger) size, so the
|
|
2348
2581
|
// slots are re-synced from master before commit to keep the committed
|
|
@@ -2458,18 +2691,35 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2458
2691
|
continue;
|
|
2459
2692
|
}
|
|
2460
2693
|
// LAST-FILL PRICE GUARD: pivot ± halfIncrement (BUY < pivot*(1-half), SELL > pivot*(1+half)).
|
|
2461
|
-
//
|
|
2694
|
+
// Bypass is per-action: only spread-correction CREATES skip so gap repair can close.
|
|
2695
|
+
// The batch-level origin is honored only for actions without their own origin stamp
|
|
2696
|
+
// (back-compat for plans that bypass buildActionsFromPlan). Cold (null) => disabled.
|
|
2462
2697
|
try {
|
|
2463
|
-
|
|
2698
|
+
const actionOrigin = action?.origin;
|
|
2699
|
+
const batchOrigin = cowResult?.origin;
|
|
2700
|
+
const isCorrectionCreate = actionOrigin === 'spread-correction'
|
|
2701
|
+
|| (actionOrigin == null && batchOrigin === 'spread-correction');
|
|
2702
|
+
if (!isCorrectionCreate) {
|
|
2703
|
+
try {
|
|
2704
|
+
if (refreshLastFillPivotFromQueue(bot))
|
|
2705
|
+
lastFillGuardPivotRefreshed = true;
|
|
2706
|
+
}
|
|
2707
|
+
catch { /* best-effort */ }
|
|
2464
2708
|
const lastPrice = bot.manager?._lastFilledPrice;
|
|
2465
2709
|
const lastType = bot.manager?._lastFilledType;
|
|
2466
|
-
const inc =
|
|
2710
|
+
const inc = resolveLastFillGuardIncrement(bot);
|
|
2467
2711
|
const check = isLastFillGuardBlocked(createPrice, order.size, order.type, lastPrice, lastType, inc);
|
|
2712
|
+
lastFillGuardStats.checked++;
|
|
2468
2713
|
if (check.blocked) {
|
|
2714
|
+
lastFillGuardStats.skipped++;
|
|
2469
2715
|
const dir = order.type === ORDER_TYPES.BUY ? 'above' : 'below';
|
|
2470
2716
|
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`, 'warn');
|
|
2471
2717
|
continue;
|
|
2472
2718
|
}
|
|
2719
|
+
lastFillGuardStats.passed++;
|
|
2720
|
+
}
|
|
2721
|
+
else {
|
|
2722
|
+
lastFillGuardStats.bypassed++;
|
|
2473
2723
|
}
|
|
2474
2724
|
}
|
|
2475
2725
|
catch (_e) { /* guard is best-effort */ }
|
|
@@ -2563,14 +2813,114 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2563
2813
|
`${crossedOrderLabel(crossedOrder)}; re-planned after its cancel confirms.`, 'warn');
|
|
2564
2814
|
continue;
|
|
2565
2815
|
}
|
|
2566
|
-
// LAST-FILL PRICE GUARD (UPDATE rotation): pivot ± halfIncrement — same as CREATE
|
|
2816
|
+
// LAST-FILL PRICE GUARD (UPDATE rotation): pivot ± halfIncrement — same as CREATE,
|
|
2817
|
+
// plus the GAP-EVACUATION ALLOWANCE (violation-reducing): a rotation stamped
|
|
2818
|
+
// origin='gap-evacuation' at plan-build moves a live order OUT of the gap band
|
|
2819
|
+
// onto its rail with bit-exact non-growing size — it shrinks the violation
|
|
2820
|
+
// surface instead of adding exposure, so it bypasses the guard. The bypass
|
|
2821
|
+
// is UPDATE-only (CREATEs carry no source slot and can never qualify) and
|
|
2822
|
+
// honors the frozen B-stamp (evacBoundary/evacGapSlots captured when the
|
|
2823
|
+
// plan was built — never re-derived here, since plans can validate against
|
|
2824
|
+
// a later-committed boundary):
|
|
2825
|
+
// - B-stamped: plan-build geometry already proved the evacuation; bypass
|
|
2826
|
+
// outright (logged + counted as bypassed).
|
|
2827
|
+
// - Unstamped gap-evacuation: prove it live from oldPrice/oldSize read
|
|
2828
|
+
// off the MASTER grid above (bot.manager.orders.get, before rotation
|
|
2829
|
+
// pre-application virtualizes sources — after that the lookup lies);
|
|
2830
|
+
// fail closed (block) when unresolvable; otherwise run the pure
|
|
2831
|
+
// isEvacuationRotationAllowed decision and bypass only on allow.
|
|
2832
|
+
// Everything else obeys the guard (per-action scoping: a rotation entry
|
|
2833
|
+
// merged into another plan inherits no bypass).
|
|
2834
|
+
// The pivot is refreshed from still-queued fills first: fills detected mid-broadcast
|
|
2835
|
+
// sit in _incomingFillQueue until the fill cycle ends, and without this the later
|
|
2836
|
+
// chunks of a long broadcast would be checked against a stale pivot.
|
|
2567
2837
|
try {
|
|
2568
|
-
|
|
2838
|
+
let bypassedEvacuation = false;
|
|
2839
|
+
const rotationOrigin = action?.origin;
|
|
2840
|
+
if (rotationOrigin === 'gap-evacuation') {
|
|
2841
|
+
const frozenB = Number(action?.evacBoundary);
|
|
2842
|
+
const frozenG = Number(action?.evacGapSlots);
|
|
2843
|
+
let stampUsable = Number.isFinite(frozenB) && Number.isFinite(frozenG);
|
|
2844
|
+
if (stampUsable) {
|
|
2845
|
+
// Stale-stamp check: the stamp froze PLAN-BUILD
|
|
2846
|
+
// geometry; if a boundary commit landed between
|
|
2847
|
+
// plan-build and execution and the source is no
|
|
2848
|
+
// longer in-band (or dest no longer rail) under
|
|
2849
|
+
// the LIVE geometry, re-prove via the unstamped
|
|
2850
|
+
// live probe below instead of bypassing outright.
|
|
2851
|
+
const stampValid = isEvacuationStampStillValid(bot.manager?.boundaryIdx, bot.manager?._gapSlots, action.id, action.newGridId, orderType);
|
|
2852
|
+
if (!stampValid) {
|
|
2853
|
+
stampUsable = false;
|
|
2854
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] Stamped evacuation for ${action.id} -> ${action.newGridId} is stale under live ` +
|
|
2855
|
+
`boundary ${bot.manager?.boundaryIdx}/gap ${bot.manager?._gapSlots} — re-proving live`, 'warn');
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
if (stampUsable) {
|
|
2859
|
+
bypassedEvacuation = true;
|
|
2860
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] Allowing ${orderType} evacuation UPDATE for ${action.id} -> ${action.newGridId} at ${Format.formatPrice6(newPrice)}: ` +
|
|
2861
|
+
`gap-evacuation stamped at plan-build (boundary ${frozenB}, gap ${frozenG})`, 'warn');
|
|
2862
|
+
}
|
|
2863
|
+
else {
|
|
2864
|
+
// Unstamped: prove violation-reducing live. masterOrder was
|
|
2865
|
+
// captured from the master grid before any rotation
|
|
2866
|
+
// pre-application — it must NOT be re-read afterwards.
|
|
2867
|
+
const oldPrice = Number(masterOrder?.price);
|
|
2868
|
+
const oldSize = Number(masterOrder?.size);
|
|
2869
|
+
if (!Number.isFinite(oldPrice) || !(oldPrice > 0) || !Number.isFinite(oldSize) || !(oldSize > 0)) {
|
|
2870
|
+
lastFillGuardStats.checked++;
|
|
2871
|
+
lastFillGuardStats.skipped++;
|
|
2872
|
+
skippedUpdateCount++;
|
|
2873
|
+
if (action.id)
|
|
2874
|
+
skippedUpdateSlotIds.add(action.id);
|
|
2875
|
+
if (action.newGridId)
|
|
2876
|
+
skippedUpdateSlotIds.add(action.newGridId);
|
|
2877
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] Skipping ${orderType} UPDATE for ${action.id} -> ${action.newGridId}: ` +
|
|
2878
|
+
`gap-evacuation source unresolvable from master grid (fail-closed)`, 'warn');
|
|
2879
|
+
continue;
|
|
2880
|
+
}
|
|
2881
|
+
let sidePrecision = null;
|
|
2882
|
+
try {
|
|
2883
|
+
sidePrecision = getPrecisionByOrderType(bot.manager.assets, orderType);
|
|
2884
|
+
}
|
|
2885
|
+
catch {
|
|
2886
|
+
sidePrecision = null;
|
|
2887
|
+
}
|
|
2888
|
+
// Geometry re-proof under the LIVE boundary: if the
|
|
2889
|
+
// source is no longer in-band (or dest no longer rail),
|
|
2890
|
+
// this is not an evacuation anymore — fall through to
|
|
2891
|
+
// the normal guard instead of bypassing via the probe.
|
|
2892
|
+
// Non-finite live geometry cannot disprove; probe proceeds.
|
|
2893
|
+
if (isEvacuationStampStillValid(bot.manager?.boundaryIdx, bot.manager?._gapSlots, action.id, action.newGridId, orderType)) {
|
|
2894
|
+
const evacCheck = isEvacuationRotationAllowed(oldPrice, oldSize, newPrice, newSize, orderType, sidePrecision);
|
|
2895
|
+
if (evacCheck.allowed) {
|
|
2896
|
+
bypassedEvacuation = true;
|
|
2897
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] Allowing ${orderType} evacuation UPDATE for ${action.id} -> ${action.newGridId} at ${Format.formatPrice6(newPrice)}: ` +
|
|
2898
|
+
`${evacCheck.reason} (probed live: ${Format.formatPrice6(oldPrice)} -> ${Format.formatPrice6(newPrice)})`, 'warn');
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
else {
|
|
2902
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] Unstamped evacuation probe for ${action.id} -> ${action.newGridId} rejected: ` +
|
|
2903
|
+
`source/dest no longer evacuation geometry under live boundary ${bot.manager?.boundaryIdx}/gap ${bot.manager?._gapSlots} — normal guard applies`, 'warn');
|
|
2904
|
+
}
|
|
2905
|
+
// Not allowed: fall through to the normal guard below.
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
if (bypassedEvacuation) {
|
|
2909
|
+
lastFillGuardStats.bypassed++;
|
|
2910
|
+
}
|
|
2911
|
+
else {
|
|
2912
|
+
try {
|
|
2913
|
+
if (refreshLastFillPivotFromQueue(bot))
|
|
2914
|
+
lastFillGuardPivotRefreshed = true;
|
|
2915
|
+
}
|
|
2916
|
+
catch { /* best-effort */ }
|
|
2569
2917
|
const lastPrice = bot.manager?._lastFilledPrice;
|
|
2570
2918
|
const lastType = bot.manager?._lastFilledType;
|
|
2571
|
-
const inc =
|
|
2919
|
+
const inc = resolveLastFillGuardIncrement(bot);
|
|
2572
2920
|
const check = isLastFillGuardBlocked(newPrice, newSize, orderType, lastPrice, lastType, inc);
|
|
2921
|
+
lastFillGuardStats.checked++;
|
|
2573
2922
|
if (check.blocked) {
|
|
2923
|
+
lastFillGuardStats.skipped++;
|
|
2574
2924
|
skippedUpdateCount++;
|
|
2575
2925
|
if (action.id)
|
|
2576
2926
|
skippedUpdateSlotIds.add(action.id);
|
|
@@ -2580,6 +2930,7 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2580
2930
|
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)})`, 'warn');
|
|
2581
2931
|
continue;
|
|
2582
2932
|
}
|
|
2933
|
+
lastFillGuardStats.passed++;
|
|
2583
2934
|
}
|
|
2584
2935
|
}
|
|
2585
2936
|
catch (_e) { /* best-effort */ }
|
|
@@ -2693,6 +3044,31 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2693
3044
|
`${crossedOrderLabel(fbCrossed)}; re-planned after its cancel confirms.`, 'warn');
|
|
2694
3045
|
continue;
|
|
2695
3046
|
}
|
|
3047
|
+
// LAST-FILL GUARD (fallback variant): this CREATE
|
|
3048
|
+
// replaces a rotation UPDATE at a repriced level,
|
|
3049
|
+
// so it obeys the same guard with a refreshed
|
|
3050
|
+
// pivot — no origin bypass, same as rotations.
|
|
3051
|
+
try {
|
|
3052
|
+
if (refreshLastFillPivotFromQueue(bot))
|
|
3053
|
+
lastFillGuardPivotRefreshed = true;
|
|
3054
|
+
}
|
|
3055
|
+
catch { /* best-effort */ }
|
|
3056
|
+
try {
|
|
3057
|
+
const fbInc = resolveLastFillGuardIncrement(bot);
|
|
3058
|
+
const fbCheck = isLastFillGuardBlocked(fbPrice, fbSize, fbType, bot.manager?._lastFilledPrice, bot.manager?._lastFilledType, fbInc);
|
|
3059
|
+
lastFillGuardStats.checked++;
|
|
3060
|
+
if (fbCheck.blocked) {
|
|
3061
|
+
lastFillGuardStats.skipped++;
|
|
3062
|
+
const fbDir = fbType === ORDER_TYPES.BUY ? 'above' : 'below';
|
|
3063
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] Skipping CREATE fallback for ${targetSlotId} at ` +
|
|
3064
|
+
`${Format.formatPrice6(fbPrice)}: ${fbDir} last filled ` +
|
|
3065
|
+
`${Format.formatPrice6(fbCheck.pivot)} (halfInc ${fbCheck.halfInc}% thr ` +
|
|
3066
|
+
`${Format.formatPrice6(fbCheck.threshold)}); re-planned after market moves`, 'warn');
|
|
3067
|
+
continue;
|
|
3068
|
+
}
|
|
3069
|
+
lastFillGuardStats.passed++;
|
|
3070
|
+
}
|
|
3071
|
+
catch (_fbGuardErr) { /* guard is best-effort */ }
|
|
2696
3072
|
const fbArgs = buildCreateOrderArgs({ type: fbType, size: fbSize, price: fbPrice }, assetA, assetB);
|
|
2697
3073
|
const fbResult = await chainOrders.buildCreateOrderOp(bot.account, fbArgs.amountToSell, fbArgs.sellAssetId, fbArgs.minToReceive, fbArgs.receiveAssetId, null);
|
|
2698
3074
|
if (fbResult) {
|
|
@@ -2723,6 +3099,39 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2723
3099
|
}
|
|
2724
3100
|
}
|
|
2725
3101
|
}
|
|
3102
|
+
// Batch-level LAST-FILL-GUARD summary: per-action pass lines would spam
|
|
3103
|
+
// big batches, so one line per batch records the mode, pivot, resolved
|
|
3104
|
+
// increment, and pass/skip/bypass counts — the guard's pass decisions
|
|
3105
|
+
// are what incident reconstruction needs. Origin folds in here as
|
|
3106
|
+
// mode=bypassed(<origin>); there is no second source of truth.
|
|
3107
|
+
// Cold (guard off) is warn, not info — a disabled guard must say so.
|
|
3108
|
+
// pivotRefreshed surfaces whether a mid-broadcast queued fill moved the
|
|
3109
|
+
// pivot under this batch's checks (the refresh itself stays debug).
|
|
3110
|
+
// Note the printed pivot is end-of-batch state: when pivotRefreshed is
|
|
3111
|
+
// true, early actions were checked against the older pivot.
|
|
3112
|
+
try {
|
|
3113
|
+
const totalGuarded = lastFillGuardStats.checked + lastFillGuardStats.bypassed;
|
|
3114
|
+
if (totalGuarded > 0) {
|
|
3115
|
+
const sumPivotRaw = bot.manager?._lastFilledPrice;
|
|
3116
|
+
const sumType = bot.manager?._lastFilledType;
|
|
3117
|
+
const sumInc = resolveLastFillGuardIncrement(bot);
|
|
3118
|
+
const cold = sumPivotRaw == null || !Number.isFinite(Number(sumPivotRaw)) || sumType == null;
|
|
3119
|
+
const batchOrigin = cowResult?.origin;
|
|
3120
|
+
const mode = cold
|
|
3121
|
+
? 'disabled(cold)'
|
|
3122
|
+
: (lastFillGuardStats.bypassed > 0 && lastFillGuardStats.checked === 0)
|
|
3123
|
+
? `bypassed(${batchOrigin || 'unknown'})`
|
|
3124
|
+
: (lastFillGuardStats.bypassed > 0
|
|
3125
|
+
? `active+bypassed(${batchOrigin || 'unknown'})`
|
|
3126
|
+
: 'active');
|
|
3127
|
+
const pivotStr = cold ? 'none' : `${Format.formatPrice6(Number(sumPivotRaw))}(${sumType})`;
|
|
3128
|
+
bot.manager.logger.log(`[LAST-FILL-GUARD] mode=${mode} pivot=${pivotStr} inc=${sumInc}% ` +
|
|
3129
|
+
`pivotRefreshed=${lastFillGuardPivotRefreshed} ` +
|
|
3130
|
+
`checked=${lastFillGuardStats.checked} passed=${lastFillGuardStats.passed} ` +
|
|
3131
|
+
`skipped=${lastFillGuardStats.skipped} bypassed=${lastFillGuardStats.bypassed}`, cold ? 'warn' : 'info');
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
catch { /* summary is best-effort */ }
|
|
2726
3135
|
if (skippedUpdateCount > 0) {
|
|
2727
3136
|
restoreSkippedUpdateSlotsInWorkingGrid(bot, workingGrid, skippedUpdateSlotIds, skippedUpdateCount);
|
|
2728
3137
|
}
|
|
@@ -3470,7 +3879,12 @@ async function processBatchResults(bot, result, opContexts) {
|
|
|
3470
3879
|
});
|
|
3471
3880
|
}
|
|
3472
3881
|
}
|
|
3473
|
-
|
|
3882
|
+
// Success line mirrors the failure-path fingerprint below
|
|
3883
|
+
// (type/price/size) so both are greppable by the same keys —
|
|
3884
|
+
// this is what ties a fill back to the slot that placed it.
|
|
3885
|
+
bot.manager.logger.log(`Placed ${ctx.order.type} order ${ctx.order.id} ` +
|
|
3886
|
+
`@${Format.formatPrice6(ctx.order.price)} x${Format.formatAmount(ctx.order.size)} ` +
|
|
3887
|
+
`-> ${chainOrderId}`, 'info');
|
|
3474
3888
|
}
|
|
3475
3889
|
else {
|
|
3476
3890
|
const fingerprint = [
|
|
@@ -3571,7 +3985,7 @@ async function processBatchResults(bot, result, opContexts) {
|
|
|
3571
3985
|
updateOperationCount
|
|
3572
3986
|
};
|
|
3573
3987
|
}
|
|
3574
|
-
export { isLastFillGuardBlocked, 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 };
|
|
3988
|
+
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 };
|
|
3575
3989
|
export default {
|
|
3576
3990
|
buildOutsideInPairGroupsForOrders,
|
|
3577
3991
|
buildOutsideInPairGroupsForCreateEntries,
|