dexbot 1.4.24 → 1.5.0
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 +67 -0
- package/README.md +7 -2
- package/analysis/README.md +61 -5
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/analyze_derivatives.ts +1 -1
- package/analysis/bot_key_utils.ts +272 -3
- package/analysis/bot_usage/discover_bot_accounts.ts +64 -9
- package/analysis/bot_usage/kibana_bot_queries.ts +40 -1
- package/analysis/chart_utils.ts +6 -3
- package/analysis/grid_correction_check.ts +932 -0
- package/analysis/resolve_bot_accounts.ts +199 -0
- package/analysis/trade_profitability.ts +36 -8
- package/analysis/tradingview/README.md +25 -0
- package/analysis/tradingview/analyze_tradingview.ts +2 -2
- package/analysis/trend_detection/package.json +1 -1
- package/claw/modules/claw_launcher.ts +5 -4
- package/claw/modules/credit_runtime_adapter.ts +2 -1
- package/claw/modules/dexbot_profiles.ts +4 -3
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/analysis/analyze_derivatives.js +1 -1
- package/dist/analysis/bot_key_utils.d.ts +28 -1
- package/dist/analysis/bot_key_utils.d.ts.map +1 -1
- package/dist/analysis/bot_key_utils.js +385 -3
- package/dist/analysis/bot_key_utils.js.map +1 -1
- package/dist/analysis/bot_usage/discover_bot_accounts.js +60 -10
- package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +45 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js +39 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
- package/dist/analysis/chart_utils.d.ts +2 -1
- package/dist/analysis/chart_utils.d.ts.map +1 -1
- package/dist/analysis/chart_utils.js +4 -1
- package/dist/analysis/chart_utils.js.map +1 -1
- package/dist/analysis/grid_correction_check.d.ts +90 -0
- package/dist/analysis/grid_correction_check.d.ts.map +1 -0
- package/dist/analysis/grid_correction_check.js +901 -0
- package/dist/analysis/grid_correction_check.js.map +1 -0
- package/dist/analysis/resolve_bot_accounts.d.ts +3 -0
- package/dist/analysis/resolve_bot_accounts.d.ts.map +1 -0
- package/dist/analysis/resolve_bot_accounts.js +216 -0
- package/dist/analysis/resolve_bot_accounts.js.map +1 -0
- package/dist/analysis/trade_profitability.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.js +40 -8
- package/dist/analysis/trade_profitability.js.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.js +2 -2
- package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +2 -1
- package/dist/bot.js.map +1 -1
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +134 -22
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/ama_signal_runner.js +2 -2
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +2 -1
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +4 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +3 -2
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/modules/account_bots.d.ts +42 -1
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +221 -22
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/account_orders.d.ts +7 -10
- package/dist/modules/account_orders.d.ts.map +1 -1
- package/dist/modules/account_orders.js +26 -2
- package/dist/modules/account_orders.js.map +1 -1
- package/dist/modules/bitshares_client.d.ts +4 -1
- package/dist/modules/bitshares_client.d.ts.map +1 -1
- package/dist/modules/bitshares_client.js +12 -4
- package/dist/modules/bitshares_client.js.map +1 -1
- package/dist/modules/bot_settings.d.ts.map +1 -1
- package/dist/modules/bot_settings.js +2 -1
- package/dist/modules/bot_settings.js.map +1 -1
- package/dist/modules/chain_orders.d.ts.map +1 -1
- package/dist/modules/chain_orders.js +21 -2
- package/dist/modules/chain_orders.js.map +1 -1
- package/dist/modules/cli_colors.d.ts +39 -0
- package/dist/modules/cli_colors.d.ts.map +1 -0
- package/dist/modules/cli_colors.js +42 -0
- package/dist/modules/cli_colors.js.map +1 -0
- package/dist/modules/config.d.ts.map +1 -1
- package/dist/modules/config.js +27 -0
- package/dist/modules/config.js.map +1 -1
- package/dist/modules/constants.d.ts +14 -1
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +66 -11
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_pricing.d.ts +61 -0
- package/dist/modules/credit_pricing.d.ts.map +1 -0
- package/dist/modules/credit_pricing.js +237 -0
- package/dist/modules/credit_pricing.js.map +1 -0
- package/dist/modules/credit_runtime.d.ts +1 -0
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +49 -107
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +25 -0
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +82 -12
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +39 -17
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +875 -134
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +11 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +108 -1
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +29 -14
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +279 -63
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_startup_runtime.d.ts +1 -0
- package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_startup_runtime.js +58 -7
- 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 +55 -3
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/fund_registry.d.ts +1 -1
- package/dist/modules/fund_registry.js +1 -1
- package/dist/modules/graceful_shutdown.d.ts +12 -1
- package/dist/modules/graceful_shutdown.d.ts.map +1 -1
- package/dist/modules/graceful_shutdown.js +39 -1
- package/dist/modules/graceful_shutdown.js.map +1 -1
- package/dist/modules/key_store.d.ts.map +1 -1
- package/dist/modules/key_store.js +38 -1
- package/dist/modules/key_store.js.map +1 -1
- package/dist/modules/launcher/adapter_requirement.d.ts +38 -0
- package/dist/modules/launcher/adapter_requirement.d.ts.map +1 -0
- package/dist/modules/launcher/adapter_requirement.js +141 -0
- package/dist/modules/launcher/adapter_requirement.js.map +1 -0
- package/dist/modules/launcher/bot_supervisor.d.ts +2 -2
- package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -1
- package/dist/modules/launcher/bot_supervisor.js +32 -3
- package/dist/modules/launcher/bot_supervisor.js.map +1 -1
- package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
- package/dist/modules/launcher/launch_modes.js +5 -4
- package/dist/modules/launcher/launch_modes.js.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +12 -8
- package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
- package/dist/modules/launcher/status_reporting.d.ts +6 -6
- package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
- package/dist/modules/launcher/status_reporting.js +9 -6
- package/dist/modules/launcher/status_reporting.js.map +1 -1
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +20 -3
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/export.d.ts +1 -1
- package/dist/modules/order/export.js +1 -1
- package/dist/modules/order/grid.d.ts +1 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +335 -43
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts +4 -2
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +242 -222
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +11 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +132 -6
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/logger.d.ts +23 -1
- package/dist/modules/order/logger.d.ts.map +1 -1
- package/dist/modules/order/logger.js +60 -17
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts +68 -14
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +281 -52
- 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 +25 -8
- 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 +262 -167
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +47 -5
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +175 -11
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +16 -15
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +199 -32
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/slot.d.ts +8 -0
- package/dist/modules/order/utils/slot.d.ts.map +1 -0
- package/dist/modules/order/utils/slot.js +16 -0
- package/dist/modules/order/utils/slot.js.map +1 -0
- package/dist/modules/order/utils/system.d.ts +52 -4
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +109 -16
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/validate.d.ts +6 -9
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +107 -85
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/order/working_grid.d.ts +5 -0
- package/dist/modules/order/working_grid.d.ts.map +1 -1
- package/dist/modules/order/working_grid.js +21 -0
- package/dist/modules/order/working_grid.js.map +1 -1
- package/dist/modules/paths.d.ts +4 -2
- package/dist/modules/paths.d.ts.map +1 -1
- package/dist/modules/paths.js +79 -24
- package/dist/modules/paths.js.map +1 -1
- package/dist/modules/utils/chain_logs.d.ts +11 -0
- package/dist/modules/utils/chain_logs.d.ts.map +1 -0
- package/dist/modules/utils/chain_logs.js +23 -0
- package/dist/modules/utils/chain_logs.js.map +1 -0
- package/dist/modules/utils/sanitize_key.d.ts +7 -0
- package/dist/modules/utils/sanitize_key.d.ts.map +1 -1
- package/dist/modules/utils/sanitize_key.js +15 -0
- package/dist/modules/utils/sanitize_key.js.map +1 -1
- package/dist/pm2.d.ts +11 -1
- package/dist/pm2.d.ts.map +1 -1
- package/dist/pm2.js +66 -15
- package/dist/pm2.js.map +1 -1
- package/dist/scripts/analyze-credit.d.ts +4 -0
- package/dist/scripts/analyze-credit.d.ts.map +1 -0
- package/dist/scripts/analyze-credit.js +760 -0
- package/dist/scripts/analyze-credit.js.map +1 -0
- package/dist/scripts/analyze-orders.d.ts +1 -10
- package/dist/scripts/analyze-orders.d.ts.map +1 -1
- package/dist/scripts/analyze-orders.js +34 -36
- package/dist/scripts/analyze-orders.js.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.d.ts +2 -5
- package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.js +83 -1
- package/dist/scripts/generate_market_adapter_whitelist.js.map +1 -1
- package/dist/scripts/test-credit-renewal.d.ts.map +1 -1
- package/dist/scripts/test-credit-renewal.js +37 -5
- package/dist/scripts/test-credit-renewal.js.map +1 -1
- package/dist/scripts/tv.d.ts +11 -0
- package/dist/scripts/tv.d.ts.map +1 -0
- package/dist/scripts/tv.js +357 -0
- package/dist/scripts/tv.js.map +1 -0
- package/dist/scripts/update.js +77 -10
- package/dist/scripts/update.js.map +1 -1
- package/dist/unlock.d.ts.map +1 -1
- package/dist/unlock.js +20 -8
- package/dist/unlock.js.map +1 -1
- package/docs/DEXBOT_COMPARISON.md +11 -11
- package/docs/EVOLUTION.md +10 -8
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/GRID_RECALCULATION.md +6 -0
- package/docs/MPA_CREDIT_USAGE.md +3 -1
- package/docs/README.md +2 -2
- package/docs/WORKFLOW.md +3 -1
- package/market_adapter/README.md +10 -0
- package/package.json +3 -1
- package/scripts/README.md +16 -0
|
@@ -110,7 +110,7 @@ function _snapshotFundState(manager) {
|
|
|
110
110
|
sellLocked: Number(manager.accountTotals?.sellLocked || 0),
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
import { floatToBlockchainInt, getPrecisionByOrderType, getPrecisionsForManager, calculateOrderCreationFees, calculateOrderSizes, calculateRotationOrderSizes, calculateAvailableFundsValue, calculateGridSideDivergenceMetric, getPrecisionSlack, getMinOrderSize, getSingleDustThreshold, getGridBestPrices, calculateSpreadFromOrders, allocateFundsByWeights, calculateGapSlots as _mathGapSlots,
|
|
113
|
+
import { floatToBlockchainInt, getPrecisionByOrderType, getPrecisionsForManager, calculateOrderCreationFees, calculateOrderSizes, calculateRotationOrderSizes, calculateAvailableFundsValue, calculateGridSideDivergenceMetric, getPrecisionSlack, getMinOrderSize, getSingleDustThreshold, getGridBestPrices, calculateSpreadFromOrders, allocateFundsByWeights, calculateGapSlots as _mathGapSlots, priceSlotEqual, getBtsSide, getSellStartIdx, resolveGapBand, countGapBandSpread, validatePersistedBoundary, adjustBudgetForBtsFees, clamp, buildGenesisFromPriceLevels, assertSlotPriceInvariant, hashPriceLevels, } from './utils/math.js';
|
|
114
114
|
import { filterOrdersByType, checkSizesBeforeMinimum, checkSizeThreshold, resolveConfiguredPriceBound, shouldFlagOutOfSpread, isOrderHealthy, isPhantomOrder, isSlotAvailable, isOrderOnChain, isOrderPlaced, hasOnChainId, isEmptyGridSlot, parseSlotIndex, calculateIdealBoundary, assignGridRoles, resolveOnChainRetypeType } from './utils/order.js';
|
|
115
115
|
import { loadAmaCenterPrice, loadAmaCenterSnapshot, withBlockchainRetry } from './utils/system.js';
|
|
116
116
|
import * as MathUtils from './utils/math.js';
|
|
@@ -361,6 +361,21 @@ export function createOrderGrid(config) {
|
|
|
361
361
|
}
|
|
362
362
|
// Sort all levels from lowest to highest (Master Rail order)
|
|
363
363
|
priceLevels.sort((a, b) => a - b);
|
|
364
|
+
// Dedupe geometric levels that collide at float precision (tiny increments);
|
|
365
|
+
// keeps slot-N ↔ index mapping stable vs migration dedupe (grid.ts:652)
|
|
366
|
+
{
|
|
367
|
+
const seen = new Set();
|
|
368
|
+
const deduped = [];
|
|
369
|
+
for (const p of priceLevels) {
|
|
370
|
+
const key = Number(p).toFixed(12);
|
|
371
|
+
if (!seen.has(key)) {
|
|
372
|
+
seen.add(key);
|
|
373
|
+
deduped.push(p);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
priceLevels.length = 0;
|
|
377
|
+
priceLevels.push(...deduped);
|
|
378
|
+
}
|
|
364
379
|
if (priceLevels.length === 0) {
|
|
365
380
|
throw new Error(`Grid generation produced no price levels for startPrice=${startPrice}, ` +
|
|
366
381
|
`bounds=[${minPrice}, ${maxPrice}], incrementPercent=${incrementPercent}. ` +
|
|
@@ -402,7 +417,8 @@ export function createOrderGrid(config) {
|
|
|
402
417
|
buy: Math.floor(gapSlots / 2),
|
|
403
418
|
sell: gapSlots - Math.floor(gapSlots / 2)
|
|
404
419
|
};
|
|
405
|
-
|
|
420
|
+
const genesis = buildGenesisFromPriceLevels(startPrice, incrementPercent, gapSlots, priceLevels);
|
|
421
|
+
return { orders: updatedOrders, boundaryIdx, initialSpreadCount, gapSlots, priceLevels, genesis };
|
|
406
422
|
}
|
|
407
423
|
/**
|
|
408
424
|
* Internal utility to clear all order-related manager caches.
|
|
@@ -442,10 +458,165 @@ function _clearOrderCachesLogic(manager) {
|
|
|
442
458
|
* @param {number|null} [boundaryIdx=null] - The master boundary index.
|
|
443
459
|
* @returns {Promise<void>}
|
|
444
460
|
*/
|
|
445
|
-
export async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
461
|
+
export async function loadGrid(manager, grid, boundaryIdx = null, genesisInput = null) {
|
|
446
462
|
if (!Array.isArray(grid))
|
|
447
463
|
return;
|
|
448
464
|
return await manager._gridLock.acquire(async () => {
|
|
465
|
+
// Genesis determinism: if snapshot provided genesis, validate slots
|
|
466
|
+
// against it; if legacy snapshot has no genesis, migrate by building
|
|
467
|
+
// genesis from live config and re-sorting to canonical price order.
|
|
468
|
+
let genesis = genesisInput || manager._genesis || null;
|
|
469
|
+
// Validate genesis hash if present (wired for stale-pair detection #3)
|
|
470
|
+
// On mismatch we only warn — genesis is still used for validation (log mode) / virtualization (enforce mode);
|
|
471
|
+
// a stale persisted snapshot vs fresh manager._genesis scenario is covered by the log/enforce gate above
|
|
472
|
+
if (genesis && Array.isArray(genesis.priceLevels) && typeof genesis.priceLevelsHash === 'string') {
|
|
473
|
+
try {
|
|
474
|
+
const recomputed = hashPriceLevels(genesis.priceLevels);
|
|
475
|
+
if (recomputed !== genesis.priceLevelsHash) {
|
|
476
|
+
manager.logger?.log?.(`[GENESIS] hash mismatch persisted=${genesis.priceLevelsHash} recomputed=${recomputed} — persisted genesis hash does not match recomputed hash; continuing with persisted genesis`, 'warn');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch { }
|
|
480
|
+
}
|
|
481
|
+
const validationMode = (() => {
|
|
482
|
+
try {
|
|
483
|
+
const raw = (typeof process !== 'undefined' && process.env?.GRID_PRICE_SLOT_VALIDATION) || 'log';
|
|
484
|
+
return String(raw).toLowerCase() === 'enforce' ? 'enforce' : 'log';
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
return 'log';
|
|
488
|
+
}
|
|
489
|
+
})();
|
|
490
|
+
if (genesis && Array.isArray(genesis.priceLevels)) {
|
|
491
|
+
// Validate each slot's price against genesis; virtualize only in enforce mode (plan §13)
|
|
492
|
+
const newGrid = [];
|
|
493
|
+
for (const slot of grid) {
|
|
494
|
+
try {
|
|
495
|
+
assertSlotPriceInvariant(slot, genesis);
|
|
496
|
+
newGrid.push(slot);
|
|
497
|
+
}
|
|
498
|
+
catch (e) {
|
|
499
|
+
const msg = `[GENESIS] Slot ${slot?.id} price mismatch vs genesis → ${validationMode === 'enforce' ? 'virtualize' : 'log-only'}: ${getErrorMessage(e)}`;
|
|
500
|
+
manager.logger?.log?.(msg, 'warn');
|
|
501
|
+
if (validationMode === 'enforce') {
|
|
502
|
+
newGrid.push({ ...slot, state: ORDER_STATES.VIRTUAL, size: 0, orderId: '' });
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
newGrid.push(slot);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
grid = newGrid;
|
|
510
|
+
// Ensure canonical price-sorted order matches slot-N order
|
|
511
|
+
// Quirk fix: previously `isPriceSorted && parsed!==i` gated re-sort, so a shuffled
|
|
512
|
+
// array with misaligned ids but unsorted prices incorrectly skipped re-sort.
|
|
513
|
+
// Now: re-sort if any parseable id != index (price order is authoritative via slot-N).
|
|
514
|
+
const idxOrderMatchesPriceOrder = (() => {
|
|
515
|
+
for (let i = 0; i < grid.length; i++) {
|
|
516
|
+
const parsed = parseSlotIndex(grid[i]?.id);
|
|
517
|
+
if (parsed === null)
|
|
518
|
+
return false;
|
|
519
|
+
if (parsed !== i)
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
return true;
|
|
523
|
+
})();
|
|
524
|
+
if (!idxOrderMatchesPriceOrder) {
|
|
525
|
+
const sorted = [...grid].sort((a, b) => {
|
|
526
|
+
const pa = parseSlotIndex(a?.id);
|
|
527
|
+
const pb = parseSlotIndex(b?.id);
|
|
528
|
+
if (pa !== null && pb !== null)
|
|
529
|
+
return pa - pb;
|
|
530
|
+
return Number(a.price) - Number(b.price);
|
|
531
|
+
});
|
|
532
|
+
// Detect if re-sort actually changes order
|
|
533
|
+
let changed = false;
|
|
534
|
+
for (let i = 0; i < grid.length; i++)
|
|
535
|
+
if (grid[i].id !== sorted[i].id) {
|
|
536
|
+
changed = true;
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
if (changed) {
|
|
540
|
+
manager.logger?.log?.(`[GENESIS] Re-sorted persisted grid to canonical slot-N order`, 'warn');
|
|
541
|
+
grid = sorted;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// Guard: explicit persistedGenesis should not blindly overwrite a fresher in-memory _genesis
|
|
545
|
+
// from an in-process rebuild (currently unreachable but latent hazard if flows change)
|
|
546
|
+
if (manager._genesis && manager._genesis.priceLevelsHash && genesis.priceLevelsHash && manager._genesis.priceLevelsHash !== genesis.priceLevelsHash) {
|
|
547
|
+
manager.logger?.log?.(`[GENESIS] persisted genesis hash ${genesis.priceLevelsHash} differs from in-memory ${manager._genesis.priceLevelsHash} — keeping in-memory genesis`, 'warn');
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
manager._genesis = genesis;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else if (grid.length > 0) {
|
|
554
|
+
// Migration: build genesis from live geometric rail (plan §4.1/§10)
|
|
555
|
+
// Do NOT derive priceLevels from persisted slot prices — a truncated
|
|
556
|
+
// persisted array would permanently shrink genesis. Recompute from
|
|
557
|
+
// startPrice/min/max/increment per createOrderGrid §2.1.
|
|
558
|
+
const startPrice = Number(manager.config?.startPrice);
|
|
559
|
+
const minPrice = Number(manager.config?.minPrice);
|
|
560
|
+
const maxPrice = Number(manager.config?.maxPrice);
|
|
561
|
+
const incPct = Number(manager.config?.incrementPercent);
|
|
562
|
+
if (Number.isFinite(startPrice) && Number.isFinite(minPrice) && Number.isFinite(maxPrice) && Number.isFinite(incPct)) {
|
|
563
|
+
try {
|
|
564
|
+
const stepUp = 1 + (incPct / 100);
|
|
565
|
+
const stepDown = 1 - (incPct / 100);
|
|
566
|
+
const priceLevels = [];
|
|
567
|
+
let upPrice = startPrice * Math.sqrt(stepUp);
|
|
568
|
+
while (upPrice <= maxPrice) {
|
|
569
|
+
priceLevels.push(upPrice);
|
|
570
|
+
upPrice *= stepUp;
|
|
571
|
+
}
|
|
572
|
+
let downPrice = startPrice * Math.sqrt(stepDown);
|
|
573
|
+
while (downPrice >= minPrice) {
|
|
574
|
+
priceLevels.push(downPrice);
|
|
575
|
+
downPrice *= stepDown;
|
|
576
|
+
}
|
|
577
|
+
priceLevels.sort((a, b) => a - b);
|
|
578
|
+
// Dedupe (floatToBlockchainInt equality via fixed 12-decimals)
|
|
579
|
+
const uniq = [];
|
|
580
|
+
const seen = new Set();
|
|
581
|
+
for (const p of priceLevels) {
|
|
582
|
+
const key = Number(p).toFixed(12);
|
|
583
|
+
if (!seen.has(key)) {
|
|
584
|
+
seen.add(key);
|
|
585
|
+
uniq.push(p);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
const gapSlotsForGenesis = calculateGapSlots(incPct, manager.config?.targetSpreadPercent, manager.config?.gridLimits);
|
|
589
|
+
const built = buildGenesisFromPriceLevels(startPrice, incPct, gapSlotsForGenesis, uniq);
|
|
590
|
+
// Cross-check: if user edited startPrice/min/max/increment across restarts on a legacy snapshot,
|
|
591
|
+
// the new-config rail will mismatch most persisted slot prices → noisy log / mass-virtualize
|
|
592
|
+
// in enforce mode and the mismatched genesis would be persisted. Count failures first.
|
|
593
|
+
let mismatchCount = 0;
|
|
594
|
+
for (const slot of grid) {
|
|
595
|
+
try {
|
|
596
|
+
assertSlotPriceInvariant(slot, built);
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
mismatchCount++;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const mismatchRatio = grid.length > 0 ? mismatchCount / grid.length : 0;
|
|
603
|
+
if (mismatchRatio > 0.5) {
|
|
604
|
+
manager.logger?.log?.(`[GENESIS] Migration: ${mismatchCount}/${grid.length} slots mismatch new-config rail (ratio ${mismatchRatio.toFixed(2)}) — config may have changed since snapshot; NOT adopting migration genesis (validation would ${validationMode === 'enforce' ? 'mass-virtualize' : 'be noisy'}). Persisted grid will be kept as-is until a clean rebuild`, 'warn');
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
if (mismatchCount > 0) {
|
|
608
|
+
manager.logger?.log?.(`[GENESIS] Migration: ${mismatchCount}/${grid.length} slots mismatch new-config rail — will be logged${validationMode === 'enforce' ? '/virtualized' : ''} on next load`, 'warn');
|
|
609
|
+
}
|
|
610
|
+
manager._genesis = built;
|
|
611
|
+
genesis = built;
|
|
612
|
+
manager.logger?.log?.(`[GENESIS] Migrated legacy grid: built genesis with ${uniq.length} levels (hash ${built.priceLevelsHash})`, 'info');
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch (e) {
|
|
616
|
+
manager.logger?.log?.(`[GENESIS] Migration failed: ${getErrorMessage(e)}`, 'warn');
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
449
620
|
try {
|
|
450
621
|
await withBlockchainRetry(() => manager._initializeAssets(), 'initializeAssets', { logger: manager.logger });
|
|
451
622
|
}
|
|
@@ -517,11 +688,10 @@ export async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
517
688
|
manager.logger?.log?.(`Restored boundary index: ${restoredBoundary}`, 'info');
|
|
518
689
|
}
|
|
519
690
|
// Reassign slot types based on current boundary.
|
|
520
|
-
// Every slot's type must match its
|
|
521
|
-
// relative to the boundary + gapSlots
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
// slot holding a SELL chain order) and auto-cancel + recreate them.
|
|
691
|
+
// Every slot's type must match its price-slot index (parseSlotIndex)
|
|
692
|
+
// relative to the boundary + gapSlots, not array position. After
|
|
693
|
+
// genesis freeze the persisted array may have been re-sorted, but
|
|
694
|
+
// slot-N is the canonical identity.
|
|
525
695
|
if (restoredBoundary !== null) {
|
|
526
696
|
const gapSlots = loadGapSlots;
|
|
527
697
|
manager._gapSlots = gapSlots;
|
|
@@ -529,9 +699,22 @@ export async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
529
699
|
const sellStartIdx = getSellStartIdx(restoredBoundary, gapSlots);
|
|
530
700
|
let reassignCount = 0;
|
|
531
701
|
grid = grid.map((slot, i) => {
|
|
532
|
-
const
|
|
702
|
+
const parsedIdx = parseSlotIndex(slot?.id);
|
|
703
|
+
// Plan §4.3: unparseable ids are invalid per decision #3 — enforce virtualizes,
|
|
704
|
+
// log mode keeps index fallback (legacy grids with ids like 'b1'/'planned' need type via position)
|
|
705
|
+
if (parsedIdx === null) {
|
|
706
|
+
if (validationMode === 'enforce') {
|
|
707
|
+
manager.logger?.log?.(`[GENESIS] unparseable slot id ${slot?.id} at index ${i} → virtualize (SPREAD)`, 'warn');
|
|
708
|
+
reassignCount++;
|
|
709
|
+
return { ...slot, state: ORDER_STATES.VIRTUAL, size: 0, orderId: '', type: ORDER_TYPES.SPREAD };
|
|
710
|
+
}
|
|
711
|
+
manager.logger?.log?.(`[GENESIS] unparseable slot id ${slot?.id} at index ${i} → log-only, using index fallback ${i} for type`, 'warn');
|
|
712
|
+
// fall through with idx = i so legacy sized VIRTUAL rail slots (e.g. 'planned') keep their size/type
|
|
713
|
+
}
|
|
714
|
+
const idx = parsedIdx !== null ? parsedIdx : i;
|
|
715
|
+
const correctType = (idx <= buyEndIdx)
|
|
533
716
|
? ORDER_TYPES.BUY
|
|
534
|
-
: (
|
|
717
|
+
: (idx >= sellStartIdx)
|
|
535
718
|
? ORDER_TYPES.SELL
|
|
536
719
|
: ORDER_TYPES.SPREAD;
|
|
537
720
|
// DEFENSIVE BACKSTOP: a VIRTUAL slot with no orderId and
|
|
@@ -570,7 +753,7 @@ export async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
570
753
|
// comparison false and wrongly resolves below-center
|
|
571
754
|
// slots to SELL.
|
|
572
755
|
if (newType === ORDER_TYPES.SPREAD && isOrderOnChain(slot)) {
|
|
573
|
-
newType = resolveOnChainRetypeType(slot,
|
|
756
|
+
newType = resolveOnChainRetypeType(slot, idx, buyEndIdx, ORDER_TYPES);
|
|
574
757
|
}
|
|
575
758
|
reassignCount++;
|
|
576
759
|
return { ...slot, type: newType };
|
|
@@ -605,6 +788,7 @@ export async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
605
788
|
}
|
|
606
789
|
manager.pauseRecalcLogging();
|
|
607
790
|
manager.pauseFundRecalc();
|
|
791
|
+
let sanitizedSizedVirtual = [];
|
|
608
792
|
try {
|
|
609
793
|
// RC-2: Use applyOrderUpdate (PRIVATE/UNLOCKED)
|
|
610
794
|
for (const order of grid) {
|
|
@@ -613,8 +797,31 @@ export async function loadGrid(manager, grid, boundaryIdx = null) {
|
|
|
613
797
|
manager.logger?.log?.(`Sanitizing corrupted order ${order.id}: ACTIVE/PARTIAL without orderId -> VIRTUAL`, 'warn');
|
|
614
798
|
currentOrder = { ...order, state: ORDER_STATES.VIRTUAL };
|
|
615
799
|
}
|
|
800
|
+
// Sized VIRTUAL slot with no on-chain id AND durable orphan
|
|
801
|
+
// evidence (createUncertain: set only where a CREATE
|
|
802
|
+
// broadcast result was lost — COW uncertain-restore and
|
|
803
|
+
// startup uncertain-create handlers). Unflagged sized
|
|
804
|
+
// VIRTUAL slots are the NORMAL planned-but-unplaced grid
|
|
805
|
+
// state (budgeted sizes cover the whole rail and persist
|
|
806
|
+
// by design), so their sizes must survive reload; only
|
|
807
|
+
// flagged orphans get zeroed so re-derivation or spread
|
|
808
|
+
// correction re-places them next cycle. One aggregate
|
|
809
|
+
// warn instead of per-slot spam.
|
|
810
|
+
if (currentOrder.state === ORDER_STATES.VIRTUAL
|
|
811
|
+
&& !hasOnChainId(currentOrder)
|
|
812
|
+
&& Number(currentOrder.size || 0) > 0
|
|
813
|
+
&& currentOrder.createUncertain === true) {
|
|
814
|
+
sanitizedSizedVirtual.push(currentOrder.id);
|
|
815
|
+
currentOrder = { ...currentOrder, size: 0, createUncertain: false };
|
|
816
|
+
}
|
|
616
817
|
await manager._applyOrderUpdate(currentOrder, 'grid-load', { skipAccounting: true });
|
|
617
818
|
}
|
|
819
|
+
if (sanitizedSizedVirtual.length > 0) {
|
|
820
|
+
const preview = sanitizedSizedVirtual.slice(0, 10).join(', ');
|
|
821
|
+
manager.logger?.log?.(`Sanitized ${sanitizedSizedVirtual.length} creation-uncertain sized slot(s) on load ` +
|
|
822
|
+
`(VIRTUAL with size but no orderId -> size dropped): ${preview}` +
|
|
823
|
+
(sanitizedSizedVirtual.length > 10 ? ' …' : ''), 'warn');
|
|
824
|
+
}
|
|
618
825
|
// Gap-band occupancy for the spread metric. Empty slots are
|
|
619
826
|
// normalized to SPREAD (side-neutral) above, so a raw
|
|
620
827
|
// `type === SPREAD` count would include every empty slot on both
|
|
@@ -901,13 +1108,15 @@ export async function initializeGrid(manager) {
|
|
|
901
1108
|
// Continuing would create grid with 0 fund allocation, rendering it non-functional
|
|
902
1109
|
throw new Error(`Cannot initialize grid without account totals: ${getErrorMessage(e)}`);
|
|
903
1110
|
}
|
|
904
|
-
const { orders, boundaryIdx, initialSpreadCount, gapSlots } = createOrderGrid({
|
|
1111
|
+
const { orders, boundaryIdx, initialSpreadCount, gapSlots, genesis } = createOrderGrid({
|
|
905
1112
|
...manager.config,
|
|
906
1113
|
startPrice: gridStartPrice,
|
|
907
1114
|
minPrice: resolvedMinP,
|
|
908
1115
|
maxPrice: resolvedMaxP,
|
|
909
1116
|
});
|
|
910
1117
|
manager._gapSlots = gapSlots;
|
|
1118
|
+
if (genesis)
|
|
1119
|
+
manager._genesis = genesis;
|
|
911
1120
|
// RC-8: Update boundary with notification to dependent systems
|
|
912
1121
|
// Persist master boundary for StrategyEngine
|
|
913
1122
|
if (manager.boundaryIdx !== boundaryIdx) {
|
|
@@ -1063,6 +1272,18 @@ export async function recalculateGrid(manager, opts) {
|
|
|
1063
1272
|
manager.logger?.log?.(`Error during startup order reconciliation: ${getErrorMessage(err)}`, 'error');
|
|
1064
1273
|
throw new Error(`Grid recalculation failed during order reconciliation: ${getErrorMessage(err)}`);
|
|
1065
1274
|
}
|
|
1275
|
+
// Fix #5 (Mode D): the P3 boundary-evidence gate above may have
|
|
1276
|
+
// re-derived the boundary via manager._restoreBoundary. recalculateGrid's
|
|
1277
|
+
// earlier persistGrid (before initializeGrid) captured the stale value, so
|
|
1278
|
+
// without this flush the corrected boundary never reaches disk and the
|
|
1279
|
+
// [BOUNDARY-EVIDENCE] contradiction re-fires on every resync. Persist the
|
|
1280
|
+
// reconciled grid (corrected boundary included) now.
|
|
1281
|
+
try {
|
|
1282
|
+
await manager.persistGrid(undefined);
|
|
1283
|
+
}
|
|
1284
|
+
catch (persistErr) {
|
|
1285
|
+
manager.logger?.log?.(`Error persisting boundary-corrected grid after reconcile: ${getErrorMessage(persistErr)}`, 'warn');
|
|
1286
|
+
}
|
|
1066
1287
|
if (_resyncAborted)
|
|
1067
1288
|
return;
|
|
1068
1289
|
manager.logger?.log?.('Full resync complete.', 'info');
|
|
@@ -1674,6 +1895,15 @@ export async function checkSpreadCondition(manager, _BitShares, updateOrdersOnCh
|
|
|
1674
1895
|
// Limit spread = nominal + half increment tolerance (0.5 steps).
|
|
1675
1896
|
const limitSpread = nominalSpread + (manager.config.incrementPercent * toleranceSteps);
|
|
1676
1897
|
manager.logger?.log?.(`Spread too wide (${Format.formatPercent(currentSpread)} > ${Format.formatPercent(limitSpread)}), correcting with ${manager.outOfSpread} extra slot(s)...`, 'warn');
|
|
1898
|
+
// Refresh funds before the side decision below: the top-of-tick recalc may
|
|
1899
|
+
// predate fills processed since, and determineOrderSideByFunds reads
|
|
1900
|
+
// manager.funds/accountTotals directly (not via _getSizingContext). No-op
|
|
1901
|
+
// while a fill cycle holds the recalc pause; funds that move after this
|
|
1902
|
+
// point are covered by the TOCTOU re-plan before broadcast.
|
|
1903
|
+
try {
|
|
1904
|
+
await manager.recalculateFunds();
|
|
1905
|
+
}
|
|
1906
|
+
catch { /* best-effort */ }
|
|
1677
1907
|
const decision = determineOrderSideByFunds(manager, lastPrice);
|
|
1678
1908
|
if (!decision.side)
|
|
1679
1909
|
return false;
|
|
@@ -1838,13 +2068,25 @@ export async function checkWindowDust(manager) {
|
|
|
1838
2068
|
const topSellOrder = allOrders
|
|
1839
2069
|
.filter((o) => o.type === ORDER_TYPES.SELL && isLiveOrder(o))
|
|
1840
2070
|
.sort((a, b) => a.price - b.price)[0];
|
|
1841
|
-
// Check if an order has a duplicate
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2071
|
+
// Check if an order has a duplicate slot — an active sibling with same price via integer round-trip.
|
|
2072
|
+
const hasDuplicatePriceLevel = (order, assets) => {
|
|
2073
|
+
const precision = order.type === ORDER_TYPES.SELL ? assets.assetA.precision : assets.assetB.precision;
|
|
2074
|
+
for (const o of allOrders) {
|
|
2075
|
+
if (o.id === order.id)
|
|
2076
|
+
continue;
|
|
2077
|
+
if (o.type !== order.type || o.state !== ORDER_STATES.ACTIVE || !o.orderId || o.price == null)
|
|
2078
|
+
continue;
|
|
2079
|
+
try {
|
|
2080
|
+
if (priceSlotEqual(o.price, order.price, precision))
|
|
2081
|
+
return true;
|
|
2082
|
+
}
|
|
2083
|
+
catch {
|
|
2084
|
+
if (o.price === order.price)
|
|
2085
|
+
return true;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return false;
|
|
2089
|
+
};
|
|
1848
2090
|
const assets = manager.assets;
|
|
1849
2091
|
const allPartials = allOrders.filter((o) => isLiveOrder(o) && o.state === ORDER_STATES.PARTIAL);
|
|
1850
2092
|
const isTopBuy = (o) => topBuyOrder && o.id === topBuyOrder.id;
|
|
@@ -1998,15 +2240,26 @@ export function determineOrderSideByFunds(manager, currentMarketPrice) {
|
|
|
1998
2240
|
const buyViable = buyAvailable > buyMinUnit;
|
|
1999
2241
|
const sellViable = sellAvailable > sellMinUnit;
|
|
2000
2242
|
let side = null;
|
|
2243
|
+
let skipReason = null;
|
|
2001
2244
|
if (buyViable && sellViable) {
|
|
2002
2245
|
// Normalize sell (assetA) to assetB units using market price so both sides
|
|
2003
2246
|
// are comparable. Without this, a raw number comparison (e.g. 2192 BTS vs
|
|
2004
2247
|
// 0.12 XRP) always picks BUY even when the sell side is larger in value.
|
|
2248
|
+
// When the market price is unavailable (non-finite, e.g. empty book after
|
|
2249
|
+
// a crash wiped one side), raw-unit comparison across assets is meaningless
|
|
2250
|
+
// and can arm the wrong side in exactly the scenario where price may be
|
|
2251
|
+
// missing. Skip the correction and let the next cycle decide with a real
|
|
2252
|
+
// price rather than guess.
|
|
2005
2253
|
const marketPrice = Number(currentMarketPrice);
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2254
|
+
if (!Number.isFinite(marketPrice) || marketPrice <= 0) {
|
|
2255
|
+
skipReason =
|
|
2256
|
+
`market price unavailable (${String(currentMarketPrice)}) with funds on both sides ` +
|
|
2257
|
+
`(buy=${Format.formatAmount8(buyAvailable)}, sell=${Format.formatAmount8(sellAvailable)})`;
|
|
2258
|
+
}
|
|
2259
|
+
else {
|
|
2260
|
+
const sellInBuyUnits = sellAvailable * marketPrice;
|
|
2261
|
+
side = buyAvailable >= sellInBuyUnits ? ORDER_TYPES.BUY : ORDER_TYPES.SELL;
|
|
2262
|
+
}
|
|
2010
2263
|
}
|
|
2011
2264
|
else if (buyViable) {
|
|
2012
2265
|
side = ORDER_TYPES.BUY;
|
|
@@ -2014,6 +2267,10 @@ export function determineOrderSideByFunds(manager, currentMarketPrice) {
|
|
|
2014
2267
|
else if (sellViable) {
|
|
2015
2268
|
side = ORDER_TYPES.SELL;
|
|
2016
2269
|
}
|
|
2270
|
+
if (skipReason) {
|
|
2271
|
+
manager.logger?.log?.(`Spread correction skipped: ${skipReason}; refusing to compare raw cross-asset units`, 'warn');
|
|
2272
|
+
return { side: null, reason: skipReason };
|
|
2273
|
+
}
|
|
2017
2274
|
if (!side) {
|
|
2018
2275
|
const committedBuy = Math.max(0, Number(manager.funds?.committed?.chain?.buy || 0));
|
|
2019
2276
|
const committedSell = Math.max(0, Number(manager.funds?.committed?.chain?.sell || 0));
|
|
@@ -2032,19 +2289,30 @@ export function determineOrderSideByFunds(manager, currentMarketPrice) {
|
|
|
2032
2289
|
side = ORDER_TYPES.SELL;
|
|
2033
2290
|
}
|
|
2034
2291
|
else {
|
|
2035
|
-
//
|
|
2036
|
-
side
|
|
2292
|
+
// Both sides hold committed inventory but market valuation is
|
|
2293
|
+
// unavailable — leave side null (skip) rather than default to
|
|
2294
|
+
// BUY. A deterministic cross-asset guess is exactly the
|
|
2295
|
+
// wrong-side risk above; the next cycle with a real price
|
|
2296
|
+
// decides. Carry the reason so the bottom skip log does not
|
|
2297
|
+
// misreport this branch as "no committed inventory".
|
|
2298
|
+
skipReason =
|
|
2299
|
+
`market price unavailable (${String(currentMarketPrice)}) with committed inventory on both sides ` +
|
|
2300
|
+
`(committed buy=${Format.formatAmount8(committedBuy)}, committed sell=${Format.formatAmount8(committedSell)})`;
|
|
2301
|
+
}
|
|
2302
|
+
if (side) {
|
|
2303
|
+
manager.logger?.log?.(`Spread correction using redistribution fallback on ${side} ` +
|
|
2304
|
+
`(free buy=${Format.formatAmount8(buyAvailable)}, free sell=${Format.formatAmount8(sellAvailable)}, ` +
|
|
2305
|
+
`price=${hasValidPrice ? Format.formatAmount8(marketPrice) : 'unavailable'})`, 'info');
|
|
2037
2306
|
}
|
|
2038
|
-
manager.logger?.log?.(`Spread correction using redistribution fallback on ${side} ` +
|
|
2039
|
-
`(free buy=${Format.formatAmount8(buyAvailable)}, free sell=${Format.formatAmount8(sellAvailable)}, ` +
|
|
2040
|
-
`price=${hasValidPrice ? Format.formatAmount8(marketPrice) : 'unavailable'})`, 'info');
|
|
2041
2307
|
}
|
|
2042
2308
|
}
|
|
2043
2309
|
if (!side) {
|
|
2044
|
-
manager.logger?.log?.(
|
|
2045
|
-
`
|
|
2310
|
+
manager.logger?.log?.(skipReason
|
|
2311
|
+
? `Spread correction skipped: ${skipReason}`
|
|
2312
|
+
: `Spread correction skipped: insufficient free funds and no committed inventory to redistribute ` +
|
|
2313
|
+
`(buy=${Format.formatAmount8(buyAvailable)}, sell=${Format.formatAmount8(sellAvailable)})`, 'warn');
|
|
2046
2314
|
}
|
|
2047
|
-
return { side, reason: side ? `Choosing ${side}` : 'Insufficient funds or committed inventory' };
|
|
2315
|
+
return { side, reason: side ? `Choosing ${side}` : (skipReason || 'Insufficient funds or committed inventory') };
|
|
2048
2316
|
}
|
|
2049
2317
|
/**
|
|
2050
2318
|
* Resolve the MIN_SPREAD_ORDERS gap reserve used by boundary promotion.
|
|
@@ -2184,9 +2452,10 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
|
|
|
2184
2452
|
.sort((a, b) => railType === ORDER_TYPES.BUY ? a.price - b.price : b.price - a.price)
|
|
2185
2453
|
.slice(0, missingSlots);
|
|
2186
2454
|
// Secondary candidates: orphaned virtual slots that have lost their
|
|
2187
|
-
// order (e.g. stale-cleaned after a race condition during a crash
|
|
2188
|
-
//
|
|
2189
|
-
//
|
|
2455
|
+
// order (e.g. stale-cleaned after a race condition during a crash, or a
|
|
2456
|
+
// lowest-sell slot dust-cancelled and then left unplaced after a
|
|
2457
|
+
// boundary shift). These sit inside the active window and are invisible
|
|
2458
|
+
// to the gap-band SPREAD filter above.
|
|
2190
2459
|
//
|
|
2191
2460
|
// Empty slots are stored SPREAD (side-neutral) after normalization, so
|
|
2192
2461
|
// the stored type can be railType OR SPREAD — what matters is that the
|
|
@@ -2195,11 +2464,22 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
|
|
|
2195
2464
|
// whose boundary position has moved into the spread or opposite zone are
|
|
2196
2465
|
// NOT re-activated on the stale side — doing so would compound inventory
|
|
2197
2466
|
// at prices where the bot already traded.
|
|
2467
|
+
//
|
|
2468
|
+
// NOTE: we deliberately accept BOTH zero-size and sized virtual slots
|
|
2469
|
+
// here. A sized sell/buy slot that lost its orderId (the classic
|
|
2470
|
+
// boundary-oscillation orphan) has size > 0 but no on-chain order — the
|
|
2471
|
+
// old `size === 0` guard excluded it, so spread correction silently
|
|
2472
|
+
// planned 0 orders and the gap never closed. Dropping the guard is safe:
|
|
2473
|
+
// create targets are rebuilt from `current: 0` + the fresh ideal from
|
|
2474
|
+
// allocateFundsByWeights (grid.ts:2790-2800, 2861-2870), so a sized
|
|
2475
|
+
// orphan's stale stored size never leaks into a placement; getMinOrderSize
|
|
2476
|
+
// is only a collision-tolerance fallback (grid.ts:2712), not the primary
|
|
2477
|
+
// sizer. Orphans are excluded from the sideSlots sizing denominator too,
|
|
2478
|
+
// so this also removes the prior dilution of every other order.
|
|
2198
2479
|
const orphanedVirtualCandidates = allOrders
|
|
2199
2480
|
.filter((o) => (o.type === railType || o.type === ORDER_TYPES.SPREAD)
|
|
2200
2481
|
&& o.state === ORDER_STATES.VIRTUAL
|
|
2201
2482
|
&& !o.orderId
|
|
2202
|
-
&& Number(o.size || 0) === 0
|
|
2203
2483
|
&& getSlotCorrectType(o) === railType)
|
|
2204
2484
|
.sort((a, b) => railType === ORDER_TYPES.BUY ? b.price - a.price : a.price - b.price)
|
|
2205
2485
|
.slice(0, missingSlots);
|
|
@@ -2281,10 +2561,22 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
|
|
|
2281
2561
|
spreadCandidates = spreadCandidates.filter((c) => {
|
|
2282
2562
|
if (c.price == null)
|
|
2283
2563
|
return false;
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2564
|
+
const precision = railType === ORDER_TYPES.SELL ? manager.assets.assetA.precision : manager.assets.assetB.precision;
|
|
2565
|
+
for (const o of allOrders) {
|
|
2566
|
+
if (!isOrderPlaced(o) || o.price == null)
|
|
2567
|
+
continue;
|
|
2568
|
+
if (o.id === c.id)
|
|
2569
|
+
continue;
|
|
2570
|
+
try {
|
|
2571
|
+
if (priceSlotEqual(o.price, c.price, precision))
|
|
2572
|
+
return false;
|
|
2573
|
+
}
|
|
2574
|
+
catch {
|
|
2575
|
+
if (o.price === c.price)
|
|
2576
|
+
return false;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
return true;
|
|
2288
2580
|
});
|
|
2289
2581
|
const filteredCount = preFilter - spreadCandidates.length;
|
|
2290
2582
|
if (filteredCount > 0) {
|
|
@@ -2296,7 +2588,7 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
|
|
|
2296
2588
|
}
|
|
2297
2589
|
if (!edgePartial && spreadCandidates.length === 0) {
|
|
2298
2590
|
manager.logger?.log?.(`[SPREAD-CORRECTION] No suitable partials, orphaned virtual slots, or spread slots found. Skipping.`, 'warn');
|
|
2299
|
-
return { ordersToPlace: [], ordersToUpdate: [] };
|
|
2591
|
+
return { ordersToPlace: [], ordersToUpdate: [], origin: 'spread-correction' };
|
|
2300
2592
|
}
|
|
2301
2593
|
const orphanedIds = new Set(orphanedVirtualCandidates.map((o) => o.id));
|
|
2302
2594
|
const sideSlots = allOrders
|
|
@@ -2308,7 +2600,7 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
|
|
|
2308
2600
|
].sort((a, b) => a.price - b.price);
|
|
2309
2601
|
const ctx = await _getSizingContext(manager, sideName);
|
|
2310
2602
|
if (!ctx || ctx.budget <= 0 || syntheticSideSlots.length === 0) {
|
|
2311
|
-
return { ordersToPlace: [], ordersToUpdate: [] };
|
|
2603
|
+
return { ordersToPlace: [], ordersToUpdate: [], origin: 'spread-correction' };
|
|
2312
2604
|
}
|
|
2313
2605
|
const precisionEpsilon = getPrecisionSlack(ctx.precision, 1);
|
|
2314
2606
|
const idealSizes = allocateFundsByWeights(ctx.budget, syntheticSideSlots.length, manager.config.weightDistribution[sideName], manager.config.incrementPercent / 100, railType === ORDER_TYPES.BUY, 0, ctx.precision);
|
|
@@ -2345,7 +2637,7 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
|
|
|
2345
2637
|
}
|
|
2346
2638
|
}
|
|
2347
2639
|
if (prioritizedTargets.length === 0) {
|
|
2348
|
-
return { ordersToPlace: [], ordersToUpdate: [] };
|
|
2640
|
+
return { ordersToPlace: [], ordersToUpdate: [], origin: 'spread-correction' };
|
|
2349
2641
|
}
|
|
2350
2642
|
const totalNeeded = prioritizedTargets.reduce((sum, t) => sum + Math.max(0, Number(t.needed || 0)), 0);
|
|
2351
2643
|
let recoveredBudget = 0;
|
|
@@ -2479,6 +2771,6 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
|
|
|
2479
2771
|
if (combinedUpdates.length > 0 || ordersToPlace.length > 0) {
|
|
2480
2772
|
manager.logger?.log?.(`[SPREAD-CORRECTION] Prepared updates=${combinedUpdates.length}, creates=${ordersToPlace.length}, remainingBudget=${Format.formatSizeByOrderType(Math.max(0, remainingBudget), railType, manager.assets)}`, 'debug');
|
|
2481
2773
|
}
|
|
2482
|
-
return { ordersToPlace, ordersToUpdate: combinedUpdates, ...(boundaryIdx === undefined ? {} : { boundaryIdx }) };
|
|
2774
|
+
return { ordersToPlace, ordersToUpdate: combinedUpdates, ...(boundaryIdx === undefined ? {} : { boundaryIdx }), origin: 'spread-correction' };
|
|
2483
2775
|
}
|
|
2484
2776
|
//# sourceMappingURL=grid.js.map
|