dexbot 1.4.9 → 1.4.11
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/README.md +3 -3
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +43 -9
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/core/market_adapter_service.d.ts +2 -0
- package/dist/market_adapter/core/market_adapter_service.d.ts.map +1 -1
- package/dist/market_adapter/core/market_adapter_service.js +9 -0
- package/dist/market_adapter/core/market_adapter_service.js.map +1 -1
- package/dist/market_adapter/market_adapter.d.ts.map +1 -1
- package/dist/market_adapter/market_adapter.js +6 -1
- package/dist/market_adapter/market_adapter.js.map +1 -1
- package/dist/modules/broadcast_failure.d.ts +0 -1
- package/dist/modules/broadcast_failure.d.ts.map +1 -1
- package/dist/modules/broadcast_failure.js +0 -1
- package/dist/modules/broadcast_failure.js.map +1 -1
- package/dist/modules/chain_keys.d.ts.map +1 -1
- package/dist/modules/chain_keys.js +19 -1
- package/dist/modules/chain_keys.js.map +1 -1
- package/dist/modules/constants.d.ts +2 -5
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +8 -5
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credential_policy.d.ts.map +1 -1
- package/dist/modules/credential_policy.js +135 -364
- package/dist/modules/credential_policy.js.map +1 -1
- package/dist/modules/daemon_node_health.d.ts +0 -1
- package/dist/modules/daemon_node_health.d.ts.map +1 -1
- package/dist/modules/daemon_node_health.js +0 -1
- package/dist/modules/daemon_node_health.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +7 -1
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +14 -5
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +27 -27
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +32 -14
- 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 +4 -11
- 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 -7
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/key_store.d.ts.map +1 -1
- package/dist/modules/key_store.js +42 -60
- package/dist/modules/key_store.js.map +1 -1
- 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 +6 -26
- package/dist/modules/launcher/bot_supervisor.js.map +1 -1
- package/dist/modules/launcher/foreign_cred_daemon.d.ts +17 -0
- package/dist/modules/launcher/foreign_cred_daemon.d.ts.map +1 -1
- package/dist/modules/launcher/foreign_cred_daemon.js +5 -14
- package/dist/modules/launcher/foreign_cred_daemon.js.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts +2 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +34 -16
- package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
- package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
- package/dist/modules/launcher/status_reporting.js +1 -11
- package/dist/modules/launcher/status_reporting.js.map +1 -1
- package/dist/modules/node_failure_ledger.d.ts +0 -1
- package/dist/modules/node_failure_ledger.d.ts.map +1 -1
- package/dist/modules/node_failure_ledger.js +0 -1
- package/dist/modules/node_failure_ledger.js.map +1 -1
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +5 -1
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/grid.d.ts +5 -5
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +236 -27
- 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 +147 -35
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/logger.d.ts +0 -8
- package/dist/modules/order/logger.d.ts.map +1 -1
- package/dist/modules/order/logger.js +1 -73
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts +1 -80
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +3 -116
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/strategy.js +2 -2
- package/dist/modules/order/strategy.js.map +1 -1
- package/dist/modules/order/sync_engine.js +4 -4
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +38 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +60 -0
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +45 -1
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +84 -0
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +5 -2
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/process_discovery.d.ts +1 -1
- package/dist/modules/process_discovery.d.ts.map +1 -1
- package/dist/modules/process_discovery.js +17 -17
- package/dist/modules/process_discovery.js.map +1 -1
- package/dist/modules/socket_json_client.d.ts +0 -1
- package/dist/modules/socket_json_client.d.ts.map +1 -1
- package/dist/modules/socket_json_client.js +0 -1
- package/dist/modules/socket_json_client.js.map +1 -1
- package/dist/modules/types.d.ts +0 -600
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/modules/types.js +15 -0
- package/dist/modules/types.js.map +1 -1
- package/dist/scripts/run-tests.js +185 -18
- package/dist/scripts/run-tests.js.map +1 -1
- package/dist/scripts/update.js +27 -17
- package/dist/scripts/update.js.map +1 -1
- package/dist/unlock.d.ts.map +1 -1
- package/dist/unlock.js +170 -86
- package/dist/unlock.js.map +1 -1
- package/package.json +1 -1
|
@@ -385,9 +385,13 @@ function createOrderGrid(config) {
|
|
|
385
385
|
if (!Number.isFinite(incrementPercent)) {
|
|
386
386
|
throw new Error(`Invalid incrementPercent: ${incrementPercent}. Must be a finite number.`);
|
|
387
387
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
388
|
+
// Fall back to the canonical INCREMENT_BOUNDS when the config omits
|
|
389
|
+
// incrementBounds. Without this, a non-positive incrementPercent (e.g. 0)
|
|
390
|
+
// silently passes validation and the geometric loop below spins forever.
|
|
391
|
+
const incrementBounds = config.incrementBounds || constants_1.INCREMENT_BOUNDS;
|
|
392
|
+
const minPercent = incrementBounds.MIN_PERCENT;
|
|
393
|
+
const maxPercent = incrementBounds.MAX_PERCENT;
|
|
394
|
+
if (incrementPercent <= 0 || incrementPercent < minPercent || incrementPercent > maxPercent) {
|
|
391
395
|
throw new Error(`Invalid incrementPercent: ${incrementPercent}. Must be between ` +
|
|
392
396
|
`${minPercent} and ${maxPercent} (inclusive).`);
|
|
393
397
|
}
|
|
@@ -544,6 +548,25 @@ async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
544
548
|
: (i >= sellStartIdx)
|
|
545
549
|
? constants_1.ORDER_TYPES.SELL
|
|
546
550
|
: constants_1.ORDER_TYPES.SPREAD;
|
|
551
|
+
// DEFENSIVE BACKSTOP: a VIRTUAL slot with no orderId and
|
|
552
|
+
// zero size is side-neutral — it is a reusable placeholder
|
|
553
|
+
// that may be activated on either rail. Storing a stale
|
|
554
|
+
// BUY/SELL type here misleads candidate-selection code
|
|
555
|
+
// (e.g. spread-correction orphaned filters and reconcile
|
|
556
|
+
// activation), which pick by stored type instead of boundary
|
|
557
|
+
// geometry. Force SPREAD so the stored type can never
|
|
558
|
+
// pre-bias which side reuses the slot. The VIRTUAL +
|
|
559
|
+
// !orderId + size-0 combination already implies not on-chain
|
|
560
|
+
// (on-chain requires ACTIVE/PARTIAL and an orderId). This is
|
|
561
|
+
// a defensive backstop for legacy persisted grids; the
|
|
562
|
+
// boundary-shift and strategy re-plan paths use
|
|
563
|
+
// assignGridRoles (order.ts) with assignOnChain, where
|
|
564
|
+
// geometry-based typing wins.
|
|
565
|
+
if ((0, order_1.isEmptyGridSlot)(slot, slot, { allowNullType: true })) {
|
|
566
|
+
if (slot.type !== constants_1.ORDER_TYPES.SPREAD)
|
|
567
|
+
reassignCount++;
|
|
568
|
+
return { ...slot, type: constants_1.ORDER_TYPES.SPREAD };
|
|
569
|
+
}
|
|
547
570
|
if (slot.type !== correctType) {
|
|
548
571
|
let newType = correctType;
|
|
549
572
|
// On-chain slots must never be reassigned to SPREAD:
|
|
@@ -606,8 +629,20 @@ async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
606
629
|
}
|
|
607
630
|
await manager._applyOrderUpdate(currentOrder, 'grid-load', { skipAccounting: true });
|
|
608
631
|
}
|
|
609
|
-
|
|
610
|
-
|
|
632
|
+
// Gap-band occupancy for the spread metric. Empty slots are
|
|
633
|
+
// normalized to SPREAD (side-neutral) above, so a raw
|
|
634
|
+
// `type === SPREAD` count would include every empty slot on both
|
|
635
|
+
// rails. countGapBandSpread requires both SPREAD type and band
|
|
636
|
+
// geometry (target = gapSlots).
|
|
637
|
+
// Index resolution: prefer the parsed slot id (price-monotonic
|
|
638
|
+
// at creation) so the count is order-independent, matching the
|
|
639
|
+
// accountant (accounting.ts). Fall back to array position only
|
|
640
|
+
// for ids that are not grid slot ids.
|
|
641
|
+
const spreadCount = (0, math_1.countGapBandSpread)(manager, grid, (o, i) => {
|
|
642
|
+
const idx = (0, order_1.parseSlotIndex)(o?.id);
|
|
643
|
+
return idx === null ? i : idx;
|
|
644
|
+
});
|
|
645
|
+
manager.initialSpreadCount = spreadCount;
|
|
611
646
|
manager.currentSpreadCount = spreadCount;
|
|
612
647
|
}
|
|
613
648
|
finally {
|
|
@@ -617,12 +652,21 @@ async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
617
652
|
manager.logger?.log?.(`Loaded ${manager.orders.size} orders from persisted grid.`, 'info');
|
|
618
653
|
});
|
|
619
654
|
}
|
|
655
|
+
function resolveMinScaleSlots(primary, secondary) {
|
|
656
|
+
const p = Number(primary);
|
|
657
|
+
if (Number.isFinite(p))
|
|
658
|
+
return Math.max(0, Math.floor(p));
|
|
659
|
+
const s = Number(secondary);
|
|
660
|
+
if (Number.isFinite(s))
|
|
661
|
+
return Math.max(0, Math.floor(s));
|
|
662
|
+
return constants_1.MARKET_ADAPTER.ASYMMETRIC_BOUNDS_MIN_SCALE_SLOTS;
|
|
663
|
+
}
|
|
620
664
|
/**
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
665
|
+
* Initialize and orchestrate the order grid.
|
|
666
|
+
*
|
|
667
|
+
* @return {Promise<void>}
|
|
668
|
+
* @throws {Error} If initialization fails or account totals are missing.
|
|
669
|
+
*/
|
|
626
670
|
async function initializeGrid(manager) {
|
|
627
671
|
if (!manager)
|
|
628
672
|
throw new Error('initializeGrid requires a manager instance');
|
|
@@ -723,6 +767,8 @@ async function initializeGrid(manager) {
|
|
|
723
767
|
let resolvedMinP = minP;
|
|
724
768
|
let resolvedMaxP = maxP;
|
|
725
769
|
let rangeScalingFactor = null;
|
|
770
|
+
let appliedTrend = null;
|
|
771
|
+
let minScaleSlots = null;
|
|
726
772
|
if (gpSource === 'ama' && Number.isFinite(minP) && Number.isFinite(maxP)
|
|
727
773
|
&& isGridRangeScalingWhitelisted) {
|
|
728
774
|
const dw = amaSnapshot?.dynamicWeights;
|
|
@@ -747,6 +793,8 @@ async function initializeGrid(manager) {
|
|
|
747
793
|
resolvedMinP = adjustment.resolvedMinPrice;
|
|
748
794
|
resolvedMaxP = adjustment.resolvedMaxPrice;
|
|
749
795
|
rangeScalingFactor = Number(adjustment.appliedAsymmetryFactor);
|
|
796
|
+
appliedTrend = (dw?.trend === 'UP' || dw?.trend === 'DOWN') ? dw.trend : null;
|
|
797
|
+
minScaleSlots = resolveMinScaleSlots(manager.config.asymmetricBounds?.minScaleSlots, dw?.minScaleSlots);
|
|
750
798
|
manager.logger?.log?.(`[BOUND-ASYMMETRY] trend=${dw.trend} slopeOffset=${dw.slopeOffset.toFixed(4)} `
|
|
751
799
|
+ `raw=${((adjustment.rawAsymmetryFactor ?? 0) * 100).toFixed(1)}% `
|
|
752
800
|
+ `cap=${((maxAsymmetryFactor ?? 0) * 100).toFixed(0)}% `
|
|
@@ -768,6 +816,8 @@ async function initializeGrid(manager) {
|
|
|
768
816
|
resolvedMaxP = gp * ((maxP / gp) * (1 + asymmetry));
|
|
769
817
|
}
|
|
770
818
|
rangeScalingFactor = asymmetry;
|
|
819
|
+
appliedTrend = rootTrend;
|
|
820
|
+
minScaleSlots = resolveMinScaleSlots(manager.config.asymmetricBounds?.minScaleSlots, rootBounds.minScaleSlots);
|
|
771
821
|
manager.logger?.log?.(`[BOUND-ASYMMETRY] trend=${rootTrend} `
|
|
772
822
|
+ `asymmetry=${(asymmetry * 100).toFixed(1)}% `
|
|
773
823
|
+ `(root-level) min ${(minP ?? 0).toFixed(8)}→${(resolvedMinP ?? 0).toFixed(8)} `
|
|
@@ -796,6 +846,35 @@ async function initializeGrid(manager) {
|
|
|
796
846
|
gridStartPrice = clamped;
|
|
797
847
|
}
|
|
798
848
|
}
|
|
849
|
+
// Narrowing-side slot guard: range scaling tightens one bound toward the
|
|
850
|
+
// center. Without a floor this can collapse that side into a near-center
|
|
851
|
+
// sliver holding few or zero active orders. Guarantee at least
|
|
852
|
+
// minScaleSlots price levels remain between the grid center and the
|
|
853
|
+
// tightened bound (in multiples of incrementPercent). The widened side
|
|
854
|
+
// still extends freely.
|
|
855
|
+
if (appliedTrend && Number.isFinite(gridStartPrice) && gridStartPrice > 0) {
|
|
856
|
+
const inc = Number(manager.config.incrementPercent);
|
|
857
|
+
const mss = Number.isFinite(minScaleSlots) ? Math.floor(Number(minScaleSlots)) : 0;
|
|
858
|
+
if (Number.isFinite(inc) && inc > 0 && mss > 0) {
|
|
859
|
+
const stepMult = 1 + (inc / 100);
|
|
860
|
+
if (appliedTrend === 'DOWN' && resolvedMaxP != null) {
|
|
861
|
+
const keepAbove = gridStartPrice * Math.pow(stepMult, mss);
|
|
862
|
+
if (resolvedMaxP < keepAbove) {
|
|
863
|
+
manager.logger?.log?.(`[BOUND-ASYMMETRY] narrowing-side guard: max ${resolvedMaxP.toFixed(8)} collapses ` +
|
|
864
|
+
`${mss} levels; holding at ${keepAbove.toFixed(8)}`, 'info');
|
|
865
|
+
resolvedMaxP = keepAbove;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
else if (appliedTrend === 'UP' && resolvedMinP != null) {
|
|
869
|
+
const belowMin = gridStartPrice * Math.pow(1 - (inc / 100), mss);
|
|
870
|
+
if (resolvedMinP > belowMin) {
|
|
871
|
+
manager.logger?.log?.(`[BOUND-ASYMMETRY] narrowing-side guard would pull min ${resolvedMinP.toFixed(8)} ` +
|
|
872
|
+
`short of ${mss} levels; holding at ${belowMin.toFixed(8)}`, 'info');
|
|
873
|
+
resolvedMinP = belowMin;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
799
878
|
manager.config.minPrice = resolvedMinP;
|
|
800
879
|
manager.config.maxPrice = resolvedMaxP;
|
|
801
880
|
manager._lastGridPricingContext = {
|
|
@@ -882,9 +961,14 @@ async function initializeGrid(manager) {
|
|
|
882
961
|
await manager.resumeFundRecalc();
|
|
883
962
|
manager.resumeRecalcLogging();
|
|
884
963
|
}
|
|
885
|
-
// RC-6: Spread count updates protected by grid lock
|
|
886
|
-
|
|
887
|
-
|
|
964
|
+
// RC-6: Spread count updates protected by grid lock.
|
|
965
|
+
// initializeGrid always sets initialSpreadCount = gapSlots (theoretical
|
|
966
|
+
// value for a fresh grid). loadGrid uses the actual SPREAD-typed slot
|
|
967
|
+
// count in the gap band, which may be lower after promotions absorbed
|
|
968
|
+
// gap slots into a rail. Both are correct for their context; callers
|
|
969
|
+
// should not assume the two paths agree.
|
|
970
|
+
manager.initialSpreadCount = initialSpreadCount.buy + initialSpreadCount.sell;
|
|
971
|
+
manager.currentSpreadCount = manager.initialSpreadCount;
|
|
888
972
|
});
|
|
889
973
|
// FIX: Use consistent optional chaining pattern for all logger calls
|
|
890
974
|
manager.logger?.log?.(`Initialized grid with ${orders.length} orders.`, 'info');
|
|
@@ -1113,6 +1197,19 @@ async function _recalculateGridOrderSizesFromBlockchain(manager, orderType, opti
|
|
|
1113
1197
|
// correct side's size calculation — using manager.orders here would
|
|
1114
1198
|
// filter by stale pre-shift types and miss the crossers, producing a
|
|
1115
1199
|
// budget allocation that doesn't match the post-shift grid structure.
|
|
1200
|
+
//
|
|
1201
|
+
// Empty (size-0 VIRTUAL) slots are stored SPREAD (side-neutral) after
|
|
1202
|
+
// normalization and are deliberately NOT included in the side's
|
|
1203
|
+
// ideal-size denominator here: a SPREAD slot cannot carry a size
|
|
1204
|
+
// (validateOrder forces SPREAD size back to 0), so including it would
|
|
1205
|
+
// only dilute the budget without giving the slot a usable size.
|
|
1206
|
+
// Activation sizing for empties is handled where re-typing happens:
|
|
1207
|
+
// spread-correction (prepareSpreadCorrectionOrders) and startup
|
|
1208
|
+
// reconcile (_deriveBudgetedSideSizes) both compute the full in-rail
|
|
1209
|
+
// geometric progression including empties, then re-type the picked slot
|
|
1210
|
+
// to BUY/SELL before placement. The COW boundary-shift path re-types
|
|
1211
|
+
// the working grid by geometry first, so crossers stay in the correct
|
|
1212
|
+
// side's denominator.
|
|
1116
1213
|
const orderSource = collectActions ? workingGrid : manager.orders;
|
|
1117
1214
|
const allSideSlots = Array.from(orderSource.values())
|
|
1118
1215
|
.filter((o) => o.type === orderType)
|
|
@@ -1903,6 +2000,36 @@ function determineOrderSideByFunds(manager, currentMarketPrice) {
|
|
|
1903
2000
|
}
|
|
1904
2001
|
return { side, reason: side ? `Choosing ${side}` : 'Insufficient funds or committed inventory' };
|
|
1905
2002
|
}
|
|
2003
|
+
// Collect contiguous empty gap-band slots adjacent to a rail edge for boundary
|
|
2004
|
+
// promotion. BUY walks upward from the boundary into the gap, SELL walks
|
|
2005
|
+
// downward from the sell start; both stop at the first unavailable slot.
|
|
2006
|
+
// Mirrors are collapsed into one directional walk (step +/- 1).
|
|
2007
|
+
//
|
|
2008
|
+
// NOTE: the promotion cap is the OPPOSITE RAIL's span, NOT the gap-band size.
|
|
2009
|
+
// SELL promotion moves the boundary down by the promoted count (boundary =
|
|
2010
|
+
// buyEndIdx - count), so capping at buyEndIdx keeps it >= 0. BUY promotion
|
|
2011
|
+
// moves the boundary up; capping at the SELL rail size (maxIdx+1-sellStartIdx)
|
|
2012
|
+
// keeps the promoted boundary at or below the last placed slot. The walk's
|
|
2013
|
+
// own idx < sellStartIdx / idx > buyEndIdx bounds already limit promotion to
|
|
2014
|
+
// the band, so this cap only binds when the opposite rail is smaller than the
|
|
2015
|
+
// band — i.e. an empty opposite rail (cap 0) blocks promotion entirely.
|
|
2016
|
+
function _collectPromotableBoundarySlots(allSlotsByPrice, railType, buyEndIdx, sellStartIdx, quota) {
|
|
2017
|
+
const isBuy = railType === constants_1.ORDER_TYPES.BUY;
|
|
2018
|
+
const maxIdx = allSlotsByPrice.length - 1;
|
|
2019
|
+
const maxPromotable = isBuy
|
|
2020
|
+
? Math.max(0, maxIdx + 1 - sellStartIdx)
|
|
2021
|
+
: Math.max(0, buyEndIdx);
|
|
2022
|
+
const promotionQuota = Math.min(quota, maxPromotable);
|
|
2023
|
+
const promoted = [];
|
|
2024
|
+
const step = isBuy ? 1 : -1;
|
|
2025
|
+
for (let idx = isBuy ? buyEndIdx + 1 : sellStartIdx - 1; promoted.length < promotionQuota && (isBuy ? idx < sellStartIdx : idx > buyEndIdx); idx += step) {
|
|
2026
|
+
const slot = allSlotsByPrice[idx];
|
|
2027
|
+
if (!slot || !(0, order_1.isSlotAvailable)(slot))
|
|
2028
|
+
break;
|
|
2029
|
+
promoted.push(slot);
|
|
2030
|
+
}
|
|
2031
|
+
return promoted;
|
|
2032
|
+
}
|
|
1906
2033
|
/**
|
|
1907
2034
|
* Prepares one or more orders to correct a wide spread.
|
|
1908
2035
|
* @param {import('./types').OrderManager} manager - The OrderManager instance.
|
|
@@ -1941,15 +2068,22 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide, outOfSpread
|
|
|
1941
2068
|
.filter((o) => o.price != null && Number.isFinite(o.price))
|
|
1942
2069
|
.sort((a, b) => a.price - b.price);
|
|
1943
2070
|
const slotIndexMap = new Map(allSlotsByPrice.map((o, i) => [o.id, i]));
|
|
1944
|
-
const gapSlots = manager._gapSlots ?? calculateGapSlots(manager.config?.incrementPercent, manager.config?.targetSpreadPercent, manager.config?.gridLimits);
|
|
1945
2071
|
// Use the committed boundary for slot classification — never a speculative
|
|
1946
2072
|
// value from syncBoundaryToFunds that hasn't been persisted through the COW
|
|
1947
2073
|
// pipeline. If the boundary shifts later via _commitWorkingGrid, the next
|
|
1948
2074
|
// spread correction cycle will re-classify with the updated committed value.
|
|
1949
2075
|
// This prevents TOCTOU-style inconsistency where slot types are chosen
|
|
1950
2076
|
// against a boundary that was never atomically committed to manager.orders.
|
|
1951
|
-
const
|
|
1952
|
-
const
|
|
2077
|
+
const resolved = (0, math_1.resolveGapBand)(manager);
|
|
2078
|
+
const gapSlots = resolved.gapSlots;
|
|
2079
|
+
const boundaryKnown = resolved.boundaryIdx !== null && resolved.sellStartIdx !== null;
|
|
2080
|
+
// NOTE: `?? 0` keeps the legacy classification fallback (boundary not
|
|
2081
|
+
// restored yet → treat as bottom of the rail) so the correction can still
|
|
2082
|
+
// act on concrete BUY/SELL candidates. Boundary PROMOTION is gated on
|
|
2083
|
+
// `boundaryKnown` below: deriving a new boundary from a fabricated 0 would
|
|
2084
|
+
// silently commit a boundary that was never real.
|
|
2085
|
+
const buyEndIdx = resolved.boundaryIdx ?? 0;
|
|
2086
|
+
const sellStartIdx = resolved.sellStartIdx ?? (0, math_1.getSellStartIdx)(buyEndIdx, gapSlots);
|
|
1953
2087
|
const getSlotCorrectType = (slot) => {
|
|
1954
2088
|
const idx = slotIndexMap.get(slot.id);
|
|
1955
2089
|
if (idx === undefined)
|
|
@@ -1979,30 +2113,68 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide, outOfSpread
|
|
|
1979
2113
|
&& getSlotCorrectType(o) === railType)
|
|
1980
2114
|
.sort((a, b) => railType === constants_1.ORDER_TYPES.BUY ? a.price - b.price : b.price - a.price)
|
|
1981
2115
|
.slice(0, missingSlots);
|
|
1982
|
-
// Secondary candidates: orphaned virtual slots
|
|
1983
|
-
//
|
|
1984
|
-
// These sit inside the active window and are invisible to the
|
|
2116
|
+
// Secondary candidates: orphaned virtual slots that have lost their
|
|
2117
|
+
// order (e.g. stale-cleaned after a race condition during a crash).
|
|
2118
|
+
// These sit inside the active window and are invisible to the
|
|
2119
|
+
// gap-band SPREAD filter above.
|
|
1985
2120
|
//
|
|
1986
|
-
//
|
|
1987
|
-
//
|
|
1988
|
-
//
|
|
1989
|
-
//
|
|
1990
|
-
//
|
|
2121
|
+
// Empty slots are stored SPREAD (side-neutral) after normalization, so
|
|
2122
|
+
// the stored type can be railType OR SPREAD — what matters is that the
|
|
2123
|
+
// boundary-correct type (geometry) matches the rail being corrected.
|
|
2124
|
+
// Filter by boundary-correct type so that filled-then-virtualized slots
|
|
2125
|
+
// whose boundary position has moved into the spread or opposite zone are
|
|
2126
|
+
// NOT re-activated on the stale side — doing so would compound inventory
|
|
2127
|
+
// at prices where the bot already traded.
|
|
1991
2128
|
const orphanedVirtualCandidates = allOrders
|
|
1992
|
-
.filter((o) => o.type === railType
|
|
2129
|
+
.filter((o) => (o.type === railType || o.type === constants_1.ORDER_TYPES.SPREAD)
|
|
1993
2130
|
&& o.state === constants_1.ORDER_STATES.VIRTUAL
|
|
1994
2131
|
&& !o.orderId
|
|
1995
2132
|
&& Number(o.size || 0) === 0
|
|
1996
2133
|
&& getSlotCorrectType(o) === railType)
|
|
1997
2134
|
.sort((a, b) => railType === constants_1.ORDER_TYPES.BUY ? b.price - a.price : a.price - b.price)
|
|
1998
2135
|
.slice(0, missingSlots);
|
|
2136
|
+
// If the funded rail is full, the spread itself may be stale: the
|
|
2137
|
+
// nearest empty slots are still in the gap band. Promote contiguous
|
|
2138
|
+
// empty gap slots from that rail edge so correction can repair the
|
|
2139
|
+
// boundary and place orders in one atomic COW commit.
|
|
2140
|
+
//
|
|
2141
|
+
// Requires a KNOWN committed boundary: with a null/unknown boundary the
|
|
2142
|
+
// rail edges are fabricated (buyEndIdx=0), so promotion would place
|
|
2143
|
+
// orders at arbitrary prices and return a boundary derived from that
|
|
2144
|
+
// fiction. Without promotion there is nothing to commit, so falling
|
|
2145
|
+
// back to the pre-existing candidate paths is safe.
|
|
2146
|
+
const promotedCandidates = [];
|
|
2147
|
+
if (boundaryKnown && orphanedVirtualCandidates.length + typedSpreadCandidates.length < missingSlots) {
|
|
2148
|
+
const rawQuota = missingSlots - orphanedVirtualCandidates.length - typedSpreadCandidates.length;
|
|
2149
|
+
promotedCandidates.push(..._collectPromotableBoundarySlots(allSlotsByPrice, railType, buyEndIdx, sellStartIdx, rawQuota));
|
|
2150
|
+
if (promotedCandidates.length > 0) {
|
|
2151
|
+
manager.logger?.log?.(`[SPREAD-CORRECTION] ${promotedCandidates.length} gap slot(s) available for boundary promotion on ${sideName}`, 'info');
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
1999
2154
|
// Merge: prefer orphaned virtuals (they already occupy correct grid positions) then
|
|
2000
2155
|
// fall back to SPREAD slots for any remaining quota.
|
|
2001
2156
|
const remainingQuota = Math.max(0, missingSlots - orphanedVirtualCandidates.length);
|
|
2002
2157
|
let spreadCandidates = [
|
|
2003
2158
|
...orphanedVirtualCandidates,
|
|
2004
|
-
...typedSpreadCandidates.slice(0, remainingQuota)
|
|
2159
|
+
...typedSpreadCandidates.slice(0, remainingQuota),
|
|
2160
|
+
...promotedCandidates
|
|
2005
2161
|
];
|
|
2162
|
+
// Dedupe by slot id: an empty in-rail slot typed SPREAD (normalized) now
|
|
2163
|
+
// qualifies for BOTH orphanedVirtualCandidates and typedSpreadCandidates
|
|
2164
|
+
// (both accept SPREAD type + rail geometry), so a single slot can be
|
|
2165
|
+
// planned twice. Duplicate CREATEs would inflate the sizing denominator
|
|
2166
|
+
// (diluting every order), mislead the plan counts, and get dropped by the
|
|
2167
|
+
// COW same-batch collision filter anyway. Keep the first occurrence
|
|
2168
|
+
// (orphaned-priority — those already occupy correct grid positions).
|
|
2169
|
+
{
|
|
2170
|
+
const seenSlotIds = new Set();
|
|
2171
|
+
spreadCandidates = spreadCandidates.filter((c) => {
|
|
2172
|
+
if (!c?.id || seenSlotIds.has(c.id))
|
|
2173
|
+
return false;
|
|
2174
|
+
seenSlotIds.add(c.id);
|
|
2175
|
+
return true;
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2006
2178
|
// P2: Filter out candidates whose price already has a placed order from any slot.
|
|
2007
2179
|
// This prevents creating a duplicate order at the same price when a prior cycle's
|
|
2008
2180
|
// order was not properly cleaned up (e.g. uncertain broadcast).
|
|
@@ -2135,6 +2307,43 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide, outOfSpread
|
|
|
2135
2307
|
});
|
|
2136
2308
|
remainingBudget -= createSize;
|
|
2137
2309
|
}
|
|
2310
|
+
let boundaryIdx;
|
|
2311
|
+
const placedPromotedIds = new Set(ordersToPlace
|
|
2312
|
+
.filter((order) => promotedCandidates.some((slot) => slot.id === order.id))
|
|
2313
|
+
.map((order) => order.id));
|
|
2314
|
+
if (placedPromotedIds.size > 0) {
|
|
2315
|
+
// Derive boundary from the placed promoted set's extremes (via
|
|
2316
|
+
// slotIndexMap), not the contiguous prefix. Any promoted-but-
|
|
2317
|
+
// unplaced slots sit in-rail or in-band as normal empty slots.
|
|
2318
|
+
let maxDist = 0;
|
|
2319
|
+
for (const id of placedPromotedIds) {
|
|
2320
|
+
const idx = slotIndexMap.get(id);
|
|
2321
|
+
if (idx === undefined)
|
|
2322
|
+
continue;
|
|
2323
|
+
const dist = railType === constants_1.ORDER_TYPES.BUY
|
|
2324
|
+
? idx - buyEndIdx
|
|
2325
|
+
: sellStartIdx - idx;
|
|
2326
|
+
if (dist > maxDist)
|
|
2327
|
+
maxDist = dist;
|
|
2328
|
+
}
|
|
2329
|
+
if (maxDist > 0) {
|
|
2330
|
+
boundaryIdx = railType === constants_1.ORDER_TYPES.BUY
|
|
2331
|
+
? buyEndIdx + maxDist
|
|
2332
|
+
: buyEndIdx - maxDist;
|
|
2333
|
+
const maxIdx = allSlotsByPrice.length - 1;
|
|
2334
|
+
const clamped = Math.max(0, Math.min(maxIdx, boundaryIdx));
|
|
2335
|
+
if (clamped !== boundaryIdx) {
|
|
2336
|
+
manager.logger?.log?.(`[SPREAD-CORRECTION] Boundary promotion clamped on ${sideName}: ` +
|
|
2337
|
+
`${boundaryIdx} -> ${clamped}`, 'warn');
|
|
2338
|
+
boundaryIdx = clamped;
|
|
2339
|
+
}
|
|
2340
|
+
manager.logger?.log?.(`[SPREAD-CORRECTION] Boundary promotion on ${sideName}: ` +
|
|
2341
|
+
`${buyEndIdx} -> ${boundaryIdx} (${placedPromotedIds.size} placed, maxDist ${maxDist})`, 'info');
|
|
2342
|
+
}
|
|
2343
|
+
else {
|
|
2344
|
+
boundaryIdx = undefined;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2138
2347
|
const combinedUpdates = [...redistributionUpdates];
|
|
2139
2348
|
for (const plannedUpdate of ordersToUpdate) {
|
|
2140
2349
|
const id = plannedUpdate?.partialOrder?.id || plannedUpdate?.id;
|
|
@@ -2157,6 +2366,6 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide, outOfSpread
|
|
|
2157
2366
|
if (combinedUpdates.length > 0 || ordersToPlace.length > 0) {
|
|
2158
2367
|
manager.logger?.log?.(`[SPREAD-CORRECTION] Prepared updates=${combinedUpdates.length}, creates=${ordersToPlace.length}, remainingBudget=${Format.formatSizeByOrderType(Math.max(0, remainingBudget), railType, manager.assets)}`, 'debug');
|
|
2159
2368
|
}
|
|
2160
|
-
return { ordersToPlace, ordersToUpdate: combinedUpdates };
|
|
2369
|
+
return { ordersToPlace, ordersToUpdate: combinedUpdates, ...(boundaryIdx === undefined ? {} : { boundaryIdx }) };
|
|
2161
2370
|
}
|
|
2162
2371
|
//# sourceMappingURL=grid.js.map
|