dexbot 1.5.1 → 1.5.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/analysis/ama_fitting/package.json +1 -1
  3. package/analysis/resolve_source.ts +3 -0
  4. package/analysis/results/ama_sweep_results_lp_pool_133_1h.json +2455 -0
  5. package/analysis/results/bot_fitting_results_lp_pool_133_1h.json +218 -0
  6. package/analysis/tradingview/README.md +5 -1
  7. package/analysis/tradingview/analyze_tradingview.ts +27 -1
  8. package/analysis/tradingview/h-bts_tradingview.html +1570 -0
  9. package/analysis/tradingview/t-bts_tradingview.html +1570 -0
  10. package/analysis/tradingview/tradingview_uplot_chart_generator.ts +351 -22
  11. package/analysis/trend_detection/package.json +1 -1
  12. package/claw/package.json +1 -1
  13. package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
  14. package/claw/runtimes/openclaw-plugin/package.json +1 -1
  15. package/claw/tests/test_claw_mcp_transport.ts +2 -2
  16. package/dist/analysis/resolve_source.d.ts.map +1 -1
  17. package/dist/analysis/resolve_source.js +3 -0
  18. package/dist/analysis/resolve_source.js.map +1 -1
  19. package/dist/analysis/tradingview/analyze_tradingview.d.ts +3 -0
  20. package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -1
  21. package/dist/analysis/tradingview/analyze_tradingview.js +27 -2
  22. package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
  23. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -1
  24. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +348 -22
  25. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -1
  26. package/dist/dexbot.js +5 -5
  27. package/dist/dexbot.js.map +1 -1
  28. package/dist/market_adapter/core/asymmetric_bounds.d.ts +33 -1
  29. package/dist/market_adapter/core/asymmetric_bounds.d.ts.map +1 -1
  30. package/dist/market_adapter/core/asymmetric_bounds.js +38 -1
  31. package/dist/market_adapter/core/asymmetric_bounds.js.map +1 -1
  32. package/dist/modules/dexbot_class.d.ts +2 -18
  33. package/dist/modules/dexbot_class.d.ts.map +1 -1
  34. package/dist/modules/dexbot_cow_runtime.d.ts +38 -37
  35. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  36. package/dist/modules/dexbot_cow_runtime.js +722 -557
  37. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  38. package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
  39. package/dist/modules/dexbot_fill_runtime.js +7 -1
  40. package/dist/modules/dexbot_fill_runtime.js.map +1 -1
  41. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  42. package/dist/modules/dexbot_maintenance_runtime.js +31 -51
  43. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  44. package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
  45. package/dist/modules/dexbot_startup_runtime.js +42 -3
  46. package/dist/modules/dexbot_startup_runtime.js.map +1 -1
  47. package/dist/modules/order/accounting.d.ts.map +1 -1
  48. package/dist/modules/order/accounting.js +19 -2
  49. package/dist/modules/order/accounting.js.map +1 -1
  50. package/dist/modules/order/export.d.ts +122 -15
  51. package/dist/modules/order/export.d.ts.map +1 -1
  52. package/dist/modules/order/export.js +310 -46
  53. package/dist/modules/order/export.js.map +1 -1
  54. package/dist/modules/order/grid.d.ts.map +1 -1
  55. package/dist/modules/order/grid.js +29 -38
  56. package/dist/modules/order/grid.js.map +1 -1
  57. package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
  58. package/dist/modules/order/grid_reconcile.js +7 -3
  59. package/dist/modules/order/grid_reconcile.js.map +1 -1
  60. package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
  61. package/dist/modules/order/grid_reconcile_internal.js +31 -33
  62. package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
  63. package/dist/modules/order/manager.d.ts +13 -6
  64. package/dist/modules/order/manager.d.ts.map +1 -1
  65. package/dist/modules/order/manager.js +22 -7
  66. package/dist/modules/order/manager.js.map +1 -1
  67. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  68. package/dist/modules/order/sync_engine.js +231 -11
  69. package/dist/modules/order/sync_engine.js.map +1 -1
  70. package/dist/modules/order/utils/math.d.ts +38 -15
  71. package/dist/modules/order/utils/math.d.ts.map +1 -1
  72. package/dist/modules/order/utils/math.js +55 -16
  73. package/dist/modules/order/utils/math.js.map +1 -1
  74. package/dist/modules/order/utils/order.d.ts +116 -20
  75. package/dist/modules/order/utils/order.d.ts.map +1 -1
  76. package/dist/modules/order/utils/order.js +340 -35
  77. package/dist/modules/order/utils/order.js.map +1 -1
  78. package/dist/modules/order/utils/system.d.ts +1 -30
  79. package/dist/modules/order/utils/system.d.ts.map +1 -1
  80. package/dist/modules/order/utils/system.js +31 -98
  81. package/dist/modules/order/utils/system.js.map +1 -1
  82. package/dist/modules/order/utils/validate.d.ts +3 -2
  83. package/dist/modules/order/utils/validate.d.ts.map +1 -1
  84. package/dist/modules/order/utils/validate.js +49 -28
  85. package/dist/modules/order/utils/validate.js.map +1 -1
  86. package/dist/scripts/analyze-credit.d.ts.map +1 -1
  87. package/dist/scripts/analyze-credit.js +53 -33
  88. package/dist/scripts/analyze-credit.js.map +1 -1
  89. package/docs/DEXBOT_COMPARISON.md +3 -3
  90. package/docs/EVOLUTION.md +7 -5
  91. package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
  92. package/docs/README.md +2 -2
  93. package/docs/WORKFLOW.md +1 -1
  94. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  * Includes grid indexing, order comparison, delta building, and strategy calculations.
6
6
  *
7
7
  * ===============================================================================
8
- * TABLE OF CONTENTS (36 exported functions)
8
+ * TABLE OF CONTENTS (35 exported functions)
9
9
  * ===============================================================================
10
10
  *
11
11
  * SECTION 1: CHAIN ORDER MATCHING & RECONCILIATION (5 functions)
@@ -46,9 +46,8 @@
46
46
  * - checkSizeThreshold(size, threshold) - Check if size exceeds threshold
47
47
  * - checkSizesBeforeMinimum(sizes, minSize) - Check sizes against minimum
48
48
  *
49
- * SECTION 7: GRID BOUNDARY & ROLES (4 functions)
49
+ * SECTION 7: GRID BOUNDARY & ROLES (3 functions)
50
50
  * - calculateIdealBoundary(allSlots, startPrice, gapSlots) - Calculate ideal boundary
51
- * - calculateFundDrivenBoundary(allSlots, availA, availB, startPrice, gapSlots) - Fund-driven boundary
52
51
  * - assignGridRoles(allSlots, boundaryIdx, gapSlots, ...) - Assign BUY/SELL roles
53
52
  * - shouldFlagOutOfSpread(order, startPrice, configSpread) - Check if order is out of spread
54
53
  *
@@ -68,7 +67,7 @@
68
67
  *
69
68
  * ===============================================================================
70
69
  */
71
- import { ORDER_TYPES, ORDER_STATES, TIMING, FEE_PARAMETERS, GRID_LIMITS, NATIVE_CLIENT, COW_PERFORMANCE } from '../../constants.js';
70
+ import { ORDER_TYPES, ORDER_STATES, TIMING, FEE_PARAMETERS, GRID_LIMITS, NATIVE_CLIENT, COW_PERFORMANCE, COW_ACTIONS } from '../../constants.js';
72
71
  import * as Format from '../format.js';
73
72
  import * as MathUtils from './math.js';
74
73
  import Logger from '../../order/logger.js';
@@ -461,11 +460,13 @@ async function correctOrderPriceOnChain(manager, correctionInfo, accountName, pr
461
460
  let shouldRemove = false;
462
461
  // CROSSING-PLACEMENT GUARD: re-pricing the chain order to its slot's
463
462
  // committed price must not cross an opposite-side live order (only
464
- // reachable when the grid geometry itself is broken). Drop the entry —
463
+ // reachable when the grid geometry itself is broken). Candidates are the
464
+ // shared master + pending-broadcast + orphan set so a re-price cannot
465
+ // cross a pending CREATE from an earlier uncertain batch. Drop the entry —
465
466
  // the next sync's price-mismatch detection re-queues the correction
466
467
  // once the crossed order is resolved (same lifecycle as a 'skipped'
467
468
  // update below).
468
- const crossed = MathUtils.findCrossedOrder(manager.orders.values(), expectedPrice, type, manager.assets, (o) => o && o.orderId && o.orderId !== chainOrderId);
469
+ const crossed = MathUtils.findCrossedOrder(buildCrossingCheckCandidates(manager), expectedPrice, type, manager.assets, (o) => isCrossingCheckCandidate(o, chainOrderId));
469
470
  if (crossed) {
470
471
  manager.logger?.log?.(`[CROSS-GUARD] Skipping price correction for ${chainOrderId} -> ${type} @${expectedPrice}: ` +
471
472
  `crosses live ${crossed.type} ${crossed.id} (${crossed.orderId}) @${crossed.price}; ` +
@@ -1001,10 +1002,14 @@ function parseSlotIndex(id) {
1001
1002
  return parseSlotIndexShared(id);
1002
1003
  }
1003
1004
  /**
1004
- * Whether a parsed chain order matches a grid slot within tolerance:
1005
- * type-compatible (slot may be SPREAD), price within tolerance, size within
1006
- * 1% quantum tolerance (floor 2 units). Shared by the startup adoption paths
1007
- * that match an uncertain-landed chain order to the slot it was created for.
1005
+ * Whether a parsed chain order matches a grid slot exactly:
1006
+ * type-compatible (slot may be SPREAD), price strictly equal via integer
1007
+ * round-trip (single epsilon), size within 1% quantum tolerance (floor 2
1008
+ * units). STRICT genesis-slot-mapping matcher — use only where the slot is
1009
+ * derived from the price (nearest-slot authority). Uncertain-landed
1010
+ * adoption (a broadcast whose on-chain price may have drifted by dust)
1011
+ * must use chainOrderMatchesSlotWithTolerance instead, or the drifted
1012
+ * order is never adopted and gets re-broadcast as a duplicate.
1008
1013
  * @param {Object} parsed - parseChainOrder output ({type, price, size, ...})
1009
1014
  * @param {Object} slot - Grid slot order object
1010
1015
  * @param {Object} assets - Manager assets ({assetA, assetB} with precision)
@@ -1027,6 +1032,131 @@ function chainOrderMatchesSlot(parsed, slot, assets) {
1027
1032
  return false;
1028
1033
  return true;
1029
1034
  }
1035
+ /**
1036
+ * Whether a parsed chain order matches a grid slot within price tolerance:
1037
+ * type-compatible (slot may be SPREAD), price within calculatePriceTolerance
1038
+ * (clamped to ~2 price quanta so dust-inflated tolerances cannot adopt a
1039
+ * wrong order), size within the same 1% quantum tolerance as the strict
1040
+ * matcher. For the UNCERTAIN-ADOPT paths only (a just-broadcast create whose
1041
+ * on-chain price may have drifted by rounding dust): the strict
1042
+ * chainOrderMatchesSlot would miss the drifted order, the slot would stay
1043
+ * VIRTUAL, and the next cycle would re-broadcast it as a duplicate.
1044
+ * Genesis slot mapping keeps the strict matcher (nearest-slot authority).
1045
+ * @param {Object} parsed - parseChainOrder output ({type, price, size, ...})
1046
+ * @param {Object} slot - Grid slot order object
1047
+ * @param {Object} assets - Manager assets ({assetA, assetB} with precision)
1048
+ * @returns {boolean}
1049
+ */
1050
+ function chainOrderMatchesSlotWithTolerance(parsed, slot, assets) {
1051
+ if (!parsed || !slot || !assets)
1052
+ return false;
1053
+ if (parsed.type !== slot.type && slot.type !== ORDER_TYPES.SPREAD)
1054
+ return false;
1055
+ const precision = parsed.type === ORDER_TYPES.SELL ? assets.assetA.precision : assets.assetB.precision;
1056
+ let tolerance = null;
1057
+ try {
1058
+ tolerance = MathUtils.calculatePriceTolerance(Math.min(parsed.price, slot.price), Math.max(parsed.size, slot.size), parsed.type, assets);
1059
+ }
1060
+ catch {
1061
+ tolerance = null;
1062
+ }
1063
+ // Clamp to ~2 price quanta (relative): dust-sized orders inflate the
1064
+ // tolerance past the grid increment, which would adopt a wrong order.
1065
+ // A null tolerance (invalid inputs) falls back to the clamp itself.
1066
+ const quantumCap = 2 * MathUtils.quantumForPrecision(precision) * Math.max(1, Math.max(parsed.price, slot.price));
1067
+ if (tolerance == null || !Number.isFinite(tolerance))
1068
+ tolerance = quantumCap;
1069
+ else
1070
+ tolerance = Math.min(tolerance, quantumCap);
1071
+ if (Math.abs(parsed.price - slot.price) > tolerance)
1072
+ return false;
1073
+ const sizeTolerance = Math.max(2, Math.floor(floatToBlockchainInt(slot.size, precision) * 0.01));
1074
+ if (Math.abs(floatToBlockchainInt(parsed.size, precision) - floatToBlockchainInt(slot.size, precision)) > sizeTolerance)
1075
+ return false;
1076
+ return true;
1077
+ }
1078
+ /**
1079
+ * Identity of a crossing-check candidate. Master orders carry orderId,
1080
+ * unmatched chain orders carry chainOrderId, pending-broadcast wrappers
1081
+ * carry slotId + order (their inner order has no chain id yet — it may not
1082
+ * even be on chain). All three classes must be visible to crossing guards:
1083
+ * an UPDATE-only rotation batch can otherwise re-price across a pending
1084
+ * CREATE from an earlier uncertain batch and self-trade (BitShares has no
1085
+ * self-trade prevention).
1086
+ * @param {Object} o - Candidate order or pending-broadcast wrapper entry
1087
+ * @returns {string|null} Chain/slot identity, or null when not placeable
1088
+ */
1089
+ function crossingCandidateChainId(o) {
1090
+ if (!o)
1091
+ return null;
1092
+ if (o.orderId)
1093
+ return o.orderId;
1094
+ if (o.chainOrderId)
1095
+ return o.chainOrderId;
1096
+ if (o.slotId && o.order)
1097
+ return o.slotId;
1098
+ return null;
1099
+ }
1100
+ /**
1101
+ * Shared predicate for every crossing-placement guard (COW create/rotation/
1102
+ * fallback, startup reconcile placement, price-correction re-queue). Accepts
1103
+ * master orders (orderId), unmatched chain orders (chainOrderId), AND
1104
+ * pending-broadcast wrappers (slotId + order) — pending entries can never be
1105
+ * in cancelOpIndexByOrderId (keyed by chain ids; wrapper orderIds are slot
1106
+ * ids in a disjoint namespace), so the exclusion check is harmless for them.
1107
+ * VIRTUAL slots (no orderId) are rejected: they have no chain presence and
1108
+ * must never block placements.
1109
+ * @param {Object} o - Candidate order or pending-broadcast wrapper entry
1110
+ * @param {string|null} [excludeChainOrderId=null] - Chain id to exempt (the order being relocated itself)
1111
+ * @param {Map|null} [cancelOpIndexByOrderId=null] - orderId -> op index of its already-queued cancel
1112
+ * @returns {boolean} True when the candidate participates in crossing checks
1113
+ */
1114
+ function isCrossingCheckCandidate(o, excludeChainOrderId = null, cancelOpIndexByOrderId = null) {
1115
+ if (!o)
1116
+ return false;
1117
+ const oid = crossingCandidateChainId(o);
1118
+ if (!oid)
1119
+ return false;
1120
+ if (excludeChainOrderId && oid === excludeChainOrderId)
1121
+ return false;
1122
+ if (cancelOpIndexByOrderId instanceof Map && cancelOpIndexByOrderId.has(oid))
1123
+ return false;
1124
+ const price = o.price ?? o.order?.price;
1125
+ if (price == null || !Number.isFinite(Number(price)))
1126
+ return false;
1127
+ return true;
1128
+ }
1129
+ /**
1130
+ * Shared candidate set for crossing-placement checks: master orders plus
1131
+ * chain-side orders that may exist on chain but are not (yet) adopted into
1132
+ * the master grid — pending-broadcast wrappers from earlier uncertain
1133
+ * batches (pushed as wrappers so the predicate can see their slotId; their
1134
+ * inner order carries type/price via findCrossedOrder's item.order fallback)
1135
+ * and unmatched chain orders (orphans). Without these, an UPDATE-only
1136
+ * rotation batch can re-price across an un-adopted chain order that
1137
+ * master-grid-only checks cannot see (the pending/unmatched batch guards
1138
+ * fire only for CREATE batches).
1139
+ * @param {Object} manager - OrderManager instance (orders Map, _pendingBroadcasts, _lastUnmatchedChainOrders)
1140
+ * @returns {any[]} Candidate orders/wrappers for findCrossedOrder
1141
+ */
1142
+ function buildCrossingCheckCandidates(manager) {
1143
+ if (!manager)
1144
+ return [];
1145
+ const candidates = manager.orders instanceof Map ? [...manager.orders.values()] : [];
1146
+ if (manager._pendingBroadcasts instanceof Map) {
1147
+ for (const entry of manager._pendingBroadcasts.values()) {
1148
+ if (entry && entry.slotId && entry.order)
1149
+ candidates.push(entry);
1150
+ }
1151
+ }
1152
+ if (Array.isArray(manager._lastUnmatchedChainOrders)) {
1153
+ for (const o of manager._lastUnmatchedChainOrders) {
1154
+ if (o && o.type != null && o.price != null)
1155
+ candidates.push(o);
1156
+ }
1157
+ }
1158
+ return candidates;
1159
+ }
1030
1160
  // ================================================================================
1031
1161
  // SECTION 4-6: FILTERING, PREDICATES & SIZE VALIDATION
1032
1162
  // ================================================================================
@@ -1300,26 +1430,6 @@ function calculateIdealBoundary(allSlots, referencePrice, gapSlots) {
1300
1430
  const buySpread = Math.floor(gapSlots / 2);
1301
1431
  return Math.max(0, Math.min(allSlots.length - 1, splitIdx - buySpread - 1));
1302
1432
  }
1303
- /**
1304
- * Calculate grid boundary based on available funds ratio.
1305
- * Distributes buy/sell slots proportional to fund values.
1306
- *
1307
- * @param {Array<Object>} allSlots - All grid slots sorted by price
1308
- * @param {number} availA - Available assetA (sell-side capital)
1309
- * @param {number} availB - Available assetB (buy-side capital)
1310
- * @param {number} price - Current reference price for valuation
1311
- * @param {number} gapSlots - Number of gap slots between buy and sell
1312
- * @returns {number} Fund-driven boundary index
1313
- */
1314
- function calculateFundDrivenBoundary(allSlots, availA, availB, price, gapSlots) {
1315
- const valA = toFiniteNumber(availA) * toFiniteNumber(price);
1316
- const valB = toFiniteNumber(availB);
1317
- const totalVal = valA + valB;
1318
- if (totalVal <= 0)
1319
- return Math.floor((allSlots.length - gapSlots) / 2);
1320
- const targetBuySlots = Math.round((allSlots.length - gapSlots) * (valB / totalVal));
1321
- return Math.max(0, Math.min(allSlots.length - gapSlots - 1, targetBuySlots - 1));
1322
- }
1323
1433
  /**
1324
1434
  * Assign BUY/SELL/SPREAD roles to grid slots based on boundary.
1325
1435
  * Slots below boundary are BUY, above boundary are SELL, between are SPREAD.
@@ -1675,10 +1785,10 @@ function deriveTargetBoundary(fills, currentBoundaryIdx, allSlots, config, gapSl
1675
1785
  }
1676
1786
  const remainingBudget = effectiveBudget - Math.abs(netShift);
1677
1787
  newBoundaryIdx += netShift;
1678
- // Clamp boundary — cap at one slot before the gap band's SELL rail,
1679
- // matching calculateFundDrivenBoundary's geometry. Degenerate geometries
1680
- // (fewer slots than the gap needs) fall back to the legacy length-1
1681
- // ceiling instead of collapsing the boundary below its current position.
1788
+ // Clamp boundary — cap at one slot before the gap band's SELL rail.
1789
+ // Degenerate geometries (fewer slots than the gap needs) fall back to the
1790
+ // legacy length-1 ceiling instead of collapsing the boundary below its
1791
+ // current position.
1682
1792
  const gapAwareCeiling = allSlots.length - gapSlots - 1;
1683
1793
  const legacyCeiling = allSlots.length - 1;
1684
1794
  const ceiling = gapAwareCeiling >= 0
@@ -1754,5 +1864,200 @@ function calculateBudgetedSizes(slots, side, budget, weightDist, incrementPercen
1754
1864
  0, precision);
1755
1865
  }
1756
1866
  // ================================================================================
1757
- export { parseChainOrder, findMatchingGridOrderByOpenOrder, applyChainSizeToGridOrder, buildFillKey, correctOrderPriceOnChain, correctAllPriceMismatches, buildCreateOrderArgs, getOrderTypeFromUpdatedFlags, resolveConfiguredPriceBound, virtualizeOrder, convertToSpreadPlaceholder, toRailHolePlaceholder, geometryTypeForSlotIndex, detectGapEvacuationCandidates, updateGapEvacuationStreaks, resolveSpreadOrderSide, chainOrderMatchesSlot, parseSlotIndex, filterOrdersByType, buildOutsideInPairGroups, extractBatchOperationResults, formatUnmatchedChainOrder, isOrderOnChain, isOrderVirtual, hasOnChainId, isOrderPlaced, isPhantomOrder, isSlotAvailable, isEmptyGridSlot, isOrderHealthy, checkSizeThreshold, checkSizesBeforeMinimum, calculateIdealBoundary, calculateFundDrivenBoundary, assignGridRoles, resolveOnChainRetypeType, shouldFlagOutOfSpread, buildIndexes, validateIndexes, ordersEqual, buildDelta, getOrderSize, deriveTargetBoundary, isShiftEligibleFill, getActiveOrdersTotal, getSideBudget, calculateBudgetedSizes, buildCreateOpFingerprint, isOrderGoneErrorMessage, recordDuplicateOrphanDetection, clearDuplicateOrphanDetection, duplicateOrphanLogInfo };
1867
+ // SECTION: COW batch-shared pure helpers (moved from dexbot_cow_runtime.ts —
1868
+ // no bot dependency; shared by the COW runtime and any future consumer).
1869
+ // ================================================================================
1870
+ /**
1871
+ * Whether a chain order still matches the cached pre-update state the
1872
+ * limit_order_update delta was built from. Only a provably-unchanged order
1873
+ * makes a re-broadcast of the identical delta safe (it applies to the same
1874
+ * base). Any other state (target applied, filled, resized) must defer.
1875
+ * @param {Object} chainOrder - Raw chain order object (get_full_accounts)
1876
+ * @param {Object|null} cachedRaw - The rawOnChain cache captured at build time
1877
+ * @returns {boolean}
1878
+ */
1879
+ function chainOrderUnchangedFromCache(chainOrder, cachedRaw) {
1880
+ if (!chainOrder || !cachedRaw)
1881
+ return false;
1882
+ const base = chainOrder.sell_price?.base;
1883
+ const quote = chainOrder.sell_price?.quote;
1884
+ const cachedBase = cachedRaw.sell_price?.base?.amount;
1885
+ const cachedQuote = cachedRaw.sell_price?.quote?.amount;
1886
+ const cachedForSale = cachedRaw.for_sale;
1887
+ if (base === undefined || quote === undefined)
1888
+ return false;
1889
+ if (cachedForSale === undefined || cachedBase === undefined || cachedQuote === undefined)
1890
+ return false;
1891
+ return String(base.amount ?? '') === String(cachedBase)
1892
+ && String(quote.amount ?? '') === String(cachedQuote)
1893
+ && String(chainOrder.for_sale ?? '') === String(cachedForSale);
1894
+ }
1895
+ /**
1896
+ * PRE-BROADCAST CROSSED-BOOK ASSERT (defense-in-depth, any-writer detection).
1897
+ *
1898
+ * Simulates the post-batch book: currently placed master orders plus this
1899
+ * batch's action overlay (CREATEs add, CANCELs remove, UPDATEs reprice/move).
1900
+ * Returns a detail string when a planned BUY would price at-or-above a planned
1901
+ * SELL — a state no honest planner produces — so the caller can refuse the
1902
+ * broadcast instead of paying for adverse fills. Placed order prices are
1903
+ * independent of grid geometry, so this catches boundary overruns regardless
1904
+ * of which writer produced them.
1905
+ *
1906
+ * Detector only: any internal failure returns null (never blocks a broadcast).
1907
+ */
1908
+ function detectCrossedBookPlan(manager, actions) {
1909
+ try {
1910
+ const startPrice = Number(manager?.config?.startPrice);
1911
+ const book = new Map();
1912
+ for (const o of Array.from(manager?.orders?.values?.() ?? [])) {
1913
+ if (!o || !o.orderId || o.price == null)
1914
+ continue;
1915
+ const price = Number(o.price);
1916
+ if (!Number.isFinite(price))
1917
+ continue;
1918
+ let type = o.type;
1919
+ if (type !== ORDER_TYPES.BUY && type !== ORDER_TYPES.SELL) {
1920
+ // Legacy SPREAD-typed placed order: derive side from the same
1921
+ // price-vs-startPrice convention used across the codebase.
1922
+ if (!Number.isFinite(startPrice))
1923
+ continue;
1924
+ type = price < startPrice ? ORDER_TYPES.BUY : ORDER_TYPES.SELL;
1925
+ }
1926
+ book.set(String(o.id), { type, price });
1927
+ }
1928
+ for (const a of actions ?? []) {
1929
+ const id = String(a.id ?? a.orderId ?? '');
1930
+ if (a.type === COW_ACTIONS.CANCEL) {
1931
+ if (id)
1932
+ book.delete(id);
1933
+ }
1934
+ else if (a.type === COW_ACTIONS.UPDATE) {
1935
+ const newPrice = Number(a.newPrice ?? a.order?.price);
1936
+ const newType = a.order?.type;
1937
+ if (id && Number.isFinite(newPrice)) {
1938
+ const entry = book.get(id);
1939
+ const type = (newType === ORDER_TYPES.BUY || newType === ORDER_TYPES.SELL)
1940
+ ? newType
1941
+ : entry?.type;
1942
+ if (entry)
1943
+ book.delete(id);
1944
+ const key = String(a.newGridId ?? id);
1945
+ if (type === ORDER_TYPES.BUY || type === ORDER_TYPES.SELL) {
1946
+ book.set(key, { type, price: newPrice });
1947
+ }
1948
+ }
1949
+ }
1950
+ else if (a.type === COW_ACTIONS.CREATE) {
1951
+ const price = Number(a.order?.price);
1952
+ const type = a.order?.type;
1953
+ if (!Number.isFinite(price) || (type !== ORDER_TYPES.BUY && type !== ORDER_TYPES.SELL))
1954
+ continue;
1955
+ if (id)
1956
+ book.set(id, { type, price });
1957
+ }
1958
+ }
1959
+ let maxBuy = -Infinity;
1960
+ let minSell = Infinity;
1961
+ for (const { type, price } of book.values()) {
1962
+ if (type === ORDER_TYPES.BUY && price > maxBuy)
1963
+ maxBuy = price;
1964
+ else if (type === ORDER_TYPES.SELL && price < minSell)
1965
+ minSell = price;
1966
+ }
1967
+ if (Number.isFinite(maxBuy) && Number.isFinite(minSell) && minSell <= maxBuy) {
1968
+ return `bestPlacedBuy=${maxBuy} >= bestPlacedSell=${minSell}`;
1969
+ }
1970
+ return null;
1971
+ }
1972
+ catch {
1973
+ return null;
1974
+ }
1975
+ }
1976
+ /**
1977
+ * Collect every on-chain order id master currently needs to converge against,
1978
+ * so adoption can re-read them by id (immune to the get_full_accounts window
1979
+ * truncation) instead of relying on a partial window read.
1980
+ *
1981
+ * Sources:
1982
+ * - master's own tracked order ids (existing on-chain orders);
1983
+ * - the batch's fresh CREATE ids extracted from the broadcast result
1984
+ * (operation_results[i][1] aligns positionally with placedContexts[i]).
1985
+ *
1986
+ * @param {any} mgr - bot.manager
1987
+ * @param {any} placedResults - broadcast result (has operation_results); null when unavailable
1988
+ * @param {any[]} placedContexts - opContexts (aligned with operation_results); null when unavailable
1989
+ * @param {string[]|null} [extraCreateIds=null] - fresh CREATE chain ids from another
1990
+ * authoritative source (e.g. the uncertain-broadcast poll confirmation) when
1991
+ * no broadcast result exists; merged into createIds so the lagging-create
1992
+ * retry guards them
1993
+ * @returns {string[]} Unique, well-formed 1.7.x order ids
1994
+ */
1995
+ function collectKnownOnChainOrderIds(mgr, placedResults, placedContexts, extraCreateIds = null) {
1996
+ const masterIds = new Set();
1997
+ const grid = mgr && mgr.grid;
1998
+ if (Array.isArray(grid)) {
1999
+ for (const slot of grid) {
2000
+ if (slot && slot.orderId && /^1\.7\.\d+$/.test(String(slot.orderId))) {
2001
+ masterIds.add(String(slot.orderId));
2002
+ }
2003
+ }
2004
+ }
2005
+ // Master tracked ids live in the orders Map (mgr.grid is legacy and
2006
+ // unset on OrderManager — without this the by-id set omits every
2007
+ // pre-existing ACTIVE order and pass-1 phantom cleanup would virtualize
2008
+ // them as fills on a partial snapshot).
2009
+ if (mgr && mgr.orders instanceof Map) {
2010
+ for (const slot of mgr.orders.values()) {
2011
+ if (slot && slot.orderId && /^1\.7\.\d+$/.test(String(slot.orderId))) {
2012
+ masterIds.add(String(slot.orderId));
2013
+ }
2014
+ }
2015
+ }
2016
+ const createIds = new Set();
2017
+ if (placedResults && Array.isArray(placedContexts)) {
2018
+ const opResults = extractBatchOperationResults(placedResults);
2019
+ if (Array.isArray(opResults)) {
2020
+ for (let i = 0; i < placedContexts.length; i++) {
2021
+ const ctx = placedContexts[i];
2022
+ if (!ctx || ctx.kind !== 'create')
2023
+ continue;
2024
+ const opResult = opResults[i] && opResults[i][1];
2025
+ if (opResult && /^1\.7\.\d+$/.test(String(opResult))) {
2026
+ createIds.add(String(opResult));
2027
+ }
2028
+ }
2029
+ }
2030
+ }
2031
+ if (Array.isArray(extraCreateIds)) {
2032
+ for (const id of extraCreateIds) {
2033
+ if (id && /^1\.7\.\d+$/.test(String(id)))
2034
+ createIds.add(String(id));
2035
+ }
2036
+ }
2037
+ // Existing chain ids referenced by non-create op contexts (cancel /
2038
+ // rotation / size-update). Pre-existing orders whose absence is expected
2039
+ // (cancels/fills in this batch), so they join the by-id set but never
2040
+ // the lagging-create guard.
2041
+ if (Array.isArray(placedContexts)) {
2042
+ for (const ctx of placedContexts) {
2043
+ if (!ctx || ctx.kind === 'create')
2044
+ continue;
2045
+ const refs = [];
2046
+ if (ctx.kind === 'cancel' && ctx.order)
2047
+ refs.push(ctx.order.orderId);
2048
+ else if (ctx.kind === 'rotation' && ctx.rotation?.oldOrder)
2049
+ refs.push(ctx.rotation.oldOrder.orderId);
2050
+ else if (ctx.kind === 'size-update' && ctx.updateInfo?.partialOrder)
2051
+ refs.push(ctx.updateInfo.partialOrder.orderId);
2052
+ for (const id of refs) {
2053
+ if (id && /^1\.7\.\d+$/.test(String(id)))
2054
+ masterIds.add(String(id));
2055
+ }
2056
+ }
2057
+ }
2058
+ const all = new Set([...masterIds, ...createIds]);
2059
+ return { masterIds: [...masterIds], createIds: [...createIds], all: [...all] };
2060
+ }
2061
+ // ================================================================================
2062
+ 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, isOrderOnChain, isOrderVirtual, hasOnChainId, isOrderPlaced, isPhantomOrder, isSlotAvailable, isEmptyGridSlot, isOrderHealthy, checkSizeThreshold, checkSizesBeforeMinimum, calculateIdealBoundary, assignGridRoles, resolveOnChainRetypeType, shouldFlagOutOfSpread, buildIndexes, validateIndexes, ordersEqual, buildDelta, getOrderSize, deriveTargetBoundary, isShiftEligibleFill, getActiveOrdersTotal, getSideBudget, calculateBudgetedSizes, buildCreateOpFingerprint, isOrderGoneErrorMessage, recordDuplicateOrphanDetection, clearDuplicateOrphanDetection, duplicateOrphanLogInfo, chainOrderUnchangedFromCache, detectCrossedBookPlan, collectKnownOnChainOrderIds };
1758
2063
  //# sourceMappingURL=order.js.map