dexbot 1.5.0 → 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 +26 -0
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/trend_detection/package.json +1 -1
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/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/constants.d.ts +2 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +12 -0
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +1 -0
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +18 -1
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +223 -32
- package/dist/modules/dexbot_cow_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/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/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +21 -16
- 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/manager.d.ts +41 -1
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +144 -15
- 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 +58 -8
- 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 +14 -0
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +51 -3
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/validate.d.ts +29 -2
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +165 -10
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/docs/DEXBOT_COMPARISON.md +3 -3
- package/docs/EVOLUTION.md +6 -5
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +3 -1
- package/docs/GRID_RECONCILE.md +1 -0
- package/docs/LOGGING.md +3 -0
- package/docs/README.md +2 -2
- package/package.json +1 -1
|
@@ -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))
|
|
@@ -1446,7 +1476,13 @@ function validateOrderSizeForExecution(bot, size, type, orderLike = null, fallba
|
|
|
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
1478
|
* Cold (pivot null or lastType null) => disabled. Spread-correction CREATES
|
|
1449
|
-
* bypass per-action (see broadcast sites);
|
|
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).
|
|
1450
1486
|
* @param {number} price - Target order price
|
|
1451
1487
|
* @param {number} size - Order size (unused, kept for compat)
|
|
1452
1488
|
* @param {string} type - ORDER_TYPES.BUY/SELL
|
|
@@ -1612,7 +1648,49 @@ function buildActionsFromPlan(_bot, plan) {
|
|
|
1612
1648
|
// inherit the spread-correction bypass. Actions built outside this helper
|
|
1613
1649
|
// carry no origin and default to guarded (safe default).
|
|
1614
1650
|
const planOrigin = normalizedPlan?.origin;
|
|
1615
|
-
|
|
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
|
+
};
|
|
1616
1694
|
const actions = [];
|
|
1617
1695
|
for (const o of ordersToCancel) {
|
|
1618
1696
|
if (o?.orderId) {
|
|
@@ -1646,7 +1724,7 @@ function buildActionsFromPlan(_bot, plan) {
|
|
|
1646
1724
|
price: newPrice,
|
|
1647
1725
|
size: newSize
|
|
1648
1726
|
}
|
|
1649
|
-
}));
|
|
1727
|
+
}, oldOrder));
|
|
1650
1728
|
}
|
|
1651
1729
|
for (const o of ordersToUpdate) {
|
|
1652
1730
|
const partialOrder = o?.partialOrder || o;
|
|
@@ -1697,12 +1775,31 @@ function buildCowResultFromPlan(bot, plan) {
|
|
|
1697
1775
|
? requestedBoundary
|
|
1698
1776
|
: bot.manager.boundaryIdx;
|
|
1699
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
|
+
};
|
|
1700
1797
|
for (const action of actions) {
|
|
1701
1798
|
if (action.type === COW_ACTIONS.CANCEL) {
|
|
1702
1799
|
const current = workingGrid.get(action.id);
|
|
1703
1800
|
if (!current)
|
|
1704
1801
|
continue;
|
|
1705
|
-
workingGrid.set(action.id,
|
|
1802
|
+
workingGrid.set(action.id, toWorkingHole(current));
|
|
1706
1803
|
}
|
|
1707
1804
|
else if (action.type === COW_ACTIONS.CREATE) {
|
|
1708
1805
|
if (!action.id || !action.order)
|
|
@@ -1720,7 +1817,7 @@ function buildCowResultFromPlan(bot, plan) {
|
|
|
1720
1817
|
if (action.newGridId && action.newGridId !== action.id) {
|
|
1721
1818
|
const current = workingGrid.get(action.id);
|
|
1722
1819
|
if (current) {
|
|
1723
|
-
workingGrid.set(action.id,
|
|
1820
|
+
workingGrid.set(action.id, toWorkingHole(current));
|
|
1724
1821
|
}
|
|
1725
1822
|
const targetId = action.newGridId;
|
|
1726
1823
|
const targetCurrent = workingGrid.get(targetId) || { id: targetId };
|
|
@@ -1781,7 +1878,11 @@ function applyRotationTransitionsToWorkingGrid(bot, workingGrid, executedContext
|
|
|
1781
1878
|
continue;
|
|
1782
1879
|
const { rotation } = ctx;
|
|
1783
1880
|
const { oldOrder, newGridId, newPrice, newSize, type } = rotation;
|
|
1784
|
-
// 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.
|
|
1785
1886
|
if (oldOrder?.id && oldOrder.id !== newGridId) {
|
|
1786
1887
|
const sourceSlot = workingGrid.get(oldOrder.id);
|
|
1787
1888
|
if (sourceSlot && sourceSlot.orderId) {
|
|
@@ -2712,35 +2813,125 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult, options = {}) {
|
|
|
2712
2813
|
`${crossedOrderLabel(crossedOrder)}; re-planned after its cancel confirms.`, 'warn');
|
|
2713
2814
|
continue;
|
|
2714
2815
|
}
|
|
2715
|
-
// LAST-FILL PRICE GUARD (UPDATE rotation): pivot ± halfIncrement — same as CREATE
|
|
2716
|
-
//
|
|
2717
|
-
//
|
|
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).
|
|
2718
2834
|
// The pivot is refreshed from still-queued fills first: fills detected mid-broadcast
|
|
2719
2835
|
// sit in _incomingFillQueue until the fill cycle ends, and without this the later
|
|
2720
2836
|
// chunks of a long broadcast would be checked against a stale pivot.
|
|
2721
2837
|
try {
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
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
|
+
}
|
|
2725
2907
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2908
|
+
if (bypassedEvacuation) {
|
|
2909
|
+
lastFillGuardStats.bypassed++;
|
|
2910
|
+
}
|
|
2911
|
+
else {
|
|
2912
|
+
try {
|
|
2913
|
+
if (refreshLastFillPivotFromQueue(bot))
|
|
2914
|
+
lastFillGuardPivotRefreshed = true;
|
|
2915
|
+
}
|
|
2916
|
+
catch { /* best-effort */ }
|
|
2917
|
+
const lastPrice = bot.manager?._lastFilledPrice;
|
|
2918
|
+
const lastType = bot.manager?._lastFilledType;
|
|
2919
|
+
const inc = resolveLastFillGuardIncrement(bot);
|
|
2920
|
+
const check = isLastFillGuardBlocked(newPrice, newSize, orderType, lastPrice, lastType, inc);
|
|
2921
|
+
lastFillGuardStats.checked++;
|
|
2922
|
+
if (check.blocked) {
|
|
2923
|
+
lastFillGuardStats.skipped++;
|
|
2924
|
+
skippedUpdateCount++;
|
|
2925
|
+
if (action.id)
|
|
2926
|
+
skippedUpdateSlotIds.add(action.id);
|
|
2927
|
+
if (action.newGridId)
|
|
2928
|
+
skippedUpdateSlotIds.add(action.newGridId);
|
|
2929
|
+
const dir = orderType === ORDER_TYPES.BUY ? 'above' : 'below';
|
|
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');
|
|
2931
|
+
continue;
|
|
2932
|
+
}
|
|
2933
|
+
lastFillGuardStats.passed++;
|
|
2742
2934
|
}
|
|
2743
|
-
lastFillGuardStats.passed++;
|
|
2744
2935
|
}
|
|
2745
2936
|
catch (_e) { /* best-effort */ }
|
|
2746
2937
|
const { amountToSell, minToReceive } = buildCreateOrderArgs({ type: orderType, size: newSize, price: newPrice }, assetA, assetB);
|