dexbot 1.4.8 → 1.4.10
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 +4 -7
- package/dist/dexbot.d.ts +3 -0
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +21 -2
- package/dist/dexbot.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/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/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +26 -0
- package/dist/modules/credit_runtime.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.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +4 -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.map +1 -1
- package/dist/modules/order/grid.js +187 -22
- 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.js +1 -1
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts +1 -1
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +3 -20
- 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 +15 -14
- 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/unlock.d.ts +4 -4
- package/dist/unlock.js +8 -8
- 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 {
|
|
@@ -882,9 +917,14 @@ async function initializeGrid(manager) {
|
|
|
882
917
|
await manager.resumeFundRecalc();
|
|
883
918
|
manager.resumeRecalcLogging();
|
|
884
919
|
}
|
|
885
|
-
// RC-6: Spread count updates protected by grid lock
|
|
886
|
-
|
|
887
|
-
|
|
920
|
+
// RC-6: Spread count updates protected by grid lock.
|
|
921
|
+
// initializeGrid always sets initialSpreadCount = gapSlots (theoretical
|
|
922
|
+
// value for a fresh grid). loadGrid uses the actual SPREAD-typed slot
|
|
923
|
+
// count in the gap band, which may be lower after promotions absorbed
|
|
924
|
+
// gap slots into a rail. Both are correct for their context; callers
|
|
925
|
+
// should not assume the two paths agree.
|
|
926
|
+
manager.initialSpreadCount = initialSpreadCount.buy + initialSpreadCount.sell;
|
|
927
|
+
manager.currentSpreadCount = manager.initialSpreadCount;
|
|
888
928
|
});
|
|
889
929
|
// FIX: Use consistent optional chaining pattern for all logger calls
|
|
890
930
|
manager.logger?.log?.(`Initialized grid with ${orders.length} orders.`, 'info');
|
|
@@ -1113,6 +1153,19 @@ async function _recalculateGridOrderSizesFromBlockchain(manager, orderType, opti
|
|
|
1113
1153
|
// correct side's size calculation — using manager.orders here would
|
|
1114
1154
|
// filter by stale pre-shift types and miss the crossers, producing a
|
|
1115
1155
|
// budget allocation that doesn't match the post-shift grid structure.
|
|
1156
|
+
//
|
|
1157
|
+
// Empty (size-0 VIRTUAL) slots are stored SPREAD (side-neutral) after
|
|
1158
|
+
// normalization and are deliberately NOT included in the side's
|
|
1159
|
+
// ideal-size denominator here: a SPREAD slot cannot carry a size
|
|
1160
|
+
// (validateOrder forces SPREAD size back to 0), so including it would
|
|
1161
|
+
// only dilute the budget without giving the slot a usable size.
|
|
1162
|
+
// Activation sizing for empties is handled where re-typing happens:
|
|
1163
|
+
// spread-correction (prepareSpreadCorrectionOrders) and startup
|
|
1164
|
+
// reconcile (_deriveBudgetedSideSizes) both compute the full in-rail
|
|
1165
|
+
// geometric progression including empties, then re-type the picked slot
|
|
1166
|
+
// to BUY/SELL before placement. The COW boundary-shift path re-types
|
|
1167
|
+
// the working grid by geometry first, so crossers stay in the correct
|
|
1168
|
+
// side's denominator.
|
|
1116
1169
|
const orderSource = collectActions ? workingGrid : manager.orders;
|
|
1117
1170
|
const allSideSlots = Array.from(orderSource.values())
|
|
1118
1171
|
.filter((o) => o.type === orderType)
|
|
@@ -1903,6 +1956,36 @@ function determineOrderSideByFunds(manager, currentMarketPrice) {
|
|
|
1903
1956
|
}
|
|
1904
1957
|
return { side, reason: side ? `Choosing ${side}` : 'Insufficient funds or committed inventory' };
|
|
1905
1958
|
}
|
|
1959
|
+
// Collect contiguous empty gap-band slots adjacent to a rail edge for boundary
|
|
1960
|
+
// promotion. BUY walks upward from the boundary into the gap, SELL walks
|
|
1961
|
+
// downward from the sell start; both stop at the first unavailable slot.
|
|
1962
|
+
// Mirrors are collapsed into one directional walk (step +/- 1).
|
|
1963
|
+
//
|
|
1964
|
+
// NOTE: the promotion cap is the OPPOSITE RAIL's span, NOT the gap-band size.
|
|
1965
|
+
// SELL promotion moves the boundary down by the promoted count (boundary =
|
|
1966
|
+
// buyEndIdx - count), so capping at buyEndIdx keeps it >= 0. BUY promotion
|
|
1967
|
+
// moves the boundary up; capping at the SELL rail size (maxIdx+1-sellStartIdx)
|
|
1968
|
+
// keeps the promoted boundary at or below the last placed slot. The walk's
|
|
1969
|
+
// own idx < sellStartIdx / idx > buyEndIdx bounds already limit promotion to
|
|
1970
|
+
// the band, so this cap only binds when the opposite rail is smaller than the
|
|
1971
|
+
// band — i.e. an empty opposite rail (cap 0) blocks promotion entirely.
|
|
1972
|
+
function _collectPromotableBoundarySlots(allSlotsByPrice, railType, buyEndIdx, sellStartIdx, quota) {
|
|
1973
|
+
const isBuy = railType === constants_1.ORDER_TYPES.BUY;
|
|
1974
|
+
const maxIdx = allSlotsByPrice.length - 1;
|
|
1975
|
+
const maxPromotable = isBuy
|
|
1976
|
+
? Math.max(0, maxIdx + 1 - sellStartIdx)
|
|
1977
|
+
: Math.max(0, buyEndIdx);
|
|
1978
|
+
const promotionQuota = Math.min(quota, maxPromotable);
|
|
1979
|
+
const promoted = [];
|
|
1980
|
+
const step = isBuy ? 1 : -1;
|
|
1981
|
+
for (let idx = isBuy ? buyEndIdx + 1 : sellStartIdx - 1; promoted.length < promotionQuota && (isBuy ? idx < sellStartIdx : idx > buyEndIdx); idx += step) {
|
|
1982
|
+
const slot = allSlotsByPrice[idx];
|
|
1983
|
+
if (!slot || !(0, order_1.isSlotAvailable)(slot))
|
|
1984
|
+
break;
|
|
1985
|
+
promoted.push(slot);
|
|
1986
|
+
}
|
|
1987
|
+
return promoted;
|
|
1988
|
+
}
|
|
1906
1989
|
/**
|
|
1907
1990
|
* Prepares one or more orders to correct a wide spread.
|
|
1908
1991
|
* @param {import('./types').OrderManager} manager - The OrderManager instance.
|
|
@@ -1941,15 +2024,22 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide, outOfSpread
|
|
|
1941
2024
|
.filter((o) => o.price != null && Number.isFinite(o.price))
|
|
1942
2025
|
.sort((a, b) => a.price - b.price);
|
|
1943
2026
|
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
2027
|
// Use the committed boundary for slot classification — never a speculative
|
|
1946
2028
|
// value from syncBoundaryToFunds that hasn't been persisted through the COW
|
|
1947
2029
|
// pipeline. If the boundary shifts later via _commitWorkingGrid, the next
|
|
1948
2030
|
// spread correction cycle will re-classify with the updated committed value.
|
|
1949
2031
|
// This prevents TOCTOU-style inconsistency where slot types are chosen
|
|
1950
2032
|
// against a boundary that was never atomically committed to manager.orders.
|
|
1951
|
-
const
|
|
1952
|
-
const
|
|
2033
|
+
const resolved = (0, math_1.resolveGapBand)(manager);
|
|
2034
|
+
const gapSlots = resolved.gapSlots;
|
|
2035
|
+
const boundaryKnown = resolved.boundaryIdx !== null && resolved.sellStartIdx !== null;
|
|
2036
|
+
// NOTE: `?? 0` keeps the legacy classification fallback (boundary not
|
|
2037
|
+
// restored yet → treat as bottom of the rail) so the correction can still
|
|
2038
|
+
// act on concrete BUY/SELL candidates. Boundary PROMOTION is gated on
|
|
2039
|
+
// `boundaryKnown` below: deriving a new boundary from a fabricated 0 would
|
|
2040
|
+
// silently commit a boundary that was never real.
|
|
2041
|
+
const buyEndIdx = resolved.boundaryIdx ?? 0;
|
|
2042
|
+
const sellStartIdx = resolved.sellStartIdx ?? (0, math_1.getSellStartIdx)(buyEndIdx, gapSlots);
|
|
1953
2043
|
const getSlotCorrectType = (slot) => {
|
|
1954
2044
|
const idx = slotIndexMap.get(slot.id);
|
|
1955
2045
|
if (idx === undefined)
|
|
@@ -1979,30 +2069,68 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide, outOfSpread
|
|
|
1979
2069
|
&& getSlotCorrectType(o) === railType)
|
|
1980
2070
|
.sort((a, b) => railType === constants_1.ORDER_TYPES.BUY ? a.price - b.price : b.price - a.price)
|
|
1981
2071
|
.slice(0, missingSlots);
|
|
1982
|
-
// Secondary candidates: orphaned virtual slots
|
|
1983
|
-
//
|
|
1984
|
-
// These sit inside the active window and are invisible to the
|
|
2072
|
+
// Secondary candidates: orphaned virtual slots that have lost their
|
|
2073
|
+
// order (e.g. stale-cleaned after a race condition during a crash).
|
|
2074
|
+
// These sit inside the active window and are invisible to the
|
|
2075
|
+
// gap-band SPREAD filter above.
|
|
1985
2076
|
//
|
|
1986
|
-
//
|
|
1987
|
-
//
|
|
1988
|
-
//
|
|
1989
|
-
//
|
|
1990
|
-
//
|
|
2077
|
+
// Empty slots are stored SPREAD (side-neutral) after normalization, so
|
|
2078
|
+
// the stored type can be railType OR SPREAD — what matters is that the
|
|
2079
|
+
// boundary-correct type (geometry) matches the rail being corrected.
|
|
2080
|
+
// Filter by boundary-correct type so that filled-then-virtualized slots
|
|
2081
|
+
// whose boundary position has moved into the spread or opposite zone are
|
|
2082
|
+
// NOT re-activated on the stale side — doing so would compound inventory
|
|
2083
|
+
// at prices where the bot already traded.
|
|
1991
2084
|
const orphanedVirtualCandidates = allOrders
|
|
1992
|
-
.filter((o) => o.type === railType
|
|
2085
|
+
.filter((o) => (o.type === railType || o.type === constants_1.ORDER_TYPES.SPREAD)
|
|
1993
2086
|
&& o.state === constants_1.ORDER_STATES.VIRTUAL
|
|
1994
2087
|
&& !o.orderId
|
|
1995
2088
|
&& Number(o.size || 0) === 0
|
|
1996
2089
|
&& getSlotCorrectType(o) === railType)
|
|
1997
2090
|
.sort((a, b) => railType === constants_1.ORDER_TYPES.BUY ? b.price - a.price : a.price - b.price)
|
|
1998
2091
|
.slice(0, missingSlots);
|
|
2092
|
+
// If the funded rail is full, the spread itself may be stale: the
|
|
2093
|
+
// nearest empty slots are still in the gap band. Promote contiguous
|
|
2094
|
+
// empty gap slots from that rail edge so correction can repair the
|
|
2095
|
+
// boundary and place orders in one atomic COW commit.
|
|
2096
|
+
//
|
|
2097
|
+
// Requires a KNOWN committed boundary: with a null/unknown boundary the
|
|
2098
|
+
// rail edges are fabricated (buyEndIdx=0), so promotion would place
|
|
2099
|
+
// orders at arbitrary prices and return a boundary derived from that
|
|
2100
|
+
// fiction. Without promotion there is nothing to commit, so falling
|
|
2101
|
+
// back to the pre-existing candidate paths is safe.
|
|
2102
|
+
const promotedCandidates = [];
|
|
2103
|
+
if (boundaryKnown && orphanedVirtualCandidates.length + typedSpreadCandidates.length < missingSlots) {
|
|
2104
|
+
const rawQuota = missingSlots - orphanedVirtualCandidates.length - typedSpreadCandidates.length;
|
|
2105
|
+
promotedCandidates.push(..._collectPromotableBoundarySlots(allSlotsByPrice, railType, buyEndIdx, sellStartIdx, rawQuota));
|
|
2106
|
+
if (promotedCandidates.length > 0) {
|
|
2107
|
+
manager.logger?.log?.(`[SPREAD-CORRECTION] ${promotedCandidates.length} gap slot(s) available for boundary promotion on ${sideName}`, 'info');
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
1999
2110
|
// Merge: prefer orphaned virtuals (they already occupy correct grid positions) then
|
|
2000
2111
|
// fall back to SPREAD slots for any remaining quota.
|
|
2001
2112
|
const remainingQuota = Math.max(0, missingSlots - orphanedVirtualCandidates.length);
|
|
2002
2113
|
let spreadCandidates = [
|
|
2003
2114
|
...orphanedVirtualCandidates,
|
|
2004
|
-
...typedSpreadCandidates.slice(0, remainingQuota)
|
|
2115
|
+
...typedSpreadCandidates.slice(0, remainingQuota),
|
|
2116
|
+
...promotedCandidates
|
|
2005
2117
|
];
|
|
2118
|
+
// Dedupe by slot id: an empty in-rail slot typed SPREAD (normalized) now
|
|
2119
|
+
// qualifies for BOTH orphanedVirtualCandidates and typedSpreadCandidates
|
|
2120
|
+
// (both accept SPREAD type + rail geometry), so a single slot can be
|
|
2121
|
+
// planned twice. Duplicate CREATEs would inflate the sizing denominator
|
|
2122
|
+
// (diluting every order), mislead the plan counts, and get dropped by the
|
|
2123
|
+
// COW same-batch collision filter anyway. Keep the first occurrence
|
|
2124
|
+
// (orphaned-priority — those already occupy correct grid positions).
|
|
2125
|
+
{
|
|
2126
|
+
const seenSlotIds = new Set();
|
|
2127
|
+
spreadCandidates = spreadCandidates.filter((c) => {
|
|
2128
|
+
if (!c?.id || seenSlotIds.has(c.id))
|
|
2129
|
+
return false;
|
|
2130
|
+
seenSlotIds.add(c.id);
|
|
2131
|
+
return true;
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2006
2134
|
// P2: Filter out candidates whose price already has a placed order from any slot.
|
|
2007
2135
|
// This prevents creating a duplicate order at the same price when a prior cycle's
|
|
2008
2136
|
// order was not properly cleaned up (e.g. uncertain broadcast).
|
|
@@ -2135,6 +2263,43 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide, outOfSpread
|
|
|
2135
2263
|
});
|
|
2136
2264
|
remainingBudget -= createSize;
|
|
2137
2265
|
}
|
|
2266
|
+
let boundaryIdx;
|
|
2267
|
+
const placedPromotedIds = new Set(ordersToPlace
|
|
2268
|
+
.filter((order) => promotedCandidates.some((slot) => slot.id === order.id))
|
|
2269
|
+
.map((order) => order.id));
|
|
2270
|
+
if (placedPromotedIds.size > 0) {
|
|
2271
|
+
// Derive boundary from the placed promoted set's extremes (via
|
|
2272
|
+
// slotIndexMap), not the contiguous prefix. Any promoted-but-
|
|
2273
|
+
// unplaced slots sit in-rail or in-band as normal empty slots.
|
|
2274
|
+
let maxDist = 0;
|
|
2275
|
+
for (const id of placedPromotedIds) {
|
|
2276
|
+
const idx = slotIndexMap.get(id);
|
|
2277
|
+
if (idx === undefined)
|
|
2278
|
+
continue;
|
|
2279
|
+
const dist = railType === constants_1.ORDER_TYPES.BUY
|
|
2280
|
+
? idx - buyEndIdx
|
|
2281
|
+
: sellStartIdx - idx;
|
|
2282
|
+
if (dist > maxDist)
|
|
2283
|
+
maxDist = dist;
|
|
2284
|
+
}
|
|
2285
|
+
if (maxDist > 0) {
|
|
2286
|
+
boundaryIdx = railType === constants_1.ORDER_TYPES.BUY
|
|
2287
|
+
? buyEndIdx + maxDist
|
|
2288
|
+
: buyEndIdx - maxDist;
|
|
2289
|
+
const maxIdx = allSlotsByPrice.length - 1;
|
|
2290
|
+
const clamped = Math.max(0, Math.min(maxIdx, boundaryIdx));
|
|
2291
|
+
if (clamped !== boundaryIdx) {
|
|
2292
|
+
manager.logger?.log?.(`[SPREAD-CORRECTION] Boundary promotion clamped on ${sideName}: ` +
|
|
2293
|
+
`${boundaryIdx} -> ${clamped}`, 'warn');
|
|
2294
|
+
boundaryIdx = clamped;
|
|
2295
|
+
}
|
|
2296
|
+
manager.logger?.log?.(`[SPREAD-CORRECTION] Boundary promotion on ${sideName}: ` +
|
|
2297
|
+
`${buyEndIdx} -> ${boundaryIdx} (${placedPromotedIds.size} placed, maxDist ${maxDist})`, 'info');
|
|
2298
|
+
}
|
|
2299
|
+
else {
|
|
2300
|
+
boundaryIdx = undefined;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2138
2303
|
const combinedUpdates = [...redistributionUpdates];
|
|
2139
2304
|
for (const plannedUpdate of ordersToUpdate) {
|
|
2140
2305
|
const id = plannedUpdate?.partialOrder?.id || plannedUpdate?.id;
|
|
@@ -2157,6 +2322,6 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide, outOfSpread
|
|
|
2157
2322
|
if (combinedUpdates.length > 0 || ordersToPlace.length > 0) {
|
|
2158
2323
|
manager.logger?.log?.(`[SPREAD-CORRECTION] Prepared updates=${combinedUpdates.length}, creates=${ordersToPlace.length}, remainingBudget=${Format.formatSizeByOrderType(Math.max(0, remainingBudget), railType, manager.assets)}`, 'debug');
|
|
2159
2324
|
}
|
|
2160
|
-
return { ordersToPlace, ordersToUpdate: combinedUpdates };
|
|
2325
|
+
return { ordersToPlace, ordersToUpdate: combinedUpdates, ...(boundaryIdx === undefined ? {} : { boundaryIdx }) };
|
|
2161
2326
|
}
|
|
2162
2327
|
//# sourceMappingURL=grid.js.map
|