dexbot 1.6.2 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/analysis/README.md +2 -2
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/grid_correction_check.ts +80 -7
- package/analysis/results/ama_sweep_results_lp_pool_133_1h.json +2455 -0
- package/analysis/results/bot_fitting_results_lp_pool_133_1h.json +218 -0
- package/analysis/tradingview/h-bts_tradingview.html +1570 -0
- package/analysis/tradingview/t-bts_tradingview.html +1570 -0
- 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/grid_correction_check.d.ts +5 -1
- package/dist/analysis/grid_correction_check.d.ts.map +1 -1
- package/dist/analysis/grid_correction_check.js +81 -7
- package/dist/analysis/grid_correction_check.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +81 -2
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +482 -8
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +6 -1
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +3 -1
- 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 +17 -5
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +45 -7
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +160 -15
- package/dist/modules/order/utils/order.js.map +1 -1
- package/docs/DEXBOT_COMPARISON.md +3 -3
- package/docs/EVOLUTION.md +8 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/README.md +1 -1
- package/package.json +1 -1
|
@@ -198,6 +198,117 @@ function _filterUnmatchedChainOrders(manager, chainOrderId) {
|
|
|
198
198
|
manager._lastUnmatchedChainOrders = manager._lastUnmatchedChainOrders.filter((u) => (u?.id || u?.orderId || u?.chainOrderId) !== chainOrderId);
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Remove a single correction entry by its full queue key
|
|
203
|
+
* (chainOrderId + surplus flag). The queue's upsert key is
|
|
204
|
+
* (chainOrderId, isSurplus), so a chain-order-only filter would silently
|
|
205
|
+
* discard a sibling entry (e.g. a cancel-only orphan sharing the id with
|
|
206
|
+
* a price update). Callers pass the entry's own isSurplus flag.
|
|
207
|
+
*/
|
|
208
|
+
function _removeCorrectionEntry(manager, chainOrderId, isSurplus) {
|
|
209
|
+
const surplus = Boolean(isSurplus);
|
|
210
|
+
if (manager && Array.isArray(manager.ordersNeedingPriceCorrection)) {
|
|
211
|
+
manager.ordersNeedingPriceCorrection = manager.ordersNeedingPriceCorrection.filter((c) => c?.chainOrderId !== chainOrderId || Boolean(c?.isSurplus) !== surplus);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Stamp queue provenance on a correction entry: queued-at timestamp plus
|
|
216
|
+
* the detector that produced it. Existing provenance (e.g. a fresher
|
|
217
|
+
* re-queue refreshing queuedAt) is preserved on merge — the sync_engine
|
|
218
|
+
* upsert spreads the new entry over the old one, so a re-queued entry
|
|
219
|
+
* keeps its original queuedAt unless the caller explicitly refreshes it.
|
|
220
|
+
* @param {Object} entry - Correction entry being queued
|
|
221
|
+
* @param {string} source - provenance tag (see queuedBy values)
|
|
222
|
+
* @returns {Object} The same entry, stamped
|
|
223
|
+
*/
|
|
224
|
+
function _stampCorrectionProvenance(entry, source) {
|
|
225
|
+
if (entry && typeof entry === 'object') {
|
|
226
|
+
if (entry.queuedAt == null)
|
|
227
|
+
entry.queuedAt = Date.now();
|
|
228
|
+
if (entry.queuedBy == null)
|
|
229
|
+
entry.queuedBy = source;
|
|
230
|
+
}
|
|
231
|
+
return entry;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Drain-time staleness validation for a price-update correction entry
|
|
235
|
+
* (Fix 1): verify the queued intent still matches the LIVE grid geometry
|
|
236
|
+
* before broadcasting.
|
|
237
|
+
*
|
|
238
|
+
* A correction entry snapshots {slot id, chainOrderId, expectedPrice} at
|
|
239
|
+
* queue time. Any geometry-changing resync (trigger-file resync,
|
|
240
|
+
* reconcileGridOrders startup path, COW commit re-map) can re-slot the
|
|
241
|
+
* chain order or move the slot's price afterwards, leaving the entry
|
|
242
|
+
* stale. Broadcasting it would REVERT the resync's placement — the
|
|
243
|
+
* duplicate-price-level incident class (stale UPDATE is the exact
|
|
244
|
+
* negation of the resync's placement, to the satoshi).
|
|
245
|
+
*
|
|
246
|
+
* An entry is actionable only when the live slot:
|
|
247
|
+
* 1. still exists in the master grid,
|
|
248
|
+
* 2. still owns this chainOrderId (not re-slotted / adopted elsewhere),
|
|
249
|
+
* 3. still targets the queued price — via priceSlotEqual on genesis
|
|
250
|
+
* grids (same integer-round-trip predicate the pass-1 detector
|
|
251
|
+
* uses) or calculatePriceTolerance on legacy grids (same predicate
|
|
252
|
+
* the detector uses there).
|
|
253
|
+
*
|
|
254
|
+
* Cancel-type entries (cancelOnly / isSurplus) are exempt — a cancel is
|
|
255
|
+
* idempotent (gone orders resolve via the orderGone path) and never
|
|
256
|
+
* re-prices onto a stale level.
|
|
257
|
+
*
|
|
258
|
+
* @param {Object} manager - OrderManager instance (live grid + assets)
|
|
259
|
+
* @param {Object} entry - Queued correction entry
|
|
260
|
+
* @returns {{valid: boolean, reason: string}} valid=false drops the entry
|
|
261
|
+
*/
|
|
262
|
+
function _validatePriceCorrectionEntry(manager, entry) {
|
|
263
|
+
if (!entry || entry.cancelOnly === true || entry.isSurplus === true) {
|
|
264
|
+
return { valid: true, reason: 'cancel-type' };
|
|
265
|
+
}
|
|
266
|
+
const slotId = entry?.gridOrder?.id;
|
|
267
|
+
const slot = (slotId && manager?.orders instanceof Map) ? manager.orders.get(slotId) : null;
|
|
268
|
+
if (!slot) {
|
|
269
|
+
return { valid: false, reason: `slot ${slotId || '?'} no longer exists` };
|
|
270
|
+
}
|
|
271
|
+
if (slot.orderId !== entry.chainOrderId) {
|
|
272
|
+
return { valid: false, reason: `slot ${slotId} now owns ${slot.orderId || 'no order'} (entry targets ${entry.chainOrderId})` };
|
|
273
|
+
}
|
|
274
|
+
const assets = manager?.assets;
|
|
275
|
+
const precision = entry.type === ORDER_TYPES.SELL ? assets?.assetA?.precision : assets?.assetB?.precision;
|
|
276
|
+
let priceMatches = false;
|
|
277
|
+
try {
|
|
278
|
+
const genesis = manager?._genesis;
|
|
279
|
+
if (genesis && Array.isArray(genesis.priceLevels)) {
|
|
280
|
+
priceMatches = priceSlotEqual(slot.price, entry.expectedPrice, precision);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
const tolerance = MathUtils.calculatePriceTolerance(entry.expectedPrice, entry.size, entry.type, assets);
|
|
284
|
+
priceMatches = Math.abs(slot.price - entry.expectedPrice) <= (tolerance ?? 0);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
priceMatches = slot.price === entry.expectedPrice;
|
|
289
|
+
}
|
|
290
|
+
if (!priceMatches) {
|
|
291
|
+
return { valid: false, reason: `slot ${slotId} now targets ${slot.price} (entry queued ${entry.expectedPrice})` };
|
|
292
|
+
}
|
|
293
|
+
// Size check: the broadcast sends amountToSell from the QUEUED snapshot.
|
|
294
|
+
// A partial fill between queue and drain changes the slot's booked size;
|
|
295
|
+
// pushing the stale size would over-write the fill (chain side rebuilds
|
|
296
|
+
// the delta from a live re-read, so it cannot corrupt, but it can still
|
|
297
|
+
// surprise). Integer-quantum comparison, same convention as the
|
|
298
|
+
// pass-1 size check — a fill-changed entry drops and the next sync
|
|
299
|
+
// re-queues from the fresh size if the order is still off-target.
|
|
300
|
+
try {
|
|
301
|
+
const sizePrecision = entry.type === ORDER_TYPES.SELL ? assets?.assetA?.precision : assets?.assetB?.precision;
|
|
302
|
+
if (isValidNumber(slot.size) && isValidNumber(entry.size)
|
|
303
|
+
&& floatToBlockchainInt(slot.size, sizePrecision) !== floatToBlockchainInt(entry.size, sizePrecision)) {
|
|
304
|
+
return { valid: false, reason: `slot ${slotId} size moved ${entry.size} -> ${slot.size} (fill changed it after queueing)` };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Precision unavailable — fail open on size (ownership + price hold).
|
|
309
|
+
}
|
|
310
|
+
return { valid: true, reason: 'live-slot-match' };
|
|
311
|
+
}
|
|
201
312
|
// ================================================================================
|
|
202
313
|
// SECTION 1: CHAIN ORDER MATCHING & RECONCILIATION
|
|
203
314
|
// ================================================================================
|
|
@@ -416,7 +527,7 @@ async function correctOrderPriceOnChain(manager, correctionInfo, accountName, pr
|
|
|
416
527
|
}
|
|
417
528
|
finally {
|
|
418
529
|
if (shouldRemove) {
|
|
419
|
-
manager
|
|
530
|
+
_removeCorrectionEntry(manager, chainOrderId, correctionInfo?.isSurplus);
|
|
420
531
|
}
|
|
421
532
|
}
|
|
422
533
|
}
|
|
@@ -448,7 +559,7 @@ async function correctOrderPriceOnChain(manager, correctionInfo, accountName, pr
|
|
|
448
559
|
}
|
|
449
560
|
finally {
|
|
450
561
|
if (shouldRemove) {
|
|
451
|
-
manager
|
|
562
|
+
_removeCorrectionEntry(manager, chainOrderId, correctionInfo?.isSurplus);
|
|
452
563
|
}
|
|
453
564
|
}
|
|
454
565
|
}
|
|
@@ -478,14 +589,20 @@ async function correctOrderPriceOnChain(manager, correctionInfo, accountName, pr
|
|
|
478
589
|
// The guard returns before the try/finally below, so drop the entry
|
|
479
590
|
// from the correction queue here — otherwise it would linger forever
|
|
480
591
|
// and re-attempt on every sync cycle.
|
|
481
|
-
manager
|
|
592
|
+
_removeCorrectionEntry(manager, chainOrderId, correctionInfo?.isSurplus);
|
|
482
593
|
return { success: false, skipped: true, error: 'crossed-placement-guard' };
|
|
483
594
|
}
|
|
484
595
|
try {
|
|
485
596
|
const updateResult = await accountOrders.updateOrder(accountName, privateKey, chainOrderId, { amountToSell, minToReceive });
|
|
486
597
|
if (updateResult === null) {
|
|
598
|
+
// Zero-delta no-op: the chain order already equals the target
|
|
599
|
+
// (replayed correction, sub-unit rounding, or a landed update
|
|
600
|
+
// observed via a lagging read). Resolved, not failed — counting
|
|
601
|
+
// it as failed turns routine no-ops into permanent MAINT WARN
|
|
602
|
+
// noise ("Delta is 0; skipping" every cycle) that hides real
|
|
603
|
+
// reverts.
|
|
487
604
|
shouldRemove = true;
|
|
488
|
-
return { success:
|
|
605
|
+
return { success: true, skipped: true };
|
|
489
606
|
}
|
|
490
607
|
shouldRemove = true;
|
|
491
608
|
return { success: true };
|
|
@@ -510,7 +627,7 @@ async function correctOrderPriceOnChain(manager, correctionInfo, accountName, pr
|
|
|
510
627
|
}
|
|
511
628
|
finally {
|
|
512
629
|
if (shouldRemove) {
|
|
513
|
-
manager
|
|
630
|
+
_removeCorrectionEntry(manager, chainOrderId, correctionInfo?.isSurplus);
|
|
514
631
|
}
|
|
515
632
|
}
|
|
516
633
|
}
|
|
@@ -533,8 +650,7 @@ async function _resolveCancelledCorrection(manager, entry) {
|
|
|
533
650
|
});
|
|
534
651
|
}
|
|
535
652
|
_filterUnmatchedChainOrders(manager, chainOrderId);
|
|
536
|
-
|
|
537
|
-
.filter((c) => c.chainOrderId !== chainOrderId);
|
|
653
|
+
_removeCorrectionEntry(manager, chainOrderId, entry?.isSurplus);
|
|
538
654
|
}
|
|
539
655
|
/**
|
|
540
656
|
* Broadcast all cancel-type corrections (cancelOnly duplicate orphans +
|
|
@@ -671,20 +787,49 @@ async function correctAllPriceMismatches(manager, accountName, privateKey, accou
|
|
|
671
787
|
const results = [];
|
|
672
788
|
let corrected = 0;
|
|
673
789
|
let failed = 0;
|
|
790
|
+
let staleDropped = 0;
|
|
791
|
+
// Dedupe on the full queue key (chainOrderId + surplus flag),
|
|
792
|
+
// matching the sync upsert key. A chain-order-only key would drop a
|
|
793
|
+
// sibling entry (price update + cancel sharing one chain id) before
|
|
794
|
+
// it ever drains.
|
|
674
795
|
const seen = new Set();
|
|
675
796
|
const ordersToCorrect = (manager.ordersNeedingPriceCorrection || []).filter((c) => {
|
|
676
|
-
if (!c.chainOrderId
|
|
797
|
+
if (!c.chainOrderId)
|
|
677
798
|
return false;
|
|
678
|
-
|
|
799
|
+
const key = `${c.chainOrderId}|${Boolean(c.isSurplus)}`;
|
|
800
|
+
if (seen.has(key))
|
|
801
|
+
return false;
|
|
802
|
+
seen.add(key);
|
|
679
803
|
return true;
|
|
680
804
|
});
|
|
681
|
-
|
|
805
|
+
// Fix 1 — drain-time staleness validation for price-update entries:
|
|
806
|
+
// a geometry-changing resync between queue and drain leaves entries
|
|
807
|
+
// whose slot was re-slotted or re-priced. Broadcasting them would
|
|
808
|
+
// revert the resync's placement (duplicate-price-level incident).
|
|
809
|
+
// Validate against the LIVE slot; drop stale entries (the next sync
|
|
810
|
+
// re-queues if the order is genuinely still off-target). Cancel-type
|
|
811
|
+
// entries are exempt (idempotent, never re-price).
|
|
812
|
+
const liveEntries = [];
|
|
813
|
+
for (const entry of ordersToCorrect) {
|
|
814
|
+
const check = _validatePriceCorrectionEntry(manager, entry);
|
|
815
|
+
if (check.valid) {
|
|
816
|
+
liveEntries.push(entry);
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
_removeCorrectionEntry(manager, entry.chainOrderId, entry?.isSurplus);
|
|
820
|
+
staleDropped++;
|
|
821
|
+
results.push({ ...entry, result: { success: true, skipped: true, staleDropped: true, staleReason: check.reason } });
|
|
822
|
+
manager?.logger?.log?.(`[CORRECTION] Dropping stale price correction for ${entry.chainOrderId} ` +
|
|
823
|
+
`(queued ${entry.queuedBy || 'unknown-source'}@${entry.queuedAt ? new Date(entry.queuedAt).toISOString() : 'unknown-time'}): ` +
|
|
824
|
+
`${check.reason}; re-queued by next sync if still off-target`, 'info');
|
|
825
|
+
}
|
|
826
|
+
const canBatch = liveEntries.length > 1
|
|
682
827
|
&& typeof accountOrders?.buildCancelOrderOp === 'function'
|
|
683
828
|
&& typeof accountOrders?.executeBatch === 'function';
|
|
684
|
-
let serialEntries =
|
|
829
|
+
let serialEntries = liveEntries;
|
|
685
830
|
if (canBatch) {
|
|
686
|
-
const cancelEntries =
|
|
687
|
-
const updateEntries =
|
|
831
|
+
const cancelEntries = liveEntries.filter((c) => c.cancelOnly === true || c.isSurplus === true);
|
|
832
|
+
const updateEntries = liveEntries.filter((c) => !(c.cancelOnly === true || c.isSurplus === true));
|
|
688
833
|
if (cancelEntries.length > 1) {
|
|
689
834
|
const batchOutcome = await _batchCancelCorrections(manager, cancelEntries, accountName, privateKey, accountOrders);
|
|
690
835
|
corrected += batchOutcome.corrected;
|
|
@@ -708,7 +853,7 @@ async function correctAllPriceMismatches(manager, accountName, privateKey, accou
|
|
|
708
853
|
if (corrected > 0 && typeof manager.persistGrid === 'function') {
|
|
709
854
|
await manager.persistGrid();
|
|
710
855
|
}
|
|
711
|
-
return { corrected, failed, results };
|
|
856
|
+
return { corrected, failed, results, staleDropped };
|
|
712
857
|
});
|
|
713
858
|
}
|
|
714
859
|
// ================================================================================
|
|
@@ -2828,6 +2973,6 @@ function collectKnownOnChainOrderIds(mgr, placedResults, placedContexts, extraCr
|
|
|
2828
2973
|
const all = new Set([...masterIds, ...createIds]);
|
|
2829
2974
|
return { masterIds: [...masterIds], createIds: [...createIds], all: [...all] };
|
|
2830
2975
|
}
|
|
2831
|
-
export { parseChainOrder, findMatchingGridOrderByOpenOrder, applyChainSizeToGridOrder, buildFillKey, correctOrderPriceOnChain, correctAllPriceMismatches, buildCreateOrderArgs, getOrderTypeFromUpdatedFlags, resolveConfiguredPriceBound, virtualizeOrder, convertToSpreadPlaceholder, toRailHolePlaceholder, geometryTypeForSlotIndex, detectGapEvacuationCandidates, updateGapEvacuationStreaks, resolveSpreadOrderSide, chainOrderMatchesSlot, chainOrderMatchesSlotWithTolerance, crossingCandidateChainId, isCrossingCheckCandidate, buildCrossingCheckCandidates, parseSlotIndex, filterOrdersByType, buildOutsideInPairGroups, extractBatchOperationResults, formatUnmatchedChainOrder, isNonBlockingUnmatchedOrder, isStrandedHoldOrder, isOrderOnChain, isOrderVirtual, hasOnChainId, isOrderPlaced, isPhantomOrder, isSlotAvailable, isEmptyGridSlot, isOrderHealthy, checkSizeThreshold, checkSizesBeforeMinimum, calculateIdealBoundary, assignGridRoles, resolveOnChainRetypeType, shouldFlagOutOfSpread, buildIndexes, validateIndexes, ordersEqual, buildDelta, deriveTargetBoundary, isShiftEligibleFill, resolveReserveCount, resolveReserveOrders, selectReserveEdgeSlots, getActiveOrdersTotal, getSideBudget, calculateBudgetedSizes, buildCreateOpFingerprint, isOrderGoneErrorMessage, recordDuplicateOrphanDetection, clearDuplicateOrphanDetection, duplicateOrphanLogInfo, chainOrderUnchangedFromCache, detectCrossedBookPlan, collectKnownOnChainOrderIds, reserveEdgeIdSet, liveWindowIdSet, checkGridPriceInvariant, reportGridPriceInvariant };
|
|
2976
|
+
export { parseChainOrder, findMatchingGridOrderByOpenOrder, applyChainSizeToGridOrder, buildFillKey, correctOrderPriceOnChain, correctAllPriceMismatches, _validatePriceCorrectionEntry, _stampCorrectionProvenance, buildCreateOrderArgs, getOrderTypeFromUpdatedFlags, resolveConfiguredPriceBound, virtualizeOrder, convertToSpreadPlaceholder, toRailHolePlaceholder, geometryTypeForSlotIndex, detectGapEvacuationCandidates, updateGapEvacuationStreaks, resolveSpreadOrderSide, chainOrderMatchesSlot, chainOrderMatchesSlotWithTolerance, crossingCandidateChainId, isCrossingCheckCandidate, buildCrossingCheckCandidates, parseSlotIndex, filterOrdersByType, buildOutsideInPairGroups, extractBatchOperationResults, formatUnmatchedChainOrder, isNonBlockingUnmatchedOrder, isStrandedHoldOrder, isOrderOnChain, isOrderVirtual, hasOnChainId, isOrderPlaced, isPhantomOrder, isSlotAvailable, isEmptyGridSlot, isOrderHealthy, checkSizeThreshold, checkSizesBeforeMinimum, calculateIdealBoundary, assignGridRoles, resolveOnChainRetypeType, shouldFlagOutOfSpread, buildIndexes, validateIndexes, ordersEqual, buildDelta, deriveTargetBoundary, isShiftEligibleFill, resolveReserveCount, resolveReserveOrders, selectReserveEdgeSlots, getActiveOrdersTotal, getSideBudget, calculateBudgetedSizes, buildCreateOpFingerprint, isOrderGoneErrorMessage, recordDuplicateOrphanDetection, clearDuplicateOrphanDetection, duplicateOrphanLogInfo, chainOrderUnchangedFromCache, detectCrossedBookPlan, collectKnownOnChainOrderIds, reserveEdgeIdSet, liveWindowIdSet, checkGridPriceInvariant, reportGridPriceInvariant };
|
|
2832
2977
|
export { resolveReserveEdgeAnchorPrice, resolveLiveReserveEdgeAnchorPrice, compareReserveEdge, collectRefillSlotIds };
|
|
2833
2978
|
//# sourceMappingURL=order.js.map
|