dexbot 1.4.1 → 1.4.3
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 +1 -0
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +29 -1
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/constants.d.ts +1 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +5 -0
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +36 -7
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/launcher/credential_daemon.d.ts.map +1 -1
- package/dist/modules/launcher/credential_daemon.js +3 -4
- package/dist/modules/launcher/credential_daemon.js.map +1 -1
- package/dist/modules/launcher/market_adapter_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/market_adapter_runtime.js +2 -4
- package/dist/modules/launcher/market_adapter_runtime.js.map +1 -1
- package/dist/modules/launcher/market_adapter_watchdog.d.ts.map +1 -1
- package/dist/modules/launcher/market_adapter_watchdog.js +4 -6
- package/dist/modules/launcher/market_adapter_watchdog.js.map +1 -1
- package/dist/modules/logger.js +0 -1
- package/dist/modules/logger.js.map +1 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +148 -99
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/logger.d.ts.map +1 -1
- package/dist/modules/order/logger.js +2 -8
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +6 -9
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +16 -1
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +2 -1
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +79 -21
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/timeout.d.ts +25 -0
- package/dist/modules/order/utils/timeout.d.ts.map +1 -0
- package/dist/modules/order/utils/timeout.js +49 -0
- package/dist/modules/order/utils/timeout.js.map +1 -0
- package/dist/modules/order/utils/validate.d.ts +8 -1
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +33 -9
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/order/utils/withPoolRef.d.ts +6 -0
- package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -0
- package/dist/modules/order/utils/withPoolRef.js +129 -0
- package/dist/modules/order/utils/withPoolRef.js.map +1 -0
- package/dist/modules/types.d.ts +1 -0
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/unlock.d.ts.map +1 -1
- package/dist/unlock.js +2 -4
- package/dist/unlock.js.map +1 -1
- package/package.json +2 -1
|
@@ -172,6 +172,7 @@ function _snapshotFundState(manager) {
|
|
|
172
172
|
const math_1 = require("./utils/math");
|
|
173
173
|
const order_1 = require("./utils/order");
|
|
174
174
|
const system_1 = require("./utils/system");
|
|
175
|
+
const withPoolRef_1 = require("./utils/withPoolRef");
|
|
175
176
|
const market_adapter_whitelist_1 = require("../market_adapter_whitelist");
|
|
176
177
|
const errors_1 = require("../utils/errors");
|
|
177
178
|
function calculateGapSlots(incrementPercent, targetSpreadPercent, gridLimitsOverride) {
|
|
@@ -499,7 +500,7 @@ async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
499
500
|
return;
|
|
500
501
|
return await manager._gridLock.acquire(async () => {
|
|
501
502
|
try {
|
|
502
|
-
await manager._initializeAssets();
|
|
503
|
+
await (0, system_1.withBlockchainRetry)(() => manager._initializeAssets(), 'initializeAssets', { logger: manager.logger });
|
|
503
504
|
}
|
|
504
505
|
catch (e) {
|
|
505
506
|
manager.logger?.log?.(`Asset initialization failed during grid load: ${(0, errors_1.getErrorMessage)(e)}`, 'warn');
|
|
@@ -596,7 +597,12 @@ async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
596
597
|
async function initializeGrid(manager) {
|
|
597
598
|
if (!manager)
|
|
598
599
|
throw new Error('initializeGrid requires a manager instance');
|
|
599
|
-
|
|
600
|
+
try {
|
|
601
|
+
await (0, system_1.withBlockchainRetry)(() => manager._initializeAssets(), 'initializeAssets', { logger: manager.logger });
|
|
602
|
+
}
|
|
603
|
+
catch (e) {
|
|
604
|
+
manager.logger?.log?.(`Asset initialization failed during grid init: ${(0, errors_1.getErrorMessage)(e)}`, 'warn');
|
|
605
|
+
}
|
|
600
606
|
// FIX: Add explicit state validation to prevent cryptic errors later
|
|
601
607
|
if (!manager.assets || !manager.assets.assetA || !manager.assets.assetB) {
|
|
602
608
|
throw new Error('Asset initialization did not complete properly - assetA or assetB undefined');
|
|
@@ -610,7 +616,7 @@ async function initializeGrid(manager) {
|
|
|
610
616
|
if (typeof mpRaw !== 'number' || isNaN(mpRaw)) {
|
|
611
617
|
try {
|
|
612
618
|
const { BitShares } = require('../bitshares_client');
|
|
613
|
-
const derived = await (0,
|
|
619
|
+
const derived = await (0, withPoolRef_1.derivePriceWithPoolRef)(BitShares, manager.config.assetA, manager.config.assetB, manager.config.priceMode || 'auto', manager.config.poolRef);
|
|
614
620
|
if (derived) {
|
|
615
621
|
manager.logger?.log?.(`[DIAGNOSTIC] initializeGrid: Derived new startPrice=${derived.toFixed(8)} (mode=${manager.config.priceMode || 'auto'})`, 'info');
|
|
616
622
|
manager.config.startPrice = Number(derived);
|
|
@@ -649,7 +655,7 @@ async function initializeGrid(manager) {
|
|
|
649
655
|
else if (gpMode === 'pool' || gpMode === 'book') {
|
|
650
656
|
try {
|
|
651
657
|
const { BitShares } = require('../bitshares_client');
|
|
652
|
-
const derived = await (0,
|
|
658
|
+
const derived = await (0, withPoolRef_1.derivePriceWithPoolRef)(BitShares, manager.config.assetA, manager.config.assetB, gpMode, manager.config.poolRef);
|
|
653
659
|
if (derived) {
|
|
654
660
|
gp = Number(derived);
|
|
655
661
|
gpSource = gpMode;
|
|
@@ -870,49 +876,79 @@ async function recalculateGrid(manager, opts) {
|
|
|
870
876
|
// Total timeout across all steps — prevents indefinite hang even if
|
|
871
877
|
// an individual withBlockchainRetry step pins the event loop.
|
|
872
878
|
const totalTimeoutMs = constants_1.PIPELINE_TIMING.TIMEOUT_MS * 2; // 10 min
|
|
879
|
+
let _resyncAborted = false;
|
|
873
880
|
const work = (async () => {
|
|
874
|
-
manager.logger?.log?.('Starting full resync...', 'info');
|
|
875
|
-
// #1: Initialize assets (returns immediately if already loaded)
|
|
876
|
-
await manager._initializeAssets();
|
|
877
|
-
// #2: Fetch account totals with timeout + retry + node failover
|
|
878
|
-
await (0, system_1.withBlockchainRetry)(() => manager.fetchAccountTotals(), 'fetchAccountTotals', { logger: manager.logger });
|
|
879
|
-
// #3: Read open orders with timeout + retry + node failover
|
|
880
|
-
const chainOpenOrders = await (0, system_1.withBlockchainRetry)(() => readOpenOrdersFn(), 'readOpenOrders', { logger: manager.logger });
|
|
881
|
-
if (!Array.isArray(chainOpenOrders))
|
|
882
|
-
return;
|
|
883
|
-
await (0, system_1.withBlockchainRetry)(() => manager.syncFromOpenOrders(chainOpenOrders, { skipAccounting: true }), 'syncFromOpenOrders', { logger: manager.logger });
|
|
884
|
-
manager.resetFunds();
|
|
885
|
-
await manager.persistGrid();
|
|
886
|
-
await initializeGrid(manager);
|
|
887
|
-
const { reconcileGridOrders } = require('./grid_reconcile');
|
|
888
|
-
// #5: Reconcile grid orders with timeout + retry + node failover
|
|
889
881
|
try {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
//
|
|
894
|
-
|
|
895
|
-
|
|
882
|
+
manager.logger?.log?.('Starting full resync...', 'info');
|
|
883
|
+
if (_resyncAborted)
|
|
884
|
+
return;
|
|
885
|
+
// #1: Initialize assets with timeout + retry + node failover
|
|
886
|
+
try {
|
|
887
|
+
await (0, system_1.withBlockchainRetry)(() => manager._initializeAssets(), 'initializeAssets', { logger: manager.logger });
|
|
888
|
+
}
|
|
889
|
+
catch (e) {
|
|
890
|
+
manager.logger?.log?.(`Asset initialization failed during resync: ${(0, errors_1.getErrorMessage)(e)}`, 'warn');
|
|
891
|
+
}
|
|
892
|
+
if (_resyncAborted)
|
|
893
|
+
return;
|
|
894
|
+
// #2: Fetch account totals with timeout + retry + node failover
|
|
895
|
+
await (0, system_1.withBlockchainRetry)(() => manager.fetchAccountTotals(), 'fetchAccountTotals', { logger: manager.logger });
|
|
896
|
+
if (_resyncAborted)
|
|
897
|
+
return;
|
|
898
|
+
// #3: Read open orders with timeout + retry + node failover
|
|
899
|
+
const chainOpenOrders = await (0, system_1.withBlockchainRetry)(() => readOpenOrdersFn(), 'readOpenOrders', { logger: manager.logger });
|
|
900
|
+
if (_resyncAborted)
|
|
901
|
+
return;
|
|
902
|
+
if (!Array.isArray(chainOpenOrders))
|
|
903
|
+
return;
|
|
904
|
+
await (0, system_1.withBlockchainRetry)(() => manager.syncFromOpenOrders(chainOpenOrders, { skipAccounting: true }), 'syncFromOpenOrders', { logger: manager.logger });
|
|
905
|
+
if (_resyncAborted)
|
|
906
|
+
return;
|
|
907
|
+
manager.resetFunds();
|
|
908
|
+
if (_resyncAborted)
|
|
909
|
+
return;
|
|
910
|
+
await manager.persistGrid();
|
|
911
|
+
if (_resyncAborted)
|
|
912
|
+
return;
|
|
913
|
+
await initializeGrid(manager);
|
|
914
|
+
if (_resyncAborted)
|
|
915
|
+
return;
|
|
916
|
+
const { reconcileGridOrders } = require('./grid_reconcile');
|
|
917
|
+
// #5: Reconcile grid orders with timeout + retry + node failover
|
|
918
|
+
try {
|
|
919
|
+
await (0, system_1.withBlockchainRetry)(() => reconcileGridOrders({ manager, config: manager.config, account, privateKey, chainOrders, chainOpenOrders }), 'reconcileGridOrders',
|
|
920
|
+
// 5 min: Phase 2 of reconcile does sequential creates (~3s each);
|
|
921
|
+
// the default 30s timeout would kill mid-batch and cause duplicate-
|
|
922
|
+
// accumulation death spirals. PIPELINE_TIMING.TIMEOUT_MS gives enough
|
|
923
|
+
// headroom for all pending creates+updates to finish in one shot.
|
|
924
|
+
{ logger: manager.logger, timeoutMs: constants_1.PIPELINE_TIMING.TIMEOUT_MS });
|
|
925
|
+
}
|
|
926
|
+
catch (err) {
|
|
927
|
+
manager.logger?.log?.(`Error during startup order reconciliation: ${(0, errors_1.getErrorMessage)(err)}`, 'error');
|
|
928
|
+
throw new Error(`Grid recalculation failed during order reconciliation: ${(0, errors_1.getErrorMessage)(err)}`);
|
|
929
|
+
}
|
|
930
|
+
if (_resyncAborted)
|
|
931
|
+
return;
|
|
932
|
+
manager.logger?.log?.('Full resync complete.', 'info');
|
|
896
933
|
}
|
|
897
|
-
|
|
898
|
-
manager.
|
|
899
|
-
throw new Error(`Grid recalculation failed during order reconciliation: ${(0, errors_1.getErrorMessage)(err)}`);
|
|
934
|
+
finally {
|
|
935
|
+
manager.finishBootstrap();
|
|
900
936
|
}
|
|
901
|
-
manager.logger?.log?.('Full resync complete.', 'info');
|
|
902
937
|
})();
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
938
|
+
// Swallow late rejection if timeout wins the race
|
|
939
|
+
Promise.resolve(work).catch(() => { });
|
|
940
|
+
let timeoutId;
|
|
941
|
+
const result = await Promise.race([
|
|
942
|
+
work,
|
|
943
|
+
new Promise((_, reject) => {
|
|
944
|
+
timeoutId = setTimeout(() => {
|
|
945
|
+
_resyncAborted = true;
|
|
946
|
+
reject(new Error(`recalculateGrid timed out after ${totalTimeoutMs}ms`));
|
|
947
|
+
}, totalTimeoutMs);
|
|
948
|
+
})
|
|
949
|
+
]);
|
|
950
|
+
clearTimeout(timeoutId);
|
|
951
|
+
return result;
|
|
916
952
|
}
|
|
917
953
|
/**
|
|
918
954
|
* Check for grid divergence and trigger update if threshold is met.
|
|
@@ -1018,10 +1054,18 @@ async function _recalculateGridOrderSizesFromBlockchain(manager, orderType, opti
|
|
|
1018
1054
|
const ctx = await _getSizingContext(manager, sideName);
|
|
1019
1055
|
if (!ctx)
|
|
1020
1056
|
return collectActions ? { actions: [], changed: false } : undefined;
|
|
1021
|
-
// Get ALL slots for this side, sorted for calculateRotationOrderSizes
|
|
1057
|
+
// Get ALL slots for this side, sorted for calculateRotationOrderSizes.
|
|
1022
1058
|
// SELL: sorted ASC (Market to Edge)
|
|
1023
1059
|
// BUY: sorted ASC (Edge to Market)
|
|
1024
|
-
|
|
1060
|
+
//
|
|
1061
|
+
// When a working grid is available (COW mode), read types from it
|
|
1062
|
+
// instead of manager.orders. This ensures slots whose type changed
|
|
1063
|
+
// due to a boundary shift (e.g. SPREAD→BUY) are included in the
|
|
1064
|
+
// correct side's size calculation — using manager.orders here would
|
|
1065
|
+
// filter by stale pre-shift types and miss the crossers, producing a
|
|
1066
|
+
// budget allocation that doesn't match the post-shift grid structure.
|
|
1067
|
+
const orderSource = collectActions ? workingGrid : manager.orders;
|
|
1068
|
+
const allSideSlots = Array.from(orderSource.values())
|
|
1025
1069
|
.filter((o) => o.type === orderType)
|
|
1026
1070
|
.sort((a, b) => a.price - b.price);
|
|
1027
1071
|
if (allSideSlots.length === 0)
|
|
@@ -1133,32 +1177,37 @@ async function updateGridFromBlockchainSnapshot(manager, orderType = 'both', fro
|
|
|
1133
1177
|
const workingGrid = new WorkingGrid(manager.orders, { baseVersion: manager._gridVersion });
|
|
1134
1178
|
const allActions = [];
|
|
1135
1179
|
let hasWorkingChanges = false;
|
|
1136
|
-
// Calculate size updates for each side (via existing sizing function in COW mode)
|
|
1137
|
-
if (orderType === constants_1.ORDER_TYPES.BUY || orderType === 'both') {
|
|
1138
|
-
const buyResult = await _recalculateGridOrderSizesFromBlockchain(manager, constants_1.ORDER_TYPES.BUY, { workingGrid });
|
|
1139
|
-
allActions.push(...buyResult.actions);
|
|
1140
|
-
hasWorkingChanges = hasWorkingChanges || buyResult.changed;
|
|
1141
|
-
}
|
|
1142
|
-
if (orderType === constants_1.ORDER_TYPES.SELL || orderType === 'both') {
|
|
1143
|
-
const sellResult = await _recalculateGridOrderSizesFromBlockchain(manager, constants_1.ORDER_TYPES.SELL, { workingGrid });
|
|
1144
|
-
allActions.push(...sellResult.actions);
|
|
1145
|
-
hasWorkingChanges = hasWorkingChanges || sellResult.changed;
|
|
1146
|
-
}
|
|
1147
|
-
// If the boundary is shifting, reassign slot types in the WorkingGrid now.
|
|
1148
|
-
// This ensures the COW commit delivers consistent types + boundaryIdx in one
|
|
1149
|
-
// atomic operation — manager.boundaryIdx must not be touched before the commit.
|
|
1150
1180
|
const newBoundary = (overrideBoundaryIdx !== null) ? overrideBoundaryIdx : manager.boundaryIdx;
|
|
1181
|
+
// REASSIGN SLOT TYPES FIRST when the boundary is shifting.
|
|
1182
|
+
// _recalculateGridOrderSizesFromBlockchain needs to see the corrected
|
|
1183
|
+
// types so that slots which cross the boundary (e.g. SPREAD→BUY) are
|
|
1184
|
+
// included in the correct side's budget allocation. Running assignGridRoles
|
|
1185
|
+
// after the size calc means crossers keep their pre-shift sizes, producing
|
|
1186
|
+
// an allocation that doesn't match the post-shift grid structure.
|
|
1151
1187
|
if (overrideBoundaryIdx !== null && overrideBoundaryIdx !== manager.boundaryIdx) {
|
|
1152
1188
|
const gapSlots = calculateGapSlots(manager.config.incrementPercent, manager.config.targetSpreadPercent, manager.config.gridLimits);
|
|
1153
1189
|
const allSlots = Array.from(workingGrid.values())
|
|
1154
1190
|
.filter((s) => s.price != null)
|
|
1155
1191
|
.sort((a, b) => a.price - b.price);
|
|
1156
|
-
const updatedSlots = (0, order_1.assignGridRoles)(allSlots, newBoundary, gapSlots, constants_1.ORDER_TYPES, constants_1.ORDER_STATES);
|
|
1192
|
+
const updatedSlots = (0, order_1.assignGridRoles)(allSlots, newBoundary, gapSlots, constants_1.ORDER_TYPES, constants_1.ORDER_STATES, { assignOnChain: true });
|
|
1157
1193
|
for (const slot of updatedSlots) {
|
|
1158
1194
|
workingGrid.set(slot.id, slot);
|
|
1159
1195
|
}
|
|
1160
1196
|
hasWorkingChanges = true;
|
|
1161
1197
|
}
|
|
1198
|
+
// Calculate size updates for each side (via existing sizing function in COW mode).
|
|
1199
|
+
// _recalculateGridOrderSizesFromBlockchain reads types from the working grid when
|
|
1200
|
+
// one is passed, so boundary-crossing slots are now correctly classified.
|
|
1201
|
+
if (orderType === constants_1.ORDER_TYPES.BUY || orderType === 'both') {
|
|
1202
|
+
const buyResult = await _recalculateGridOrderSizesFromBlockchain(manager, constants_1.ORDER_TYPES.BUY, { workingGrid });
|
|
1203
|
+
allActions.push(...buyResult.actions);
|
|
1204
|
+
hasWorkingChanges = hasWorkingChanges || buyResult.changed;
|
|
1205
|
+
}
|
|
1206
|
+
if (orderType === constants_1.ORDER_TYPES.SELL || orderType === 'both') {
|
|
1207
|
+
const sellResult = await _recalculateGridOrderSizesFromBlockchain(manager, constants_1.ORDER_TYPES.SELL, { workingGrid });
|
|
1208
|
+
allActions.push(...sellResult.actions);
|
|
1209
|
+
hasWorkingChanges = hasWorkingChanges || sellResult.changed;
|
|
1210
|
+
}
|
|
1162
1211
|
// Return COW result only if there are changes
|
|
1163
1212
|
if (allActions.length === 0 && !hasWorkingChanges) {
|
|
1164
1213
|
return null;
|
|
@@ -1468,8 +1517,21 @@ async function checkSpreadCondition(manager, _BitShares, updateOrdersOnChainBatc
|
|
|
1468
1517
|
correction = await prepareSpreadCorrectionOrders(manager, decision.side);
|
|
1469
1518
|
if (!correction)
|
|
1470
1519
|
return false;
|
|
1471
|
-
|
|
1472
|
-
|
|
1520
|
+
let placeCount = correction.ordersToPlace?.length || 0;
|
|
1521
|
+
let updateCount = correction.ordersToUpdate?.length || 0;
|
|
1522
|
+
// STARVATION FALLBACK: If the selected side has no correctable slots (e.g.
|
|
1523
|
+
// all SPREAD slots already filled or misaligned), try the opposite side.
|
|
1524
|
+
if ((placeCount + updateCount) === 0) {
|
|
1525
|
+
const oppositeSide = decision.side === constants_1.ORDER_TYPES.BUY ? constants_1.ORDER_TYPES.SELL : constants_1.ORDER_TYPES.BUY;
|
|
1526
|
+
manager.logger?.log?.(`[SPREAD] Side ${decision.side} produced zero candidates; ` +
|
|
1527
|
+
`trying opposite side ${oppositeSide}.`, 'debug');
|
|
1528
|
+
const oppositeCorrection = await prepareSpreadCorrectionOrders(manager, oppositeSide);
|
|
1529
|
+
if (oppositeCorrection) {
|
|
1530
|
+
correction = oppositeCorrection;
|
|
1531
|
+
placeCount = correction.ordersToPlace?.length || 0;
|
|
1532
|
+
updateCount = correction.ordersToUpdate?.length || 0;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1473
1535
|
// Capture fund snapshot under lock for pre-flight verification before broadcast
|
|
1474
1536
|
fundSnapshot = _snapshotFundState(manager);
|
|
1475
1537
|
return (placeCount + updateCount) > 0;
|
|
@@ -1730,29 +1792,7 @@ function determineOrderSideByFunds(manager, currentMarketPrice) {
|
|
|
1730
1792
|
const sellInBuyUnits = (Number.isFinite(marketPrice) && marketPrice > 0)
|
|
1731
1793
|
? sellAvailable * marketPrice
|
|
1732
1794
|
: sellAvailable;
|
|
1733
|
-
|
|
1734
|
-
// When the market is below the grid center, the bot should prefer SELL
|
|
1735
|
-
// (to narrow the spread from above — sells are too high). When the market
|
|
1736
|
-
// is above the grid center, prefer BUY (buys are too low). This prevents
|
|
1737
|
-
// spread correction from placing orders on the retreating side, which would
|
|
1738
|
-
// compound inventory in the wrong direction after a batch of fill-induced
|
|
1739
|
-
// boundary shifts.
|
|
1740
|
-
const centerPrice = manager._lastGridPricingContext?.startPrice
|
|
1741
|
-
?? Number(manager.config.startPrice)
|
|
1742
|
-
?? null;
|
|
1743
|
-
const hasDirection = Number.isFinite(marketPrice)
|
|
1744
|
-
&& marketPrice > 0
|
|
1745
|
-
&& Number.isFinite(centerPrice)
|
|
1746
|
-
&& centerPrice > 0;
|
|
1747
|
-
if (hasDirection && marketPrice < centerPrice) {
|
|
1748
|
-
side = constants_1.ORDER_TYPES.SELL;
|
|
1749
|
-
}
|
|
1750
|
-
else if (hasDirection && marketPrice > centerPrice) {
|
|
1751
|
-
side = constants_1.ORDER_TYPES.BUY;
|
|
1752
|
-
}
|
|
1753
|
-
else {
|
|
1754
|
-
side = buyAvailable >= sellInBuyUnits ? constants_1.ORDER_TYPES.BUY : constants_1.ORDER_TYPES.SELL;
|
|
1755
|
-
}
|
|
1795
|
+
side = buyAvailable >= sellInBuyUnits ? constants_1.ORDER_TYPES.BUY : constants_1.ORDER_TYPES.SELL;
|
|
1756
1796
|
}
|
|
1757
1797
|
else if (buyViable) {
|
|
1758
1798
|
side = constants_1.ORDER_TYPES.BUY;
|
|
@@ -1852,27 +1892,27 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide) {
|
|
|
1852
1892
|
// 1. Priority: Update existing PARTIAL orders at the edge (Highest Buy / Lowest Sell).
|
|
1853
1893
|
// 2. Fallback: Activate SPREAD slots at the edge (Lowest Spread for Buy / Highest Spread for Sell).
|
|
1854
1894
|
const allOrders = Array.from(manager.orders.values());
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
.sort((a, b) => railType === constants_1.ORDER_TYPES.BUY ? b.price - a.price : a.price - b.price);
|
|
1859
|
-
if (partials.length > 0) {
|
|
1860
|
-
edgePartial = partials[0];
|
|
1861
|
-
manager.logger?.log?.(`[SPREAD-CORRECTION] Identified partial order at ${edgePartial.price} for update`, 'debug');
|
|
1862
|
-
}
|
|
1863
|
-
// Boundary-correct type computation. Used by both candidate pools below to ensure
|
|
1864
|
-
// spread correction does not re-activate slots whose current boundary position
|
|
1865
|
-
// places them on the wrong side or in the spread zone — doing so would compound
|
|
1866
|
-
// inventory at prices where the bot already traded.
|
|
1895
|
+
// Boundary-correct type computation — hoisted before edge-partial and
|
|
1896
|
+
// candidate filters so all call sites (including the edge-partial filter
|
|
1897
|
+
// which now uses getSlotCorrectType) can access it.
|
|
1867
1898
|
//
|
|
1868
|
-
// The natural type of a slot is derived from its position in the price-sorted
|
|
1869
|
-
// relative to boundaryIdx + gapSlots: indices in [0, boundaryIdx] are
|
|
1870
|
-
// in [boundaryIdx + gapSlots + 1, N-1] are SELL, the middle
|
|
1899
|
+
// The natural type of a slot is derived from its position in the price-sorted
|
|
1900
|
+
// rail relative to boundaryIdx + gapSlots: indices in [0, boundaryIdx] are
|
|
1901
|
+
// BUY, indices in [boundaryIdx + gapSlots + 1, N-1] are SELL, the middle
|
|
1902
|
+
// band is SPREAD.
|
|
1871
1903
|
const allSlotsByPrice = allOrders
|
|
1872
1904
|
.filter((o) => o.price != null && Number.isFinite(o.price))
|
|
1873
1905
|
.sort((a, b) => a.price - b.price);
|
|
1874
1906
|
const slotIndexMap = new Map(allSlotsByPrice.map((o, i) => [o.id, i]));
|
|
1875
1907
|
const gapSlots = calculateGapSlots(manager.config?.incrementPercent, manager.config?.targetSpreadPercent, manager.config?.gridLimits);
|
|
1908
|
+
// Sync boundary from current fund state to avoid stale boundaryIdx causing
|
|
1909
|
+
// getSlotCorrectType to misclassify slots (e.g. after fills shifted the
|
|
1910
|
+
// boundary but the COW commit hasn't updated it yet). This is safe under
|
|
1911
|
+
// the grid lock — no concurrent modifications can race with this read.
|
|
1912
|
+
const boundarySync = (0, system_1.syncBoundaryToFunds)(manager);
|
|
1913
|
+
if (boundarySync.changed && boundarySync.newIdx !== undefined) {
|
|
1914
|
+
manager.boundaryIdx = boundarySync.newIdx;
|
|
1915
|
+
}
|
|
1876
1916
|
const bIdx = manager.boundaryIdx ?? 0;
|
|
1877
1917
|
const buyEndIdx = bIdx;
|
|
1878
1918
|
const sellStartIdx = bIdx + Number(gapSlots) + 1;
|
|
@@ -1886,6 +1926,15 @@ async function prepareSpreadCorrectionOrders(manager, preferredSide) {
|
|
|
1886
1926
|
return constants_1.ORDER_TYPES.SELL;
|
|
1887
1927
|
return constants_1.ORDER_TYPES.SPREAD;
|
|
1888
1928
|
};
|
|
1929
|
+
let edgePartial = null;
|
|
1930
|
+
const partials = allOrders
|
|
1931
|
+
.filter((o) => getSlotCorrectType(o) === railType
|
|
1932
|
+
&& o.state === constants_1.ORDER_STATES.PARTIAL)
|
|
1933
|
+
.sort((a, b) => railType === constants_1.ORDER_TYPES.BUY ? b.price - a.price : a.price - b.price);
|
|
1934
|
+
if (partials.length > 0) {
|
|
1935
|
+
edgePartial = partials[0];
|
|
1936
|
+
manager.logger?.log?.(`[SPREAD-CORRECTION] Identified partial order at ${edgePartial.price} for update`, 'debug');
|
|
1937
|
+
}
|
|
1889
1938
|
// Primary candidates: SPREAD-type slots adjacent to the gap. Filter by
|
|
1890
1939
|
// boundary-correct type so a SPREAD slot that, after a boundary shift, now sits
|
|
1891
1940
|
// in the BUY or SELL zone is excluded — it would otherwise be placed on the
|