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
|
@@ -101,7 +101,7 @@ import * as client from '../bitshares_client.js';
|
|
|
101
101
|
const { BitShares } = client;
|
|
102
102
|
import { NATIVE_CLIENT } from '../constants.js';
|
|
103
103
|
const { toFiniteNumber } = Format;
|
|
104
|
-
import { blockchainToFloat, floatToBlockchainInt, calculatePriceTolerance, getAssetFees, getBtsSide } from './utils/math.js';
|
|
104
|
+
import { blockchainToFloat, floatToBlockchainInt, calculatePriceTolerance, getAssetFees, getBtsSide, slotIndexForPrice, isSlotInRail, priceSlotEqual } from './utils/math.js';
|
|
105
105
|
import { parseChainOrder, findMatchingGridOrderByOpenOrder, applyChainSizeToGridOrder, convertToSpreadPlaceholder, virtualizeOrder, buildFillKey, isOrderPlaced, hasOnChainId, isOrderVirtual, resolveSpreadOrderSide, duplicateOrphanLogInfo } from './utils/order.js';
|
|
106
106
|
import { resolveProcessedFillPersistenceMode } from './processed_fill_store.js';
|
|
107
107
|
import { getErrorMessage } from '../utils/errors.js';
|
|
@@ -196,6 +196,58 @@ function describeNearestAdoptionCandidates(mgr, chainOrder, precision, calcToler
|
|
|
196
196
|
* @param {Function} calcToleranceFn - calculatePriceTolerance function
|
|
197
197
|
* @returns {{candidateSlotId: string, candidateSlotPrice: number, priceDiff: number, tolerance: number}|null}
|
|
198
198
|
*/
|
|
199
|
+
async function adoptChainOrderIntoSlot(mgr, slot, chainOrder, chainOrderId, rawChainOrders, matchedGridOrderIds, chainOrderIdsOnGrid, filledOrders, updatedOrders, skipAccounting) {
|
|
200
|
+
let bestMatch = { ...slot };
|
|
201
|
+
const wasVirtual = slot.state === ORDER_STATES.VIRTUAL;
|
|
202
|
+
const wasPartial = slot.state === ORDER_STATES.PARTIAL;
|
|
203
|
+
bestMatch.orderId = chainOrderId;
|
|
204
|
+
bestMatch.state = wasVirtual ? ORDER_STATES.ACTIVE : slot.state;
|
|
205
|
+
const bestMatchRaw = rawChainOrders.get(chainOrderId);
|
|
206
|
+
bestMatch.rawOnChain = bestMatchRaw ? { ...bestMatchRaw, fetchedAt: Date.now() } : bestMatchRaw;
|
|
207
|
+
matchedGridOrderIds.add(bestMatch.id);
|
|
208
|
+
if (bestMatch.rawOnChain) {
|
|
209
|
+
const rawDeferredFee = toFiniteNumber(bestMatch.rawOnChain.deferred_fee, null);
|
|
210
|
+
if (rawDeferredFee !== null && rawDeferredFee > 0) {
|
|
211
|
+
bestMatch.btsFeeState = { deferredFee: blockchainToFloat(rawDeferredFee, BTS_PRECISION) };
|
|
212
|
+
}
|
|
213
|
+
else if (wasVirtual && rawDeferredFee !== null && rawDeferredFee <= 0 && bestMatch.rawOnChain.for_sale > 0) {
|
|
214
|
+
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const precision = (bestMatch.type === ORDER_TYPES.SELL) ? mgr.assets.assetA.precision : mgr.assets.assetB.precision;
|
|
218
|
+
const targetInt = floatToBlockchainInt(slot.size, precision);
|
|
219
|
+
const chainInt = floatToBlockchainInt(chainOrder.size, precision);
|
|
220
|
+
if (targetInt !== chainInt) {
|
|
221
|
+
const updated = await applyChainSizeToGridOrder(mgr, bestMatch, chainOrder.size);
|
|
222
|
+
if (updated)
|
|
223
|
+
bestMatch = { ...bestMatch, ...updated };
|
|
224
|
+
if (chainInt > 0) {
|
|
225
|
+
if (wasVirtual && bestMatch.state !== ORDER_STATES.PARTIAL)
|
|
226
|
+
bestMatch.state = ORDER_STATES.ACTIVE;
|
|
227
|
+
else if (chainInt < targetInt)
|
|
228
|
+
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
229
|
+
else if (wasPartial)
|
|
230
|
+
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
231
|
+
else
|
|
232
|
+
bestMatch.state = ORDER_STATES.ACTIVE;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const spreadOrder = convertToSpreadPlaceholder(bestMatch);
|
|
236
|
+
filledOrders.push({ ...bestMatch });
|
|
237
|
+
await mgr._applyOrderUpdate(spreadOrder, 'sync-pass2-filled', { skipAccounting: skipAccounting, fee: 0 });
|
|
238
|
+
updatedOrders.push(spreadOrder);
|
|
239
|
+
chainOrderIdsOnGrid.add(chainOrderId);
|
|
240
|
+
return true; // filled path
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else if (wasPartial) {
|
|
244
|
+
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
245
|
+
}
|
|
246
|
+
await mgr._applyOrderUpdate(bestMatch, 'sync-pass2-orphan', { skipAccounting: skipAccounting, fee: 0 });
|
|
247
|
+
updatedOrders.push(bestMatch);
|
|
248
|
+
chainOrderIdsOnGrid.add(chainOrderId);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
199
251
|
function computeOutOfToleranceDriftTag(mgr, chainOrder, calcToleranceFn) {
|
|
200
252
|
if (!mgr?.orders || !chainOrder)
|
|
201
253
|
return null;
|
|
@@ -508,6 +560,39 @@ class SyncEngine {
|
|
|
508
560
|
}
|
|
509
561
|
}
|
|
510
562
|
mgr.logger?.log?.(`[SYNC] Starting synchronization from ${parsedChainOrders.size} blockchain orders...`, 'info');
|
|
563
|
+
// SUSPECT EMPTY READ GUARD (Fix C): an empty chain read while the grid
|
|
564
|
+
// still holds placed orders (slots with orderIds) is far more likely a
|
|
565
|
+
// lagging/partial node response than a genuinely emptied account — a
|
|
566
|
+
// 0-order read treated as authoritative has virtualized live orders
|
|
567
|
+
// ("phantom" resets). Refuse to reconcile on a
|
|
568
|
+
// suspect empty; the guard is self-expiring: after
|
|
569
|
+
// TIMING.SYNC_SUSPECT_EMPTY_READ_LIMIT consecutive empty reads the
|
|
570
|
+
// account really is empty and the sync accepts it. Any non-empty read
|
|
571
|
+
// resets the counter.
|
|
572
|
+
if (parsedChainOrders.size === 0) {
|
|
573
|
+
const gridOrderIds = Array.from(mgr.orders.values()).filter((o) => o?.orderId).length;
|
|
574
|
+
if (gridOrderIds > 0) {
|
|
575
|
+
const suspect = mgr._suspectEmptyReads || { count: 0, firstAt: 0 };
|
|
576
|
+
suspect.count += 1;
|
|
577
|
+
if (!suspect.firstAt)
|
|
578
|
+
suspect.firstAt = Date.now();
|
|
579
|
+
mgr._suspectEmptyReads = suspect;
|
|
580
|
+
const limit = Math.max(1, Number(TIMING.SYNC_SUSPECT_EMPTY_READ_LIMIT) || 3);
|
|
581
|
+
if (suspect.count < limit) {
|
|
582
|
+
mgr.logger?.log?.(`[SYNC] Suspect empty read (${suspect.count}/${limit} consecutive) with ${gridOrderIds} grid orderIds — ` +
|
|
583
|
+
`refusing reconciliation (phantom protection); accepting after ${limit} consecutive empties or next non-empty read`, 'warn');
|
|
584
|
+
return { filledOrders: [], updatedOrders: [], ordersNeedingCorrection: [], unmatchedChainOrders: [] };
|
|
585
|
+
}
|
|
586
|
+
mgr.logger?.log?.(`[SYNC] Empty read confirmed after ${suspect.count} consecutive attempts — reconciling to empty account`, 'warn');
|
|
587
|
+
mgr._suspectEmptyReads = { count: 0, firstAt: 0 };
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
mgr._suspectEmptyReads = { count: 0, firstAt: 0 };
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
mgr._suspectEmptyReads = { count: 0, firstAt: 0 };
|
|
595
|
+
}
|
|
511
596
|
// Collect all order IDs that might be modified during reconciliation
|
|
512
597
|
// Lock them to prevent concurrent modifications from createOrder/cancelOrder
|
|
513
598
|
const orderIdsToLock = new Set();
|
|
@@ -644,10 +729,24 @@ class SyncEngine {
|
|
|
644
729
|
continue;
|
|
645
730
|
}
|
|
646
731
|
else {
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
732
|
+
const genesisForPass1 = mgr._genesis;
|
|
733
|
+
let isPriceEqual = false;
|
|
734
|
+
if (genesisForPass1 && Array.isArray(genesisForPass1.priceLevels)) {
|
|
735
|
+
const precision = gridOrder.type === ORDER_TYPES.SELL ? assetAPrecision : assetBPrecision;
|
|
736
|
+
// Genesis path: single epsilon via integer round-trip
|
|
737
|
+
try {
|
|
738
|
+
isPriceEqual = priceSlotEqual(chainOrder.price, gridOrder.price, precision);
|
|
739
|
+
}
|
|
740
|
+
catch {
|
|
741
|
+
isPriceEqual = chainOrder.price === gridOrder.price;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
const priceTolerance = calculatePriceTolerance(gridOrder.price, gridOrder.size, gridOrder.type, mgr.assets);
|
|
746
|
+
const normalizedTolerance = (priceTolerance === null) ? 0 : priceTolerance;
|
|
747
|
+
isPriceEqual = Math.abs(chainOrder.price - gridOrder.price) <= normalizedTolerance;
|
|
748
|
+
}
|
|
749
|
+
if (!isPriceEqual) {
|
|
651
750
|
queueCorrection({
|
|
652
751
|
gridOrder: { ...gridOrder },
|
|
653
752
|
chainOrderId: gridOrder.orderId,
|
|
@@ -663,6 +762,84 @@ class SyncEngine {
|
|
|
663
762
|
const currentSizeInt = floatToBlockchainInt(gridOrder.size, precision);
|
|
664
763
|
const chainSizeInt = floatToBlockchainInt(chainOrder.size, precision);
|
|
665
764
|
if (currentSizeInt !== chainSizeInt) {
|
|
765
|
+
// SIZE-CONSISTENCY TIEBREAK (mis-tracked duplicate): the
|
|
766
|
+
// tracked order's chain size disagrees with the slot's
|
|
767
|
+
// booked remaining size. Before adopting the chain size,
|
|
768
|
+
// check whether ANOTHER chain order at this price level
|
|
769
|
+
// matches the booked size exactly. The grid allows at most
|
|
770
|
+
// one order per level, so a second order whose size equals
|
|
771
|
+
// the booked remaining means the slot's orderId was
|
|
772
|
+
// mis-assigned (duplicate-price order created by a stale
|
|
773
|
+
// rebuild racing the fill booking) and the real order is
|
|
774
|
+
// the other one. Rebind the slot to it; the previously
|
|
775
|
+
// tracked order falls through to pass 2 as a duplicate-
|
|
776
|
+
// price orphan and is queued for cancellation there.
|
|
777
|
+
let swapMatch = null;
|
|
778
|
+
if (currentSizeInt > 0) {
|
|
779
|
+
for (const [candidateId, candidateOrder] of parsedChainOrders) {
|
|
780
|
+
if (candidateId === gridOrder.orderId)
|
|
781
|
+
continue;
|
|
782
|
+
if (chainOrderIdsOnGrid.has(candidateId))
|
|
783
|
+
continue;
|
|
784
|
+
if (!candidateOrder || candidateOrder.type !== chainOrder.type)
|
|
785
|
+
continue;
|
|
786
|
+
// Per-candidate price check: genesis → priceSlotEqual, else tolerance
|
|
787
|
+
const genesisForSwap = mgr._genesis;
|
|
788
|
+
let swapPriceEqual = false;
|
|
789
|
+
if (genesisForSwap && Array.isArray(genesisForSwap.priceLevels)) {
|
|
790
|
+
const precision = gridOrder.type === ORDER_TYPES.SELL ? assetAPrecision : assetBPrecision;
|
|
791
|
+
try {
|
|
792
|
+
swapPriceEqual = priceSlotEqual(candidateOrder.price, gridOrder.price, precision);
|
|
793
|
+
}
|
|
794
|
+
catch {
|
|
795
|
+
swapPriceEqual = candidateOrder.price === gridOrder.price;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
const candidateTolerance = calculatePriceTolerance(Math.min(candidateOrder.price, gridOrder.price), Math.max(candidateOrder.size, gridOrder.size), gridOrder.type, mgr.assets);
|
|
800
|
+
swapPriceEqual = Math.abs(candidateOrder.price - gridOrder.price) <= (candidateTolerance ?? 0);
|
|
801
|
+
}
|
|
802
|
+
if (!swapPriceEqual)
|
|
803
|
+
continue;
|
|
804
|
+
if (floatToBlockchainInt(candidateOrder.size, precision) !== currentSizeInt)
|
|
805
|
+
continue;
|
|
806
|
+
swapMatch = { id: candidateId, order: candidateOrder };
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (swapMatch) {
|
|
811
|
+
const swapRaw = rawChainOrders.get(swapMatch.id);
|
|
812
|
+
const reboundOrder = {
|
|
813
|
+
...gridOrder,
|
|
814
|
+
orderId: swapMatch.id,
|
|
815
|
+
rawOnChain: swapRaw
|
|
816
|
+
? { ...swapRaw, fetchedAt: Date.now() }
|
|
817
|
+
: gridOrder.rawOnChain,
|
|
818
|
+
};
|
|
819
|
+
// Restore fee lifecycle / partial-fill evidence from the
|
|
820
|
+
// adopted order's deferred_fee (same convention as the
|
|
821
|
+
// pass-2 adoption path below: deferred_fee > 0 → fee
|
|
822
|
+
// state; deferred_fee === 0 with for_sale > 0 → the
|
|
823
|
+
// order was partially filled).
|
|
824
|
+
const rawDeferredFee = toFiniteNumber(reboundOrder.rawOnChain?.deferred_fee, null);
|
|
825
|
+
if (rawDeferredFee !== null && rawDeferredFee > 0) {
|
|
826
|
+
reboundOrder.btsFeeState = { deferredFee: blockchainToFloat(rawDeferredFee, BTS_PRECISION) };
|
|
827
|
+
}
|
|
828
|
+
else if (rawDeferredFee !== null && rawDeferredFee <= 0
|
|
829
|
+
&& toFiniteNumber(reboundOrder.rawOnChain?.for_sale, 0) > 0) {
|
|
830
|
+
reboundOrder.state = ORDER_STATES.PARTIAL;
|
|
831
|
+
}
|
|
832
|
+
chainOrderIdsOnGrid.delete(gridOrder.orderId);
|
|
833
|
+
chainOrderIdsOnGrid.add(swapMatch.id);
|
|
834
|
+
await mgr._applyOrderUpdate(reboundOrder, 'sync-pass1-duplicate-swap', { skipAccounting: skipAccounting, fee: 0 });
|
|
835
|
+
updatedOrders.push(reboundOrder);
|
|
836
|
+
mgr.logger?.log?.(`[SYNC] Size tiebreak for ${gridOrder.id}: tracked order ${gridOrder.orderId} ` +
|
|
837
|
+
`(chain size ${chainOrder.size}) disagrees with booked size ${gridOrder.size}, but ` +
|
|
838
|
+
`duplicate-price order ${swapMatch.id} (chain size ${swapMatch.order.size}) matches ` +
|
|
839
|
+
`it exactly — rebinding slot to ${swapMatch.id}; ${gridOrder.orderId} falls through ` +
|
|
840
|
+
`to pass 2 as a duplicate-price orphan (queued for cancellation).`, 'warn');
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
666
843
|
const newSize = blockchainToFloat(chainSizeInt, precision);
|
|
667
844
|
const newInt = floatToBlockchainInt(newSize, precision);
|
|
668
845
|
if (newInt > 0) {
|
|
@@ -755,168 +932,99 @@ class SyncEngine {
|
|
|
755
932
|
}
|
|
756
933
|
}
|
|
757
934
|
// ====================================================================
|
|
758
|
-
// PASS 2: CHAIN → GRID -
|
|
935
|
+
// PASS 2: CHAIN → GRID - Nearest-slot deterministic adoption (genesis-frozen)
|
|
759
936
|
// ====================================================================
|
|
760
|
-
|
|
937
|
+
const genesis = mgr._genesis;
|
|
938
|
+
const hasGenesis = genesis && Array.isArray(genesis.priceLevels) && genesis.priceLevels.length > 0;
|
|
939
|
+
const anchorPrice = hasGenesis ? genesis.startPrice : mgr.config?.startPrice;
|
|
940
|
+
const sortedChainEntries = [...parsedChainOrders.entries()].sort((a, b) => {
|
|
941
|
+
const pa = toFiniteNumber(a[1].price);
|
|
942
|
+
const pb = toFiniteNumber(b[1].price);
|
|
943
|
+
const da = Number.isFinite(pa) && Number.isFinite(anchorPrice) ? Math.abs(pa - anchorPrice) : Infinity;
|
|
944
|
+
const db = Number.isFinite(pb) && Number.isFinite(anchorPrice) ? Math.abs(pb - anchorPrice) : Infinity;
|
|
945
|
+
return da - db;
|
|
946
|
+
});
|
|
947
|
+
for (const [chainOrderId, chainOrder] of sortedChainEntries) {
|
|
761
948
|
if (chainOrderIdsOnGrid.has(chainOrderId))
|
|
762
949
|
continue;
|
|
763
|
-
//
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
950
|
+
// Genesis path: nearest-slot is single authority (no tolerance)
|
|
951
|
+
if (hasGenesis) {
|
|
952
|
+
let idx;
|
|
953
|
+
try {
|
|
954
|
+
idx = slotIndexForPrice(chainOrder.price, genesis);
|
|
955
|
+
}
|
|
956
|
+
catch {
|
|
957
|
+
unmatchedChainOrders.push({ chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), reason: 'no-available-nearest-slot' });
|
|
958
|
+
queueCorrection({ gridOrder: { id: `slot-unknown-${chainOrderId}`, type: chainOrder.type }, chainOrderId, expectedPrice: chainOrder.price, size: chainOrder.size, type: chainOrder.type, isSurplus: true, cancelOnly: true });
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
const slotId = `slot-${idx}`;
|
|
962
|
+
const gapSlots = genesis.gapSlots ?? mgr._gapSlots ?? 0;
|
|
963
|
+
const boundaryIdx = mgr.boundaryIdx;
|
|
964
|
+
// Duplicate-price guard becomes slotId equality: if placed order already occupies this slot
|
|
965
|
+
const occupying = mgr.orders.get(slotId);
|
|
966
|
+
if (occupying && isOrderPlaced(occupying) && occupying.type === chainOrder.type) {
|
|
967
|
+
unmatchedChainOrders.push({ chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), reason: 'duplicate-price-level', candidateSlotId: slotId });
|
|
968
|
+
const { level, suffix } = duplicateOrphanLogInfo(chainOrderId);
|
|
969
|
+
mgr.logger?.log?.(`[SYNC] Orphaned chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price}, size=${chainOrder.size}) — NOT adopted: duplicates slot ${slotId} (${occupying.orderId})${suffix}`, level);
|
|
970
|
+
queueCorrection({ gridOrder: occupying, chainOrderId, expectedPrice: chainOrder.price, size: chainOrder.size, type: chainOrder.type, isSurplus: true, cancelOnly: true });
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
// Gap exclusion: nearest slot in SPREAD gap → no adopt. When boundaryIdx == null (pre-boundary sync) the gap is unknown so we allow adoption — the next reconcile will relocate gap stray per §10 table's cancelOnly intent (documented hole).
|
|
974
|
+
if (boundaryIdx != null && Number.isFinite(Number(boundaryIdx))) {
|
|
975
|
+
const inRail = isSlotInRail(boundaryIdx, gapSlots, chainOrder.type, { id: slotId });
|
|
976
|
+
if (!inRail) {
|
|
977
|
+
unmatchedChainOrders.push({ chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), reason: 'no-available-nearest-slot', candidateSlotId: slotId });
|
|
978
|
+
queueCorrection({ gridOrder: { id: slotId, type: chainOrder.type }, chainOrderId, expectedPrice: chainOrder.price, size: chainOrder.size, type: chainOrder.type, isSurplus: true, cancelOnly: true });
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
const slot = mgr.orders.get(slotId);
|
|
983
|
+
if (!slot || matchedGridOrderIds.has(slot.id) || slot.orderId) {
|
|
984
|
+
unmatchedChainOrders.push({ chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), reason: 'no-available-nearest-slot', candidateSlotId: slotId });
|
|
985
|
+
const diag = describeNearestAdoptionCandidates(mgr, chainOrder, (chainOrder.type === ORDER_TYPES.SELL ? assetAPrecision : assetBPrecision), (p, s, t) => calculatePriceTolerance(p, s, t, mgr.assets), matchedGridOrderIds);
|
|
986
|
+
mgr.logger?.log?.(`[SYNC] Unmatched chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price}, size=${chainOrder.size}): nearest slot ${slotId} not adoptable. Nearest candidates: ${diag}`, 'warn');
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const typeCompat = slot.type === chainOrder.type || slot.type === ORDER_TYPES.SPREAD;
|
|
990
|
+
if (!typeCompat) {
|
|
991
|
+
unmatchedChainOrders.push({ chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), reason: 'no-available-nearest-slot', candidateSlotId: slotId });
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
await adoptChainOrderIntoSlot(mgr, slot, chainOrder, chainOrderId, rawChainOrders, matchedGridOrderIds, chainOrderIdsOnGrid, filledOrders, updatedOrders, skipAccounting);
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
// Legacy fallback when genesis unavailable (migration)
|
|
770
998
|
const duplicatePriceOrder = Array.from(mgr.orders.values()).find((o) => {
|
|
771
999
|
const co = chainOrder;
|
|
772
|
-
return o.type === co.type &&
|
|
773
|
-
isOrderPlaced(o) &&
|
|
774
|
-
co != null &&
|
|
775
|
-
Math.abs(o.price - co.price) <= calculatePriceTolerance(Math.min(o.price, co.price), Math.max(o.size, co.size), o.type, mgr.assets);
|
|
1000
|
+
return o.type === co.type && isOrderPlaced(o) && co != null && Math.abs(o.price - co.price) <= calculatePriceTolerance(Math.min(o.price, co.price), Math.max(o.size, co.size), o.type, mgr.assets);
|
|
776
1001
|
});
|
|
777
1002
|
if (duplicatePriceOrder) {
|
|
778
|
-
unmatchedChainOrders.push({
|
|
779
|
-
chainOrderId,
|
|
780
|
-
type: chainOrder.type,
|
|
781
|
-
price: chainOrder.price,
|
|
782
|
-
size: chainOrder.size,
|
|
783
|
-
raw: rawChainOrders.get(chainOrderId),
|
|
784
|
-
reason: 'duplicate-price-level',
|
|
785
|
-
candidateSlotId: duplicatePriceOrder.id,
|
|
786
|
-
});
|
|
1003
|
+
unmatchedChainOrders.push({ chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), reason: 'duplicate-price-level', candidateSlotId: duplicatePriceOrder.id });
|
|
787
1004
|
const { level, suffix } = duplicateOrphanLogInfo(chainOrderId);
|
|
788
|
-
mgr.logger?.log?.(`[SYNC] Orphaned chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price},
|
|
789
|
-
|
|
790
|
-
`${duplicatePriceOrder.id} (${duplicatePriceOrder.orderId} at ${duplicatePriceOrder.price})${suffix}`, level);
|
|
791
|
-
// Gap 2: Queue duplicate chain order for cancellation via the correction
|
|
792
|
-
// mechanism (called from _performSyncFromOpenOrders). Duplicate price levels
|
|
793
|
-
// block new placements. Cancelling proactively unblocks CREATEs.
|
|
794
|
-
queueCorrection({
|
|
795
|
-
gridOrder: duplicatePriceOrder,
|
|
796
|
-
chainOrderId,
|
|
797
|
-
expectedPrice: chainOrder.price,
|
|
798
|
-
size: chainOrder.size,
|
|
799
|
-
type: chainOrder.type,
|
|
800
|
-
isSurplus: true,
|
|
801
|
-
cancelOnly: true,
|
|
802
|
-
});
|
|
1005
|
+
mgr.logger?.log?.(`[SYNC] Orphaned chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price}, size=${chainOrder.size}) — NOT adopted: duplicates price level of active ${duplicatePriceOrder.id} (${duplicatePriceOrder.orderId} at ${duplicatePriceOrder.price})${suffix}`, level);
|
|
1006
|
+
queueCorrection({ gridOrder: duplicatePriceOrder, chainOrderId, expectedPrice: chainOrder.price, size: chainOrder.size, type: chainOrder.type, isSurplus: true, cancelOnly: true });
|
|
803
1007
|
continue;
|
|
804
1008
|
}
|
|
805
|
-
const match = findMatchingGridOrderByOpenOrder({ orderId: chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size }, {
|
|
806
|
-
orders: mgr.orders,
|
|
807
|
-
assets: mgr.assets,
|
|
808
|
-
calcToleranceFn: (p, s, t) => calculatePriceTolerance(p, s, t, mgr.assets),
|
|
809
|
-
logger: mgr.logger,
|
|
810
|
-
allowSmallerChainSize: true,
|
|
811
|
-
requireAvailableSlot: true,
|
|
812
|
-
excludeGridOrderIds: matchedGridOrderIds
|
|
813
|
-
});
|
|
1009
|
+
const match = findMatchingGridOrderByOpenOrder({ orderId: chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size }, { orders: mgr.orders, assets: mgr.assets, calcToleranceFn: (p, s, t) => calculatePriceTolerance(p, s, t, mgr.assets), logger: mgr.logger, allowSmallerChainSize: true, requireAvailableSlot: true, excludeGridOrderIds: matchedGridOrderIds });
|
|
814
1010
|
if (match && !matchedGridOrderIds.has(match.id)) {
|
|
815
|
-
|
|
816
|
-
const wasVirtual = match.state === ORDER_STATES.VIRTUAL;
|
|
817
|
-
const wasPartial = match.state === ORDER_STATES.PARTIAL;
|
|
818
|
-
bestMatch.orderId = chainOrderId;
|
|
819
|
-
bestMatch.state = wasVirtual ? ORDER_STATES.ACTIVE : match.state;
|
|
820
|
-
const bestMatchRaw = rawChainOrders.get(chainOrderId);
|
|
821
|
-
bestMatch.rawOnChain = bestMatchRaw
|
|
822
|
-
? { ...bestMatchRaw, fetchedAt: Date.now() }
|
|
823
|
-
: bestMatchRaw;
|
|
824
|
-
matchedGridOrderIds.add(bestMatch.id);
|
|
825
|
-
// Reconstruct btsFeeState from raw chain order's deferred_fee.
|
|
826
|
-
// After a grid reset, in-memory orders lose btsFeeState. The chain's
|
|
827
|
-
// limit_order_object stores the original deferred_fee (or 0 after a
|
|
828
|
-
// partial fill). Restoring it ensures correct fee lifecycle accounting
|
|
829
|
-
// (cancel refunds, fill maker discounts) on the reconciled order.
|
|
830
|
-
// Only set btsFeeState when deferred_fee > 0 — _resolveBtsFeeLifecycle
|
|
831
|
-
// deletes btsFeeState when nextDeferred is 0, so setting it to 0 here
|
|
832
|
-
// would be an unnecessary set-then-delete cycle.
|
|
833
|
-
if (bestMatch.rawOnChain) {
|
|
834
|
-
const rawDeferredFee = toFiniteNumber(bestMatch.rawOnChain.deferred_fee, null);
|
|
835
|
-
if (rawDeferredFee !== null && rawDeferredFee > 0) {
|
|
836
|
-
bestMatch.btsFeeState = { deferredFee: blockchainToFloat(rawDeferredFee, BTS_PRECISION) };
|
|
837
|
-
}
|
|
838
|
-
else if (wasVirtual && rawDeferredFee !== null && rawDeferredFee <= 0 && bestMatch.rawOnChain.for_sale > 0) {
|
|
839
|
-
// Core zeros deferred_fee on every fill (fill_limit_order db_market.cpp:1895).
|
|
840
|
-
// If deferred_fee is 0 while for_sale > 0, the order was partially filled.
|
|
841
|
-
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
const precision = (bestMatch.type === ORDER_TYPES.SELL) ? assetAPrecision : assetBPrecision;
|
|
845
|
-
const targetInt = floatToBlockchainInt(match.size, precision);
|
|
846
|
-
const chainInt = floatToBlockchainInt(chainOrder.size, precision);
|
|
847
|
-
if (targetInt !== chainInt) {
|
|
848
|
-
const updated = await applyChainSizeToGridOrder(mgr, bestMatch, chainOrder.size);
|
|
849
|
-
if (updated)
|
|
850
|
-
bestMatch = { ...bestMatch, ...updated };
|
|
851
|
-
if (chainInt > 0) {
|
|
852
|
-
if (wasVirtual) {
|
|
853
|
-
// Only override to ACTIVE if partial-fill check above didn't already
|
|
854
|
-
// set PARTIAL via deferred_fee === 0. Skip override to preserve signal.
|
|
855
|
-
if (bestMatch.state !== ORDER_STATES.PARTIAL) {
|
|
856
|
-
bestMatch.state = ORDER_STATES.ACTIVE;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
else if (chainInt < targetInt) {
|
|
860
|
-
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
861
|
-
}
|
|
862
|
-
else if (wasPartial) {
|
|
863
|
-
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
864
|
-
}
|
|
865
|
-
else {
|
|
866
|
-
bestMatch.state = ORDER_STATES.ACTIVE;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
const spreadOrder = convertToSpreadPlaceholder(bestMatch);
|
|
871
|
-
filledOrders.push({ ...bestMatch });
|
|
872
|
-
await mgr._applyOrderUpdate(spreadOrder, 'sync-pass2-filled', { skipAccounting: skipAccounting, fee: 0 });
|
|
873
|
-
updatedOrders.push(spreadOrder);
|
|
874
|
-
chainOrderIdsOnGrid.add(chainOrderId);
|
|
875
|
-
continue;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
else if (wasPartial) {
|
|
879
|
-
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
880
|
-
}
|
|
881
|
-
await mgr._applyOrderUpdate(bestMatch, 'sync-pass2-orphan', { skipAccounting: skipAccounting, fee: 0 });
|
|
882
|
-
updatedOrders.push(bestMatch);
|
|
883
|
-
chainOrderIdsOnGrid.add(chainOrderId);
|
|
1011
|
+
await adoptChainOrderIntoSlot(mgr, match, chainOrder, chainOrderId, rawChainOrders, matchedGridOrderIds, chainOrderIdsOnGrid, filledOrders, updatedOrders, skipAccounting);
|
|
884
1012
|
}
|
|
885
1013
|
else if (match) {
|
|
886
|
-
mgr.logger?.log?.(`Warning: Orphan chain order ${chainOrderId} matched grid order ${match.id},
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
gridOrder: match,
|
|
890
|
-
chainOrderId,
|
|
891
|
-
expectedPrice: chainOrder.price,
|
|
892
|
-
size: chainOrder.size,
|
|
893
|
-
type: chainOrder.type,
|
|
894
|
-
isSurplus: true,
|
|
895
|
-
cancelOnly: true,
|
|
896
|
-
});
|
|
897
|
-
// Also push to unmatchedChainOrders so the existing auto-cancel
|
|
898
|
-
// path at dexbot_class.ts:4262 can pick it up if the correction
|
|
899
|
-
// mechanism does not fire first.
|
|
900
|
-
unmatchedChainOrders.push({
|
|
901
|
-
chainOrderId,
|
|
902
|
-
type: chainOrder.type,
|
|
903
|
-
price: chainOrder.price,
|
|
904
|
-
size: chainOrder.size,
|
|
905
|
-
raw: rawChainOrders.get(chainOrderId),
|
|
906
|
-
reason: 'already-matched-slot',
|
|
907
|
-
candidateSlotId: match.id,
|
|
908
|
-
});
|
|
1014
|
+
mgr.logger?.log?.(`Warning: Orphan chain order ${chainOrderId} matched grid order ${match.id}, but grid order was already matched to another chain order. Queuing orphan for cancellation.`, 'warn');
|
|
1015
|
+
queueCorrection({ gridOrder: match, chainOrderId, expectedPrice: chainOrder.price, size: chainOrder.size, type: chainOrder.type, isSurplus: true, cancelOnly: true });
|
|
1016
|
+
unmatchedChainOrders.push({ chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), reason: 'already-matched-slot', candidateSlotId: match.id });
|
|
909
1017
|
}
|
|
910
1018
|
else {
|
|
911
|
-
//
|
|
912
|
-
// Fallback: adopt into the nearest VIRTUAL/spread slot so the
|
|
913
|
-
// orphan becomes visible to checkWindowDust for dust auto-cancel.
|
|
914
|
-
// The duplicate-price-level guard already ran above so it is not
|
|
915
|
-
// repeated here.
|
|
1019
|
+
// Legacy spread-orphan fallback: adopt into nearest VIRTUAL/spread slot (allowSpreadType + skipSizeMatch) with widened tolerance
|
|
916
1020
|
const adoptedSlot = findMatchingGridOrderByOpenOrder({ orderId: chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size }, {
|
|
917
1021
|
orders: mgr.orders,
|
|
918
1022
|
assets: mgr.assets,
|
|
919
|
-
calcToleranceFn: (p, s, t) =>
|
|
1023
|
+
calcToleranceFn: (p, s, t) => {
|
|
1024
|
+
const strict = calculatePriceTolerance(p, s, t, mgr.assets);
|
|
1025
|
+
const mult = mgr?.config?.gridLimits?.ORPHAN_ADOPTION_TOLERANCE_MULTIPLIER || 4;
|
|
1026
|
+
return strict == null ? null : strict * mult;
|
|
1027
|
+
},
|
|
920
1028
|
allowSpreadType: true,
|
|
921
1029
|
skipSizeMatch: true,
|
|
922
1030
|
requireAvailableSlot: true,
|
|
@@ -938,30 +1046,20 @@ class SyncEngine {
|
|
|
938
1046
|
state: adoptedState,
|
|
939
1047
|
size: chainOrder.size,
|
|
940
1048
|
price: chainOrder.price,
|
|
941
|
-
rawOnChain: adoptedRaw
|
|
942
|
-
? { ...adoptedRaw, fetchedAt: Date.now() }
|
|
943
|
-
: adoptedRaw,
|
|
1049
|
+
rawOnChain: adoptedRaw ? { ...adoptedRaw, fetchedAt: Date.now() } : adoptedRaw,
|
|
944
1050
|
...(adoptedBtsFeeState ? { btsFeeState: adoptedBtsFeeState } : {}),
|
|
945
1051
|
};
|
|
946
1052
|
matchedGridOrderIds.add(adoptedSlot.id);
|
|
947
1053
|
chainOrderIdsOnGrid.add(chainOrderId);
|
|
948
1054
|
await mgr._applyOrderUpdate(adoptedOrder, 'sync-pass2-adopt-orphan', { skipAccounting: skipAccounting, fee: 0 });
|
|
949
1055
|
updatedOrders.push(adoptedOrder);
|
|
950
|
-
mgr.logger?.log?.(`[SYNC] Orphaned chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price},
|
|
951
|
-
`size=${chainOrder.size}) adopted into slot ${adoptedSlot.id} (was ${adoptedSlot.type})`, 'warn');
|
|
1056
|
+
mgr.logger?.log?.(`[SYNC] Orphaned chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price}, size=${chainOrder.size}) adopted into slot ${adoptedSlot.id} (was ${adoptedSlot.type})`, 'warn');
|
|
952
1057
|
}
|
|
953
1058
|
else {
|
|
954
1059
|
const precision = (chainOrder.type === ORDER_TYPES.SELL) ? assetAPrecision : assetBPrecision;
|
|
955
1060
|
const candidateDiagnostics = describeNearestAdoptionCandidates(mgr, chainOrder, precision, (p, s, t) => calculatePriceTolerance(p, s, t, mgr.assets), matchedGridOrderIds);
|
|
956
1061
|
const driftTag = computeOutOfToleranceDriftTag(mgr, chainOrder, (p, s, t) => calculatePriceTolerance(p, s, t, mgr.assets));
|
|
957
|
-
const unmatchedEntry = {
|
|
958
|
-
chainOrderId,
|
|
959
|
-
type: chainOrder.type,
|
|
960
|
-
price: chainOrder.price,
|
|
961
|
-
size: chainOrder.size,
|
|
962
|
-
raw: rawChainOrders.get(chainOrderId),
|
|
963
|
-
candidateDiagnostics
|
|
964
|
-
};
|
|
1062
|
+
const unmatchedEntry = { chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), candidateDiagnostics };
|
|
965
1063
|
if (driftTag) {
|
|
966
1064
|
unmatchedEntry.reason = 'price-drift-orphan';
|
|
967
1065
|
unmatchedEntry.candidateSlotId = driftTag.candidateSlotId;
|
|
@@ -970,10 +1068,7 @@ class SyncEngine {
|
|
|
970
1068
|
unmatchedEntry.tolerance = driftTag.tolerance;
|
|
971
1069
|
}
|
|
972
1070
|
unmatchedChainOrders.push(unmatchedEntry);
|
|
973
|
-
mgr.logger?.log?.(`[SYNC] Unmatched chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price}, ` +
|
|
974
|
-
`size=${chainOrder.size}): no adoptable slot found` +
|
|
975
|
-
(driftTag ? ` (price-drift-orphan slot=${driftTag.candidateSlotId}@${driftTag.candidateSlotPrice} diff=${driftTag.priceDiff} tol=${driftTag.tolerance})` : '') +
|
|
976
|
-
`. Nearest candidates: ${candidateDiagnostics}`, 'warn');
|
|
1071
|
+
mgr.logger?.log?.(`[SYNC] Unmatched chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price}, size=${chainOrder.size}): no adoptable slot found` + (driftTag ? ` (price-drift-orphan slot=${driftTag.candidateSlotId}@${driftTag.candidateSlotPrice} diff=${driftTag.priceDiff} tol=${driftTag.tolerance})` : '') + `. Nearest candidates: ${candidateDiagnostics}`, 'warn');
|
|
977
1072
|
}
|
|
978
1073
|
}
|
|
979
1074
|
}
|
|
@@ -1492,7 +1587,7 @@ class SyncEngine {
|
|
|
1492
1587
|
// and the last update wins (`applyGridUpdateBatch` is last-wins
|
|
1493
1588
|
// per slot), leaving a phantom residual equal to the earlier
|
|
1494
1589
|
// fills' sum — exactly the fund-invariant CRITICAL seen in the
|
|
1495
|
-
//
|
|
1590
|
+
// AAA-BBB log whenever a batch contained 2+ fills of one order.
|
|
1496
1591
|
const gridUpdates = [];
|
|
1497
1592
|
const filledOrders = [];
|
|
1498
1593
|
const updatedOrders = [];
|