dexbot 1.6.0 → 1.6.2
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 +33 -0
- package/README.md +5 -4
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/tradingview/tradingview_uplot_chart_generator.ts +19 -5
- package/analysis/trend_detection/package.json +1 -1
- 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/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +19 -5
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -1
- package/dist/dexbot.js +1 -1
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_book_data.js +2 -2
- package/dist/market_adapter/inputs/fetch_book_data.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +22 -3
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +15 -15
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js +2 -2
- package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
- package/dist/market_adapter/inputs/window_cache.d.ts +54 -8
- package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
- package/dist/market_adapter/inputs/window_cache.js +328 -87
- package/dist/market_adapter/inputs/window_cache.js.map +1 -1
- package/dist/modules/account_bots.js +2 -2
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.d.ts +1 -1
- package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.js +18 -4
- package/dist/modules/bitshares-native/subscriptions.js.map +1 -1
- package/dist/modules/bitshares_client.d.ts.map +1 -1
- package/dist/modules/bitshares_client.js +10 -0
- package/dist/modules/bitshares_client.js.map +1 -1
- package/dist/modules/constants.d.ts +12 -3
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +66 -20
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +24 -6
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +34 -6
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +94 -17
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +57 -6
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +474 -38
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +53 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +277 -9
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +69 -0
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +277 -7
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_startup_runtime.js +6 -1
- 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 +32 -4
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/market_adapter_whitelist.js +1 -1
- package/dist/modules/market_adapter_whitelist.js.map +1 -1
- package/dist/modules/order/grid.d.ts +3 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +93 -16
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +73 -7
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/logger.js +1 -1
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts +14 -1
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +54 -2
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +183 -10
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +65 -15
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +93 -22
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +51 -1
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +122 -2
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/utils/errors.d.ts +22 -0
- package/dist/modules/utils/errors.d.ts.map +1 -1
- package/dist/modules/utils/errors.js +30 -0
- package/dist/modules/utils/errors.js.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.js +3 -2
- package/dist/scripts/generate_market_adapter_whitelist.js.map +1 -1
- package/docs/BITSHARES_ONBOARDING.md +14 -6
- package/docs/COPY_ON_WRITE_MASTER_PLAN.md +1 -1
- package/docs/DEXBOT_COMPARISON.md +3 -7
- package/docs/EVOLUTION.md +9 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +4 -9
- package/docs/GRID_RECALCULATION.md +8 -7
- package/docs/LIFECYCLE.md +2 -2
- package/docs/README.md +9 -1
- package/docs/architecture.md +7 -7
- package/docs/developer_guide.md +4 -4
- package/market_adapter/README.md +14 -11
- package/package.json +1 -1
- package/scripts/README.md +4 -4
- package/analysis/results/ama_sweep_results_lp_pool_133_1h.json +0 -2455
- package/analysis/results/bot_fitting_results_lp_pool_133_1h.json +0 -218
- package/analysis/tradingview/h-bts_tradingview.html +0 -1570
- package/analysis/tradingview/t-bts_tradingview.html +0 -1570
|
@@ -98,10 +98,32 @@ import * as Format from './format.js';
|
|
|
98
98
|
import { lookupAsset, sleep, resolveAccountRef } from './utils/system.js';
|
|
99
99
|
import * as chainOrders from '../chain_orders.js';
|
|
100
100
|
import * as client from '../bitshares_client.js';
|
|
101
|
+
/**
|
|
102
|
+
* Test seam for targeted single-order refetches (drift refetch + sub-dust
|
|
103
|
+
* residual verification). When manager._readSingleOrderFn is a function it
|
|
104
|
+
* is used instead of the chain read. The chain path starts with
|
|
105
|
+
* waitForConnected (30s timeout against live nodes); tests that drive fill
|
|
106
|
+
* paths with a drift signal or a sub-dust residual would otherwise hang on
|
|
107
|
+
* that connect for a read whose result they do not even assert on.
|
|
108
|
+
* Production never sets this; default is the chain.
|
|
109
|
+
*/
|
|
110
|
+
function readSingleOrderSeam(mgr, orderId, timeoutMs) {
|
|
111
|
+
const seam = mgr?._readSingleOrderFn;
|
|
112
|
+
if (typeof seam === 'function')
|
|
113
|
+
return seam(orderId, timeoutMs);
|
|
114
|
+
return chainOrders.readSingleOrder(orderId, timeoutMs);
|
|
115
|
+
}
|
|
116
|
+
/** Same seam for the batched drift refetch (batch path). */
|
|
117
|
+
function batchReadOrdersSeam(mgr, orderIds, timeoutMs) {
|
|
118
|
+
const seam = mgr?._batchReadOrdersFn;
|
|
119
|
+
if (typeof seam === 'function')
|
|
120
|
+
return seam(orderIds, timeoutMs);
|
|
121
|
+
return chainOrders.batchReadOrders(orderIds, timeoutMs);
|
|
122
|
+
}
|
|
101
123
|
const { BitShares } = client;
|
|
102
124
|
import { NATIVE_CLIENT } from '../constants.js';
|
|
103
125
|
const { toFiniteNumber } = Format;
|
|
104
|
-
import { blockchainToFloat, floatToBlockchainInt, calculatePriceTolerance, getAssetFees, getBtsSide, getSellStartIdx, slotIndexForPrice, isChainPriceOutOfGrid, isSlotInRail, isSlotIndexInGapBand, priceSlotEqual } from './utils/math.js';
|
|
126
|
+
import { blockchainToFloat, floatToBlockchainInt, calculatePriceTolerance, getAssetFees, getBtsSide, getSellStartIdx, slotIndexForPrice, isChainPriceOutOfGrid, isSlotInRail, isSlotIndexInGapBand, priceForSlot, priceSlotEqual } from './utils/math.js';
|
|
105
127
|
import { parseChainOrder, findMatchingGridOrderByOpenOrder, applyChainSizeToGridOrder, convertToSpreadPlaceholder, virtualizeOrder, buildFillKey, isOrderPlaced, hasOnChainId, isOrderVirtual, resolveSpreadOrderSide, duplicateOrphanLogInfo } from './utils/order.js';
|
|
106
128
|
import { parseSlotIndex } from './utils/slot.js';
|
|
107
129
|
import { resolveProcessedFillPersistenceMode } from './processed_fill_store.js';
|
|
@@ -117,6 +139,9 @@ import { getErrorMessage } from '../utils/errors.js';
|
|
|
117
139
|
* Test seam: when manager._confirmEmptyReadFn is a function it is used
|
|
118
140
|
* instead of the chain read (must resolve to a raw order array, or null
|
|
119
141
|
* when the re-read itself is ambiguous). A throwing seam is 'ambiguous'.
|
|
142
|
+
* When manager._skipEmptyReadConfirmDelay is truthy the production
|
|
143
|
+
* SYNC_EMPTY_READ_CONFIRM_DELAY_MS pacing is skipped (tests); the confirm
|
|
144
|
+
* semantics (confirmed / contradicted / ambiguous / unavailable) are unchanged.
|
|
120
145
|
*
|
|
121
146
|
* @param {Object} mgr - OrderManager instance (accountId, config, logger).
|
|
122
147
|
* @returns {Promise<string>} 'confirmed' (still empty — accept),
|
|
@@ -130,7 +155,9 @@ async function confirmSuspectEmptyRead(mgr) {
|
|
|
130
155
|
if (mgr?.config?.dryRun)
|
|
131
156
|
return 'unavailable';
|
|
132
157
|
const seam = mgr?._confirmEmptyReadFn;
|
|
133
|
-
const delay =
|
|
158
|
+
const delay = mgr?._skipEmptyReadConfirmDelay
|
|
159
|
+
? 0
|
|
160
|
+
: Math.max(0, Number(TIMING.SYNC_EMPTY_READ_CONFIRM_DELAY_MS) || 0);
|
|
134
161
|
if (typeof seam === 'function') {
|
|
135
162
|
if (delay > 0)
|
|
136
163
|
await sleep(delay);
|
|
@@ -264,7 +291,75 @@ function describeNearestAdoptionCandidates(mgr, chainOrder, precision, calcToler
|
|
|
264
291
|
* @param {Function} calcToleranceFn - calculatePriceTolerance function
|
|
265
292
|
* @returns {{candidateSlotId: string, candidateSlotPrice: number, priceDiff: number, tolerance: number}|null}
|
|
266
293
|
*/
|
|
294
|
+
/**
|
|
295
|
+
* Assert (non-fatally) that an adopted slot still carries its own genesis
|
|
296
|
+
* level, not a price inherited from the chain order.
|
|
297
|
+
*
|
|
298
|
+
* Both adoption paths keep the slot's price by NOT assigning it — an absence of
|
|
299
|
+
* a write. That is correct but invisible: nothing would notice if a later edit
|
|
300
|
+
* added `price: chainOrder.price` back, and nothing would notice a slot whose
|
|
301
|
+
* price had already been corrupted before it reached adoption. This makes the
|
|
302
|
+
* rule explicit and warns when it is broken.
|
|
303
|
+
*
|
|
304
|
+
* Deliberately non-throwing: adoption already has handled rejection paths, and
|
|
305
|
+
* turning a warning into a crash here would strand a live chain order. The
|
|
306
|
+
* caller's existing rejection handling stays the enforcement; this is the
|
|
307
|
+
* signal that surfaces the corruption in the log.
|
|
308
|
+
*
|
|
309
|
+
* @returns {boolean} true when the slot's price is its genesis level (or cannot
|
|
310
|
+
* be judged), false when it demonstrably is not.
|
|
311
|
+
*/
|
|
312
|
+
function adoptedSlotKeepsItsOwnPrice(mgr, adopted, chainOrderId, path) {
|
|
313
|
+
try {
|
|
314
|
+
const genesis = mgr?._genesis;
|
|
315
|
+
const levels = genesis?.priceLevels;
|
|
316
|
+
if (!Array.isArray(levels) || levels.length === 0)
|
|
317
|
+
return true; // no ladder: unjudgeable, fail open
|
|
318
|
+
const idx = parseSlotIndex(adopted?.id);
|
|
319
|
+
if (idx === null || !Number.isFinite(idx) || idx < 0 || idx >= levels.length)
|
|
320
|
+
return true;
|
|
321
|
+
const expected = Number(priceForSlot(idx, genesis));
|
|
322
|
+
const got = Number(adopted?.price);
|
|
323
|
+
if (!Number.isFinite(expected) || expected <= 0)
|
|
324
|
+
return true;
|
|
325
|
+
if (!Number.isFinite(got))
|
|
326
|
+
return true;
|
|
327
|
+
const diff = Math.abs(got - expected);
|
|
328
|
+
const rel = diff / Math.max(1e-12, Math.abs(expected));
|
|
329
|
+
if (rel > 1e-9 && diff > 1e-12) {
|
|
330
|
+
mgr.logger?.log?.(`[SYNC] Adoption (${path}) for ${chainOrderId} into slot ${adopted.id}: slot price ${got} is NOT the slot's ` +
|
|
331
|
+
`genesis level ${expected} (drift ${(rel * 100).toFixed(3)}%). Adoption must not write the chain order's price ` +
|
|
332
|
+
`into slot.price — the slot keeps its own level. Adopting anyway (the chain order is real); investigate what ` +
|
|
333
|
+
`corrupted this slot's price.`, 'warn');
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return true; // never let an observability check break adoption
|
|
340
|
+
}
|
|
341
|
+
}
|
|
267
342
|
async function adoptChainOrderIntoSlot(mgr, slot, chainOrder, chainOrderId, rawChainOrders, matchedGridOrderIds, chainOrderIdsOnGrid, filledOrders, updatedOrders, skipAccounting) {
|
|
343
|
+
// ADOPTION NEVER TAKES THE CHAIN ORDER'S PRICE.
|
|
344
|
+
//
|
|
345
|
+
// Two different prices are conflated on this path, and only one of them is
|
|
346
|
+
// a grid position:
|
|
347
|
+
//
|
|
348
|
+
// slot.price what this slot's LEVEL is -> must equal priceForSlot(idx, genesis)
|
|
349
|
+
// chainOrder.price where the order RESTS on chain -> may differ legitimately
|
|
350
|
+
//
|
|
351
|
+
// They legitimately differ after a grid regeneration: the grid is
|
|
352
|
+
// recentered, old chain orders keep resting at the OLD grid's levels, and
|
|
353
|
+
// adoption adopts the order into the nearest slot (slotIndexForPrice). That
|
|
354
|
+
// is normal and is why this function accepts a differing chainOrder.price.
|
|
355
|
+
//
|
|
356
|
+
// It must not WRITE it. The resting price is chain metadata about where the
|
|
357
|
+
// order was placed; it disappears on the next rotation/cancel. Writing it
|
|
358
|
+
// into slot.price is the S1 corruption this whole invariant exists to
|
|
359
|
+
// prevent, so `price` is deliberately absent from the assignments below and
|
|
360
|
+
// the slot keeps its own level in `bestMatch` (spread from `slot`).
|
|
361
|
+
//
|
|
362
|
+
// Size is the opposite: size IS real state, so the chain value is adopted.
|
|
268
363
|
let bestMatch = { ...slot };
|
|
269
364
|
const wasVirtual = slot.state === ORDER_STATES.VIRTUAL;
|
|
270
365
|
const wasPartial = slot.state === ORDER_STATES.PARTIAL;
|
|
@@ -329,6 +424,7 @@ async function adoptChainOrderIntoSlot(mgr, slot, chainOrder, chainOrderId, rawC
|
|
|
329
424
|
else if (wasPartial) {
|
|
330
425
|
bestMatch.state = ORDER_STATES.PARTIAL;
|
|
331
426
|
}
|
|
427
|
+
adoptedSlotKeepsItsOwnPrice(mgr, bestMatch, chainOrderId, 'genesis');
|
|
332
428
|
const applied = await mgr._applyOrderUpdate(bestMatch, 'sync-pass2-orphan', { skipAccounting: skipAccounting, fee: 0 });
|
|
333
429
|
if (applied === false) {
|
|
334
430
|
// Same un-poisoning as the filled path above: the slot counts as
|
|
@@ -1219,6 +1315,20 @@ class SyncEngine {
|
|
|
1219
1315
|
});
|
|
1220
1316
|
if (adoptedSlot && !matchedGridOrderIds.has(adoptedSlot.id) && !adoptedSlot.orderId) {
|
|
1221
1317
|
const precision = (chainOrder.type === ORDER_TYPES.SELL) ? assetAPrecision : assetBPrecision;
|
|
1318
|
+
// Legacy path parity with the genesis adoption path: the
|
|
1319
|
+
// slot's price is its GENESIS level and is never overwritten
|
|
1320
|
+
// with the chain order's price. Adopting chainOrder.price
|
|
1321
|
+
// here made the slot's price whichever price happened to be
|
|
1322
|
+
// on the book, which is how an off-grid order could become a
|
|
1323
|
+
// grid level and then be re-emitted as a plan price. The
|
|
1324
|
+
// size below is still the chain value: size is real state,
|
|
1325
|
+
// price is a ladder position.
|
|
1326
|
+
const legacyRail = isSlotInRail(mgr.boundaryIdx, mgr._gapSlots ?? 0, chainOrder.type, adoptedSlot);
|
|
1327
|
+
if (!legacyRail) {
|
|
1328
|
+
unmatchedChainOrders.push({ chainOrderId, type: chainOrder.type, price: chainOrder.price, size: chainOrder.size, raw: rawChainOrders.get(chainOrderId), reason: 'out-of-rail-deferred', candidateSlotId: adoptedSlot.id });
|
|
1329
|
+
mgr.logger?.log?.(`[SYNC] Orphaned chain order ${chainOrderId} (${chainOrder.type}, price=${chainOrder.price}, size=${chainOrder.size}) — NOT adopted into slot ${adoptedSlot.id}: slot is outside the active rail for a ${chainOrder.type}; deferred`, 'warn');
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1222
1332
|
const chainInt = floatToBlockchainInt(chainOrder.size, precision);
|
|
1223
1333
|
const adoptedRaw = rawChainOrders.get(chainOrderId);
|
|
1224
1334
|
const adoptedState = chainInt > 0 ? ORDER_STATES.PARTIAL : ORDER_STATES.VIRTUAL;
|
|
@@ -1226,16 +1336,32 @@ class SyncEngine {
|
|
|
1226
1336
|
const rawFee = toFiniteNumber(adoptedRaw.deferred_fee, null);
|
|
1227
1337
|
return rawFee !== null && rawFee > 0 ? { deferredFee: blockchainToFloat(rawFee, BTS_PRECISION) } : undefined;
|
|
1228
1338
|
})() : undefined;
|
|
1339
|
+
// ADOPTION NEVER TAKES THE CHAIN ORDER'S PRICE (legacy parity).
|
|
1340
|
+
//
|
|
1341
|
+
// Same rule as adoptChainOrderIntoSlot, stated here because
|
|
1342
|
+
// this path is the one that used to get it wrong: it set
|
|
1343
|
+
// `price: chainOrder.price`, so a slot's id and its price
|
|
1344
|
+
// could disagree — an off-grid order became a grid level and
|
|
1345
|
+
// was then re-emitted as a plan price (S1).
|
|
1346
|
+
//
|
|
1347
|
+
// `...adoptedSlot` below carries the slot's own level and
|
|
1348
|
+
// `price` is deliberately NOT reassigned. A differing
|
|
1349
|
+
// chainOrder.price is expected (a regeneration leaves old
|
|
1350
|
+
// orders resting at old levels) and is not corruption; it
|
|
1351
|
+
// simply is not this slot's price.
|
|
1352
|
+
//
|
|
1353
|
+
// Size differs: size is real state, so the chain value is
|
|
1354
|
+
// adopted. Price is a ladder position; it is not.
|
|
1229
1355
|
const adoptedOrder = {
|
|
1230
1356
|
...adoptedSlot,
|
|
1231
1357
|
orderId: chainOrderId,
|
|
1232
1358
|
type: chainOrder.type,
|
|
1233
1359
|
state: adoptedState,
|
|
1234
1360
|
size: chainOrder.size,
|
|
1235
|
-
price: chainOrder.price,
|
|
1236
1361
|
rawOnChain: adoptedRaw ? { ...adoptedRaw, fetchedAt: Date.now() } : adoptedRaw,
|
|
1237
1362
|
...(adoptedBtsFeeState ? { btsFeeState: adoptedBtsFeeState } : {}),
|
|
1238
1363
|
};
|
|
1364
|
+
adoptedSlotKeepsItsOwnPrice(mgr, adoptedOrder, chainOrderId, 'legacy-fallback');
|
|
1239
1365
|
const applied = await mgr._applyOrderUpdate(adoptedOrder, 'sync-pass2-adopt-orphan', { skipAccounting: skipAccounting, fee: 0 });
|
|
1240
1366
|
if (applied === false) {
|
|
1241
1367
|
// Fatal rejection: parity with the genesis adoption path —
|
|
@@ -1392,7 +1518,7 @@ class SyncEngine {
|
|
|
1392
1518
|
}
|
|
1393
1519
|
else {
|
|
1394
1520
|
try {
|
|
1395
|
-
const residualOrder = await
|
|
1521
|
+
const residualOrder = await readSingleOrderSeam(mgr, matchedGridOrder.orderId, 3000);
|
|
1396
1522
|
residualForSale = residualOrder ? toFiniteNumber(residualOrder.for_sale, null) : null;
|
|
1397
1523
|
}
|
|
1398
1524
|
catch (residualErr) {
|
|
@@ -1538,7 +1664,7 @@ class SyncEngine {
|
|
|
1538
1664
|
let chainRefetched = false;
|
|
1539
1665
|
if (ctx.driftSignal) {
|
|
1540
1666
|
try {
|
|
1541
|
-
const fresh = await
|
|
1667
|
+
const fresh = await readSingleOrderSeam(mgr, orderId, 3000);
|
|
1542
1668
|
if (fresh) {
|
|
1543
1669
|
const freshForSale = toFiniteNumber(fresh.for_sale, null);
|
|
1544
1670
|
if (freshForSale !== null && Number.isFinite(freshForSale)) {
|
|
@@ -1749,7 +1875,7 @@ class SyncEngine {
|
|
|
1749
1875
|
const refetchMap = new Map();
|
|
1750
1876
|
if (driftOrderIds.size > 0) {
|
|
1751
1877
|
try {
|
|
1752
|
-
const batchResults = await
|
|
1878
|
+
const batchResults = await batchReadOrdersSeam(mgr, [...driftOrderIds], 3000);
|
|
1753
1879
|
for (const [orderId, freshOrder] of batchResults) {
|
|
1754
1880
|
if (freshOrder) {
|
|
1755
1881
|
const freshForSale = toFiniteNumber(freshOrder.for_sale, null);
|
|
@@ -2049,8 +2175,30 @@ class SyncEngine {
|
|
|
2049
2175
|
? rawType
|
|
2050
2176
|
: null;
|
|
2051
2177
|
const descriptorPrice = toFiniteNumber(placedDescriptor?.price, NaN);
|
|
2178
|
+
// Resolve the slot's authority: the GENESIS LEVEL,
|
|
2179
|
+
// not the descriptor. Used for BOTH price and type.
|
|
2180
|
+
// Deriving the type from descriptorPrice (as this
|
|
2181
|
+
// once did) let a corrupt descriptor pick the side
|
|
2182
|
+
// while the price was corrected to the ladder, so a
|
|
2183
|
+
// slot whose level is on the BUY side could
|
|
2184
|
+
// materialize as SELL @ <buy-level> — price and type
|
|
2185
|
+
// disagreeing in one order object.
|
|
2186
|
+
const materializeIdx = parseSlotIndex(gridOrderId);
|
|
2187
|
+
let ladderLevel = null;
|
|
2188
|
+
try {
|
|
2189
|
+
const genesis = mgr._genesis;
|
|
2190
|
+
if (materializeIdx !== null && Array.isArray(genesis?.priceLevels) && genesis.priceLevels.length > 0) {
|
|
2191
|
+
const onLadder = Number(priceForSlot(materializeIdx, genesis));
|
|
2192
|
+
if (Number.isFinite(onLadder) && onLadder > 0)
|
|
2193
|
+
ladderLevel = onLadder;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
catch { /* fall back to the descriptor below */ }
|
|
2197
|
+
// Only fall back to the descriptor when the ladder
|
|
2198
|
+
// cannot speak (migration / unparseable id).
|
|
2052
2199
|
if (!materializeType && Number.isFinite(descriptorPrice)) {
|
|
2053
|
-
|
|
2200
|
+
const sidePrice = ladderLevel ?? descriptorPrice;
|
|
2201
|
+
materializeType = resolveSpreadOrderSide(sidePrice, mgr.config.startPrice);
|
|
2054
2202
|
}
|
|
2055
2203
|
const descriptorSize = toFiniteNumber(placedDescriptor?.size, NaN);
|
|
2056
2204
|
const hasDescriptor = !!materializeType
|
|
@@ -2065,14 +2213,39 @@ class SyncEngine {
|
|
|
2065
2213
|
// slot-scheme metadata are absent. The next
|
|
2066
2214
|
// readOpenOrders pass repopulates rawOnChain
|
|
2067
2215
|
// from the live order (slot-scheme ids only).
|
|
2216
|
+
//
|
|
2217
|
+
// PRICE comes from the GENESIS LADDER, not
|
|
2218
|
+
// from the descriptor. The descriptor is
|
|
2219
|
+
// whatever order object the caller carried
|
|
2220
|
+
// in, and writing it here made a slot's price
|
|
2221
|
+
// whichever price that object happened to
|
|
2222
|
+
// hold — the same carried-price-into-
|
|
2223
|
+
// slot.price class as the adoption writer,
|
|
2224
|
+
// and for a slot being materialized at a new
|
|
2225
|
+
// index it can be a price from a different
|
|
2226
|
+
// grid entirely. The descriptor price is kept
|
|
2227
|
+
// only when genesis is unavailable
|
|
2228
|
+
// (migration), where there is no ladder to
|
|
2229
|
+
// derive from.
|
|
2230
|
+
let materializePrice = descriptorPrice;
|
|
2231
|
+
let priceSource = 'descriptor';
|
|
2232
|
+
if (ladderLevel !== null && materializeIdx !== null) {
|
|
2233
|
+
materializePrice = ladderLevel;
|
|
2234
|
+
priceSource = 'genesis';
|
|
2235
|
+
}
|
|
2068
2236
|
const materializedOrder = {
|
|
2069
2237
|
id: gridOrderId,
|
|
2070
2238
|
type: materializeType,
|
|
2071
|
-
price:
|
|
2239
|
+
price: materializePrice,
|
|
2072
2240
|
size: descriptorSize,
|
|
2073
2241
|
state: isPartialPlacement ? ORDER_STATES.PARTIAL : ORDER_STATES.ACTIVE,
|
|
2074
2242
|
orderId: chainOrderId,
|
|
2075
2243
|
};
|
|
2244
|
+
if (priceSource === 'descriptor' && materializeIdx !== null) {
|
|
2245
|
+
mgr.logger?.log?.(`[SYNC] createOrder for ${gridOrderId}: materialized with the DESCRIPTOR price ` +
|
|
2246
|
+
`${descriptorPrice} because no genesis ladder was available to derive ` +
|
|
2247
|
+
`the slot level — verify this slot's price on the next sync`, 'warn');
|
|
2248
|
+
}
|
|
2076
2249
|
if (chainData.deferredFee !== undefined && chainData.deferredFee !== null) {
|
|
2077
2250
|
materializedOrder.btsFeeState = { deferredFee: Math.max(0, chainData.deferredFee) };
|
|
2078
2251
|
}
|
|
@@ -2081,10 +2254,10 @@ class SyncEngine {
|
|
|
2081
2254
|
fee: fee,
|
|
2082
2255
|
});
|
|
2083
2256
|
if (materialized === false) {
|
|
2084
|
-
mgr.logger?.log?.(`[SYNC] createOrder linkage FAILED: grid order ${gridOrderId} not in master and materializing chain order ${chainOrderId} (${materializeType} @${
|
|
2257
|
+
mgr.logger?.log?.(`[SYNC] createOrder linkage FAILED: grid order ${gridOrderId} not in master and materializing chain order ${chainOrderId} (${materializeType} @${materializePrice} x${descriptorSize}) was rejected — chain order left untracked`, 'error');
|
|
2085
2258
|
}
|
|
2086
2259
|
else {
|
|
2087
|
-
mgr.logger?.log?.(`[SYNC] createOrder for unknown grid order ${gridOrderId}: materialized ${materializeType} @${
|
|
2260
|
+
mgr.logger?.log?.(`[SYNC] createOrder for unknown grid order ${gridOrderId}: materialized ${materializeType} @${materializePrice} x${descriptorSize} (price source: ${priceSource}) -> ${chainOrderId} (master lost the slot mid-broadcast)`, 'warn');
|
|
2088
2261
|
}
|
|
2089
2262
|
}
|
|
2090
2263
|
}
|