dexbot 1.4.19 → 1.4.21

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 (302) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +22 -12
  3. package/analysis/ama_fitting/analyze_lambda_vs_slow.ts +2 -2
  4. package/analysis/ama_fitting/calibrate_convergence_er.ts +1 -1
  5. package/analysis/ama_fitting/optimizer_high_resolution.ts +2 -1
  6. package/analysis/ama_fitting/package.json +1 -1
  7. package/analysis/analyze_derivatives.ts +4 -2
  8. package/analysis/analyze_dynamic_weight.ts +3 -1
  9. package/analysis/analyze_kalman.ts +3 -1
  10. package/analysis/analyze_regime.ts +3 -1
  11. package/analysis/analyze_regime_windows.ts +3 -1
  12. package/analysis/analyze_volatility.ts +2 -2
  13. package/analysis/bot_fitting/backtest_ama_sweep.ts +2 -1
  14. package/analysis/bot_fitting/backtest_bot_fitting.ts +17 -12
  15. package/analysis/bot_fitting/shared_utils.ts +1 -1
  16. package/analysis/bot_key_utils.ts +1 -5
  17. package/analysis/bot_usage/kibana_bot_queries.ts +1 -256
  18. package/analysis/chart_css.ts +1 -1
  19. package/analysis/chart_utils.ts +24 -0
  20. package/analysis/derivative_chart_generator.ts +3 -2
  21. package/analysis/math_utils.ts +1 -8
  22. package/analysis/trend_detection/DYNAMIC_WEIGHT_RESEARCH.md +1 -1
  23. package/analysis/trend_detection/dynamic_weight_chart_generator.ts +91 -55
  24. package/analysis/trend_detection/package.json +1 -1
  25. package/claw/ecosystem.config.cjs +2 -3
  26. package/claw/examples/claw_profiles_example.ts +4 -1
  27. package/claw/examples/memu_integration_example.ts +17 -17
  28. package/claw/modules/chain_actions.ts +51 -54
  29. package/claw/modules/chain_broadcast.ts +66 -104
  30. package/claw/modules/claw_bridge.ts +60 -80
  31. package/claw/modules/claw_catalog.ts +48 -17
  32. package/claw/modules/claw_infra.ts +1 -5
  33. package/claw/modules/claw_launcher.ts +47 -49
  34. package/claw/modules/claw_skill_md.ts +14 -16
  35. package/claw/modules/credit_runtime_adapter.ts +2 -18
  36. package/claw/modules/decision_loop.ts +4 -0
  37. package/claw/modules/dexbot_bridge.ts +1 -1
  38. package/claw/modules/dexbot_profiles.ts +8 -22
  39. package/claw/modules/honest_ecosystem.ts +24 -8
  40. package/claw/modules/launcher_paths.ts +4 -2
  41. package/claw/modules/liquidity_pools.ts +10 -3
  42. package/claw/modules/mcp_utils.ts +109 -0
  43. package/claw/modules/memu_bridge.ts +76 -54
  44. package/claw/modules/mpa_utils.ts +26 -3
  45. package/claw/modules/position_discovery.ts +16 -27
  46. package/claw/modules/position_manager.ts +32 -41
  47. package/claw/modules/position_manager_watch.ts +3 -2
  48. package/claw/modules/short_mpa_strategy.ts +1 -9
  49. package/claw/modules/skill_utils.ts +5 -3
  50. package/claw/modules/utils.ts +9 -1
  51. package/claw/package.json +1 -1
  52. package/claw/runtimes/openclaw-plugin/index.ts +27 -10
  53. package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
  54. package/claw/runtimes/openclaw-plugin/package.json +1 -1
  55. package/claw/scripts/claw_bridge.ts +1 -1
  56. package/claw/scripts/claw_mcp_server.ts +19 -78
  57. package/claw/scripts/honest_assets_report.ts +19 -84
  58. package/claw/scripts/memu_mcp_server.ts +57 -155
  59. package/claw/tests/test_claw_bridge.ts +6 -2
  60. package/claw/tests/test_claw_mcp_transport.ts +2 -2
  61. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js +2 -2
  62. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js.map +1 -1
  63. package/dist/analysis/ama_fitting/calibrate_convergence_er.js +1 -1
  64. package/dist/analysis/ama_fitting/calibrate_convergence_er.js.map +1 -1
  65. package/dist/analysis/ama_fitting/optimizer_high_resolution.d.ts.map +1 -1
  66. package/dist/analysis/ama_fitting/optimizer_high_resolution.js +2 -1
  67. package/dist/analysis/ama_fitting/optimizer_high_resolution.js.map +1 -1
  68. package/dist/analysis/analyze_derivatives.d.ts.map +1 -1
  69. package/dist/analysis/analyze_derivatives.js +4 -2
  70. package/dist/analysis/analyze_derivatives.js.map +1 -1
  71. package/dist/analysis/analyze_dynamic_weight.js +3 -1
  72. package/dist/analysis/analyze_dynamic_weight.js.map +1 -1
  73. package/dist/analysis/analyze_kalman.js +3 -1
  74. package/dist/analysis/analyze_kalman.js.map +1 -1
  75. package/dist/analysis/analyze_regime.js +3 -1
  76. package/dist/analysis/analyze_regime.js.map +1 -1
  77. package/dist/analysis/analyze_regime_windows.js +3 -1
  78. package/dist/analysis/analyze_regime_windows.js.map +1 -1
  79. package/dist/analysis/analyze_volatility.js +2 -2
  80. package/dist/analysis/analyze_volatility.js.map +1 -1
  81. package/dist/analysis/bot_fitting/backtest_ama_sweep.d.ts.map +1 -1
  82. package/dist/analysis/bot_fitting/backtest_ama_sweep.js +2 -1
  83. package/dist/analysis/bot_fitting/backtest_ama_sweep.js.map +1 -1
  84. package/dist/analysis/bot_fitting/backtest_bot_fitting.js +18 -13
  85. package/dist/analysis/bot_fitting/backtest_bot_fitting.js.map +1 -1
  86. package/dist/analysis/bot_fitting/shared_utils.d.ts +1 -12
  87. package/dist/analysis/bot_fitting/shared_utils.d.ts.map +1 -1
  88. package/dist/analysis/bot_fitting/shared_utils.js +1 -1
  89. package/dist/analysis/bot_fitting/shared_utils.js.map +1 -1
  90. package/dist/analysis/bot_key_utils.d.ts +1 -1
  91. package/dist/analysis/bot_key_utils.d.ts.map +1 -1
  92. package/dist/analysis/bot_key_utils.js +1 -5
  93. package/dist/analysis/bot_key_utils.js.map +1 -1
  94. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +1 -337
  95. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
  96. package/dist/analysis/bot_usage/kibana_bot_queries.js +1 -241
  97. package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
  98. package/dist/analysis/chart_css.d.ts +1 -15
  99. package/dist/analysis/chart_css.d.ts.map +1 -1
  100. package/dist/analysis/chart_css.js +1 -1
  101. package/dist/analysis/chart_css.js.map +1 -1
  102. package/dist/analysis/chart_utils.d.ts +0 -3
  103. package/dist/analysis/chart_utils.d.ts.map +1 -1
  104. package/dist/analysis/chart_utils.js +25 -0
  105. package/dist/analysis/chart_utils.js.map +1 -1
  106. package/dist/analysis/derivative_chart_generator.d.ts.map +1 -1
  107. package/dist/analysis/derivative_chart_generator.js +3 -2
  108. package/dist/analysis/derivative_chart_generator.js.map +1 -1
  109. package/dist/analysis/math_utils.d.ts +3 -5
  110. package/dist/analysis/math_utils.d.ts.map +1 -1
  111. package/dist/analysis/math_utils.js +3 -5
  112. package/dist/analysis/math_utils.js.map +1 -1
  113. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -1
  114. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +82 -50
  115. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -1
  116. package/dist/bot.js +2 -2
  117. package/dist/bot.js.map +1 -1
  118. package/dist/credential-daemon.js +1 -1
  119. package/dist/credential-daemon.js.map +1 -1
  120. package/dist/dexbot.d.ts.map +1 -1
  121. package/dist/dexbot.js +20 -17
  122. package/dist/dexbot.js.map +1 -1
  123. package/dist/market_adapter/ama_signal_runner.js +3 -3
  124. package/dist/market_adapter/core/kibana_client.d.ts +1 -3
  125. package/dist/market_adapter/core/kibana_client.d.ts.map +1 -1
  126. package/dist/market_adapter/core/kibana_client.js +1 -1
  127. package/dist/market_adapter/core/kibana_client.js.map +1 -1
  128. package/dist/market_adapter/core/market_adapter_service.d.ts +1 -2
  129. package/dist/market_adapter/core/market_adapter_service.d.ts.map +1 -1
  130. package/dist/market_adapter/core/market_adapter_service.js +9 -4
  131. package/dist/market_adapter/core/market_adapter_service.js.map +1 -1
  132. package/dist/market_adapter/core/strategies/regime_gate.d.ts +1 -2
  133. package/dist/market_adapter/core/strategies/regime_gate.d.ts.map +1 -1
  134. package/dist/market_adapter/core/strategies/regime_gate.js +1 -1
  135. package/dist/market_adapter/core/strategies/regime_gate.js.map +1 -1
  136. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +2 -2
  137. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -1
  138. package/dist/market_adapter/inputs/fetch_lp_data.d.ts +5 -5
  139. package/dist/market_adapter/inputs/fetch_lp_data.js +5 -5
  140. package/dist/market_adapter/inputs/kibana_source.d.ts +1 -2
  141. package/dist/market_adapter/inputs/kibana_source.d.ts.map +1 -1
  142. package/dist/market_adapter/inputs/kibana_source.js +1 -1
  143. package/dist/market_adapter/inputs/kibana_source.js.map +1 -1
  144. package/dist/market_adapter/lp_chart_strategy_loader.d.ts +1 -2
  145. package/dist/market_adapter/lp_chart_strategy_loader.d.ts.map +1 -1
  146. package/dist/market_adapter/lp_chart_strategy_loader.js +4 -4
  147. package/dist/market_adapter/lp_chart_strategy_loader.js.map +1 -1
  148. package/dist/market_adapter/market_adapter.d.ts.map +1 -1
  149. package/dist/market_adapter/market_adapter.js +2 -1
  150. package/dist/market_adapter/market_adapter.js.map +1 -1
  151. package/dist/modules/account_bots.d.ts +3 -0
  152. package/dist/modules/account_bots.d.ts.map +1 -1
  153. package/dist/modules/account_bots.js +156 -46
  154. package/dist/modules/account_bots.js.map +1 -1
  155. package/dist/modules/account_orders.d.ts +1 -7
  156. package/dist/modules/account_orders.d.ts.map +1 -1
  157. package/dist/modules/account_orders.js +9 -17
  158. package/dist/modules/account_orders.js.map +1 -1
  159. package/dist/modules/bitshares_client.d.ts +2 -14
  160. package/dist/modules/bitshares_client.d.ts.map +1 -1
  161. package/dist/modules/bitshares_client.js +22 -31
  162. package/dist/modules/bitshares_client.js.map +1 -1
  163. package/dist/modules/bot_settings.js +1 -1
  164. package/dist/modules/bot_settings.js.map +1 -1
  165. package/dist/modules/chain_keys.js +2 -2
  166. package/dist/modules/chain_keys.js.map +1 -1
  167. package/dist/modules/chain_orders.d.ts +2 -3
  168. package/dist/modules/chain_orders.d.ts.map +1 -1
  169. package/dist/modules/chain_orders.js +8 -25
  170. package/dist/modules/chain_orders.js.map +1 -1
  171. package/dist/modules/config.d.ts +2 -0
  172. package/dist/modules/config.d.ts.map +1 -1
  173. package/dist/modules/config.js +6 -1
  174. package/dist/modules/config.js.map +1 -1
  175. package/dist/modules/constants.d.ts +0 -1
  176. package/dist/modules/constants.d.ts.map +1 -1
  177. package/dist/modules/constants.js +1 -5
  178. package/dist/modules/constants.js.map +1 -1
  179. package/dist/modules/credential_policy.js +1 -1
  180. package/dist/modules/credential_policy.js.map +1 -1
  181. package/dist/modules/dexbot_class.d.ts.map +1 -1
  182. package/dist/modules/dexbot_class.js +3 -2
  183. package/dist/modules/dexbot_class.js.map +1 -1
  184. package/dist/modules/dexbot_cow_runtime.d.ts +0 -7
  185. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  186. package/dist/modules/dexbot_cow_runtime.js +95 -0
  187. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  188. package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
  189. package/dist/modules/dexbot_state_recovery.js +22 -2
  190. package/dist/modules/dexbot_state_recovery.js.map +1 -1
  191. package/dist/modules/fund_registry.d.ts.map +1 -1
  192. package/dist/modules/fund_registry.js +5 -2
  193. package/dist/modules/fund_registry.js.map +1 -1
  194. package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -1
  195. package/dist/modules/launcher/bot_supervisor.js +6 -7
  196. package/dist/modules/launcher/bot_supervisor.js.map +1 -1
  197. package/dist/modules/launcher/foreign_cred_daemon.d.ts.map +1 -1
  198. package/dist/modules/launcher/foreign_cred_daemon.js +6 -0
  199. package/dist/modules/launcher/foreign_cred_daemon.js.map +1 -1
  200. package/dist/modules/launcher/market_adapter_runtime.d.ts +2 -3
  201. package/dist/modules/launcher/market_adapter_runtime.d.ts.map +1 -1
  202. package/dist/modules/launcher/market_adapter_runtime.js +7 -15
  203. package/dist/modules/launcher/market_adapter_runtime.js.map +1 -1
  204. package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
  205. package/dist/modules/launcher/monolithic_runtime.js +1 -0
  206. package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
  207. package/dist/modules/launcher/status_reporting.d.ts +1 -2
  208. package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
  209. package/dist/modules/launcher/status_reporting.js +1 -4
  210. package/dist/modules/launcher/status_reporting.js.map +1 -1
  211. package/dist/modules/order/accounting.js +2 -2
  212. package/dist/modules/order/accounting.js.map +1 -1
  213. package/dist/modules/order/format.d.ts +0 -8
  214. package/dist/modules/order/format.d.ts.map +1 -1
  215. package/dist/modules/order/format.js +0 -8
  216. package/dist/modules/order/format.js.map +1 -1
  217. package/dist/modules/order/grid.d.ts.map +1 -1
  218. package/dist/modules/order/grid.js +146 -32
  219. package/dist/modules/order/grid.js.map +1 -1
  220. package/dist/modules/order/logger.js +1 -1
  221. package/dist/modules/order/logger.js.map +1 -1
  222. package/dist/modules/order/logger_state.d.ts +1 -13
  223. package/dist/modules/order/logger_state.d.ts.map +1 -1
  224. package/dist/modules/order/logger_state.js +1 -11
  225. package/dist/modules/order/logger_state.js.map +1 -1
  226. package/dist/modules/order/manager.d.ts +13 -0
  227. package/dist/modules/order/manager.d.ts.map +1 -1
  228. package/dist/modules/order/manager.js +75 -2
  229. package/dist/modules/order/manager.js.map +1 -1
  230. package/dist/modules/order/processed_fill_store.d.ts +0 -5
  231. package/dist/modules/order/processed_fill_store.d.ts.map +1 -1
  232. package/dist/modules/order/processed_fill_store.js +1 -12
  233. package/dist/modules/order/processed_fill_store.js.map +1 -1
  234. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  235. package/dist/modules/order/sync_engine.js +10 -3
  236. package/dist/modules/order/sync_engine.js.map +1 -1
  237. package/dist/modules/order/utils/math.d.ts +80 -1
  238. package/dist/modules/order/utils/math.d.ts.map +1 -1
  239. package/dist/modules/order/utils/math.js +138 -2
  240. package/dist/modules/order/utils/math.js.map +1 -1
  241. package/dist/modules/order/utils/order.d.ts.map +1 -1
  242. package/dist/modules/order/utils/order.js +10 -2
  243. package/dist/modules/order/utils/order.js.map +1 -1
  244. package/dist/modules/paths.d.ts +26 -1
  245. package/dist/modules/paths.d.ts.map +1 -1
  246. package/dist/modules/paths.js +39 -6
  247. package/dist/modules/paths.js.map +1 -1
  248. package/dist/modules/runtime_settings.js +3 -1
  249. package/dist/modules/runtime_settings.js.map +1 -1
  250. package/dist/modules/settings_merge.d.ts.map +1 -1
  251. package/dist/modules/settings_merge.js +3 -1
  252. package/dist/modules/settings_merge.js.map +1 -1
  253. package/dist/modules/storage/browser_adapter.d.ts +8 -3
  254. package/dist/modules/storage/browser_adapter.d.ts.map +1 -1
  255. package/dist/modules/storage/browser_adapter.js +53 -4
  256. package/dist/modules/storage/browser_adapter.js.map +1 -1
  257. package/dist/modules/utils/sanitize_key.d.ts +7 -0
  258. package/dist/modules/utils/sanitize_key.d.ts.map +1 -0
  259. package/dist/modules/utils/sanitize_key.js +15 -0
  260. package/dist/modules/utils/sanitize_key.js.map +1 -0
  261. package/dist/pm2.js +6 -6
  262. package/dist/pm2.js.map +1 -1
  263. package/dist/scripts/analyze-git.js +2 -1
  264. package/dist/scripts/analyze-git.js.map +1 -1
  265. package/dist/scripts/analyze-orders.d.ts +2 -2
  266. package/dist/scripts/analyze-orders.d.ts.map +1 -1
  267. package/dist/scripts/analyze-orders.js +6 -14
  268. package/dist/scripts/analyze-orders.js.map +1 -1
  269. package/dist/scripts/runner.js +1 -1
  270. package/dist/scripts/runner.js.map +1 -1
  271. package/dist/scripts/update.js +1 -1
  272. package/dist/scripts/update.js.map +1 -1
  273. package/docs/BITSHARES_ONBOARDING.md +63 -18
  274. package/docs/COPY_ON_WRITE_MASTER_PLAN.md +5 -14
  275. package/docs/COW_INVARIANTS.md +4 -4
  276. package/docs/CREDENTIAL_SECURITY.md +0 -11
  277. package/docs/DEXBOT_COMPARISON.md +11 -11
  278. package/docs/EVOLUTION.md +12 -10
  279. package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +27 -83
  280. package/docs/GRID_RECALCULATION.md +1 -13
  281. package/docs/GRID_RECONCILE.md +24 -24
  282. package/docs/LIFECYCLE.md +4 -4
  283. package/docs/LOGGING.md +1 -1
  284. package/docs/MPA_CREDIT_USAGE.md +3 -3
  285. package/docs/README.md +2 -2
  286. package/docs/WORKFLOW.md +2 -2
  287. package/docs/architecture.md +43 -38
  288. package/docs/developer_guide.md +16 -18
  289. package/market_adapter/README.md +0 -2
  290. package/modules/README.md +2 -1
  291. package/package.json +2 -3
  292. package/scripts/README.md +1 -1
  293. package/scripts/lib/dexbot-paths.sh +20 -11
  294. package/claw/openclaw.plugin.json +0 -13
  295. package/dist/market_adapter/merge_lp_data.d.ts +0 -3
  296. package/dist/market_adapter/merge_lp_data.d.ts.map +0 -1
  297. package/dist/market_adapter/merge_lp_data.js +0 -125
  298. package/dist/market_adapter/merge_lp_data.js.map +0 -1
  299. package/dist/market_adapter/utils/paths.d.ts +0 -3
  300. package/dist/market_adapter/utils/paths.d.ts.map +0 -1
  301. package/dist/market_adapter/utils/paths.js +0 -5
  302. package/dist/market_adapter/utils/paths.js.map +0 -1
@@ -111,7 +111,7 @@ function _snapshotFundState(manager) {
111
111
  sellLocked: Number(manager.accountTotals?.sellLocked || 0),
112
112
  };
113
113
  }
114
- import { floatToBlockchainInt, getPrecisionByOrderType, getPrecisionsForManager, calculateOrderCreationFees, calculateOrderSizes, calculateRotationOrderSizes, calculateAvailableFundsValue, calculateGridSideDivergenceMetric, getPrecisionSlack, getMinOrderSize, getSingleDustThreshold, getGridBestPrices, calculateSpreadFromOrders, allocateFundsByWeights, calculateGapSlots as _mathGapSlots, findPriceCollision, getBtsSide, getSellStartIdx, resolveGapBand, countGapBandSpread, adjustBudgetForBtsFees, clamp, } from './utils/math.js';
114
+ import { floatToBlockchainInt, getPrecisionByOrderType, getPrecisionsForManager, calculateOrderCreationFees, calculateOrderSizes, calculateRotationOrderSizes, calculateAvailableFundsValue, calculateGridSideDivergenceMetric, getPrecisionSlack, getMinOrderSize, getSingleDustThreshold, getGridBestPrices, calculateSpreadFromOrders, allocateFundsByWeights, calculateGapSlots as _mathGapSlots, findPriceCollision, getBtsSide, getSellStartIdx, resolveGapBand, countGapBandSpread, validatePersistedBoundary, adjustBudgetForBtsFees, clamp, } from './utils/math.js';
115
115
  import { filterOrdersByType, checkSizesBeforeMinimum, checkSizeThreshold, resolveConfiguredPriceBound, shouldFlagOutOfSpread, isOrderHealthy, isPhantomOrder, isSlotAvailable, isOrderOnChain, isOrderPlaced, hasOnChainId, isEmptyGridSlot, parseSlotIndex, calculateIdealBoundary, assignGridRoles, resolveOnChainRetypeType } from './utils/order.js';
116
116
  import { loadAmaCenterPrice, loadAmaCenterSnapshot, withBlockchainRetry } from './utils/system.js';
117
117
  import { derivePriceWithPoolRef } from './utils/withPoolRef.js';
@@ -468,10 +468,57 @@ export async function loadGrid(manager, grid, boundaryIdx = null) {
468
468
  await manager.resetFunds();
469
469
  manager.funds.btsFeesOwed = savedBtsFeesOwed;
470
470
  });
471
+ // RESTORE-TIME BOUNDARY GATE: a persisted boundary is untrusted
472
+ // disk state. Any writer bug that committed AND persisted an
473
+ // overrun boundary (e.g. the pre-5eb3ca7 promotion path) would
474
+ // otherwise legalize itself on every restart: resolveGapBand
475
+ // re-derives sellStartIdx from whatever value is restored, and
476
+ // none of the runtime gates (commit gate, detectors) run before
477
+ // this point. Validate against the snapshot BEFORE restoring;
478
+ // on failure attempt to repair by re-deriving the structural
479
+ // center boundary from slot prices, falling back to "no boundary"
480
+ // (callers sync against chain right after load either way).
481
+ const loadGapSlots = calculateGapSlots(manager.config?.incrementPercent, manager.config?.targetSpreadPercent, manager.config?.gridLimits);
482
+ let restoredBoundary = typeof boundaryIdx === 'number' ? boundaryIdx : null;
483
+ if (restoredBoundary !== null) {
484
+ const check = validatePersistedBoundary(restoredBoundary, grid, loadGapSlots);
485
+ if (!check.ok) {
486
+ manager.logger?.log?.(`[GRID-LOAD] Persisted boundary rejected (${check.reason}): ${check.detail}. ` +
487
+ `Attempting re-derivation from slot prices.`, 'error');
488
+ restoredBoundary = null;
489
+ const startPrice = Number(manager.config?.startPrice);
490
+ if (Number.isFinite(startPrice) && Array.isArray(grid) && grid.length > 0) {
491
+ const priceSorted = [...grid].sort((a, b) => Number(a.price) - Number(b.price));
492
+ const derived = calculateIdealBoundary(priceSorted.map((s) => ({ price: s.price })), startPrice, loadGapSlots);
493
+ if (Number.isInteger(derived) && derived >= 0
494
+ && validatePersistedBoundary(derived, priceSorted, loadGapSlots).ok) {
495
+ // boundaryIdx indexes the STORED array ordering, not
496
+ // the price-sorted copy — map the anchor slot back
497
+ // so the repair stays correct even if a snapshot is
498
+ // not price-sorted (the honest-load path assumes it;
499
+ // the repair path must not inherit that silently).
500
+ const anchorSlot = priceSorted[derived];
501
+ let mappedIdx = grid.indexOf(anchorSlot);
502
+ if (mappedIdx === -1 && anchorSlot?.id != null) {
503
+ mappedIdx = grid.findIndex((s) => s && s.id === anchorSlot.id);
504
+ }
505
+ if (mappedIdx >= 0) {
506
+ restoredBoundary = mappedIdx;
507
+ manager.logger?.log?.(`[GRID-LOAD] Persisted boundary repaired: ${boundaryIdx} -> ${mappedIdx} ` +
508
+ `(re-derived from slot prices, sorted idx ${derived}).`, 'warn');
509
+ }
510
+ }
511
+ }
512
+ if (restoredBoundary === null) {
513
+ manager.logger?.log?.(`[GRID-LOAD] Could not re-derive a safe boundary; continuing without one. ` +
514
+ `The next sync cycle will reconcile grid geometry against the chain.`, 'error');
515
+ }
516
+ }
517
+ }
471
518
  // Restore boundary index for StrategyEngine
472
- if (typeof boundaryIdx === 'number') {
473
- manager._restoreBoundary(boundaryIdx);
474
- manager.logger?.log?.(`Restored boundary index: ${boundaryIdx}`, 'info');
519
+ if (restoredBoundary !== null) {
520
+ manager._restoreBoundary(restoredBoundary);
521
+ manager.logger?.log?.(`Restored boundary index: ${restoredBoundary}`, 'info');
475
522
  }
476
523
  // Reassign slot types based on current boundary.
477
524
  // Every slot's type must match its position in the price-sorted rail
@@ -479,11 +526,11 @@ export async function loadGrid(manager, grid, boundaryIdx = null) {
479
526
  // split spread zones and ILLEGAL_SPREAD_STATE errors. The subsequent
480
527
  // sync will detect type mismatches with chain orders (e.g. a BUY-zone
481
528
  // slot holding a SELL chain order) and auto-cancel + recreate them.
482
- if (typeof boundaryIdx === 'number') {
483
- const gapSlots = calculateGapSlots(manager.config?.incrementPercent, manager.config?.targetSpreadPercent, manager.config?.gridLimits);
529
+ if (restoredBoundary !== null) {
530
+ const gapSlots = loadGapSlots;
484
531
  manager._gapSlots = gapSlots;
485
- const buyEndIdx = boundaryIdx;
486
- const sellStartIdx = getSellStartIdx(boundaryIdx, gapSlots);
532
+ const buyEndIdx = restoredBoundary;
533
+ const sellStartIdx = getSellStartIdx(restoredBoundary, gapSlots);
487
534
  let reassignCount = 0;
488
535
  grid = grid.map((slot, i) => {
489
536
  const correctType = (i <= buyEndIdx)
@@ -535,7 +582,7 @@ export async function loadGrid(manager, grid, boundaryIdx = null) {
535
582
  return slot;
536
583
  });
537
584
  if (reassignCount > 0) {
538
- manager.logger?.log?.(`[GRID-TYPE-CORRECT] Reassigned ${reassignCount} stale slot type(s) based on boundary ${boundaryIdx}`, 'warn');
585
+ manager.logger?.log?.(`[GRID-TYPE-CORRECT] Reassigned ${reassignCount} stale slot type(s) based on boundary ${restoredBoundary}`, 'warn');
539
586
  }
540
587
  }
541
588
  // Gap 6: Grid size cap — validate grid slot count against expected maximum.
@@ -1986,29 +2033,53 @@ export function determineOrderSideByFunds(manager, currentMarketPrice) {
1986
2033
  }
1987
2034
  return { side, reason: side ? `Choosing ${side}` : 'Insufficient funds or committed inventory' };
1988
2035
  }
2036
+ /**
2037
+ * Resolve the MIN_SPREAD_ORDERS gap reserve used by boundary promotion.
2038
+ * Promotion may never consume the last `reserve` empty slots of the gap
2039
+ * band — this is the floor that keeps the spread from being zeroed.
2040
+ */
2041
+ function resolveMinSpreadOrdersReserve(manager) {
2042
+ const raw = Number(manager?.config?.gridLimits?.MIN_SPREAD_ORDERS ?? GRID_LIMITS.MIN_SPREAD_ORDERS);
2043
+ return (Number.isFinite(raw) && raw >= 0) ? Math.floor(raw) : GRID_LIMITS.MIN_SPREAD_ORDERS;
2044
+ }
1989
2045
  // Collect contiguous empty gap-band slots adjacent to a rail edge for boundary
1990
2046
  // promotion. BUY walks upward from the boundary into the gap, SELL walks
1991
2047
  // downward from the sell start; both stop at the first unavailable slot.
1992
2048
  // Mirrors are collapsed into one directional walk (step +/- 1).
1993
2049
  //
1994
- // NOTE: the promotion cap is the OPPOSITE RAIL's span, NOT the gap-band size.
1995
- // SELL promotion moves the boundary down by the promoted count (boundary =
1996
- // buyEndIdx - count), so capping at buyEndIdx keeps it >= 0. BUY promotion
1997
- // moves the boundary up; capping at the SELL rail size (maxIdx+1-sellStartIdx)
1998
- // keeps the promoted boundary at or below the last placed slot. The walk's
1999
- // own idx < sellStartIdx / idx > buyEndIdx bounds already limit promotion to
2000
- // the band, so this cap only binds when the opposite rail is smaller than the
2001
- // band — i.e. an empty opposite rail (cap 0) blocks promotion entirely.
2002
- function _collectPromotableBoundarySlots(allSlotsByPrice, railType, buyEndIdx, sellStartIdx, quota) {
2050
+ // PROMOTION CAP (gap-band geometry): promotion may consume at most
2051
+ // bandSize − MIN_SPREAD_ORDERS slots, where bandSize = sellStartIdx −
2052
+ // buyEndIdx − 1. The previous cap (the OPPOSITE RAIL's span) constrained
2053
+ // nothing: for BUY it was the sell rail's length and for SELL the buy
2054
+ // rail's length, so a single correction could promote every gap slot and
2055
+ // zero the spread. Reserving MIN_SPREAD_ORDERS empty slots guarantees the
2056
+ // gap never closes within this path regardless of quota.
2057
+ //
2058
+ // DEFENSE-IN-DEPTH WALK BOUNDS: the loop itself also stops
2059
+ // MIN_SPREAD_ORDERS short of the opposite rail edge (idx <= sellStartIdx−1−
2060
+ // reserve for BUY / idx >= buyEndIdx+1+reserve for SELL), so even if the
2061
+ // cap above is ever miscomputed the walk cannot consume the reserved gap.
2062
+ // When bandSize <= reserve the bound disables promotion entirely.
2063
+ function _collectPromotableBoundarySlots(allSlotsByPrice, railType, buyEndIdx, sellStartIdx, quota, spreadReserve, maxDepth) {
2003
2064
  const isBuy = railType === ORDER_TYPES.BUY;
2004
- const maxIdx = allSlotsByPrice.length - 1;
2005
- const maxPromotable = isBuy
2006
- ? Math.max(0, maxIdx + 1 - sellStartIdx)
2007
- : Math.max(0, buyEndIdx);
2008
- const promotionQuota = Math.min(quota, maxPromotable);
2065
+ const bandSize = Math.max(0, sellStartIdx - buyEndIdx - 1);
2066
+ // Depth is bounded by three independent caps (defense-in-depth):
2067
+ // reserve cap — keep MIN_SPREAD_ORDERS empties in the band
2068
+ // stranding cap— the boundary slide must never move a placed
2069
+ // opposite-rail order into the implied spread band;
2070
+ // constraining DEPTH here (not clamping the derived
2071
+ // boundary afterwards) keeps the promoted slots'
2072
+ // classification consistent with the new geometry
2073
+ // walk bounds — hard stop short of the opposite rail edge, even if
2074
+ // both caps above were ever miscomputed
2075
+ const maxPromotable = Math.max(0, bandSize - spreadReserve);
2076
+ const promotionQuota = Math.min(quota, maxPromotable, Math.max(0, maxDepth));
2009
2077
  const promoted = [];
2010
2078
  const step = isBuy ? 1 : -1;
2011
- for (let idx = isBuy ? buyEndIdx + 1 : sellStartIdx - 1; promoted.length < promotionQuota && (isBuy ? idx < sellStartIdx : idx > buyEndIdx); idx += step) {
2079
+ for (let idx = isBuy ? buyEndIdx + 1 : sellStartIdx - 1; promoted.length < promotionQuota
2080
+ && (isBuy
2081
+ ? idx <= sellStartIdx - 1 - spreadReserve
2082
+ : idx >= buyEndIdx + 1 + spreadReserve); idx += step) {
2012
2083
  const slot = allSlotsByPrice[idx];
2013
2084
  if (!slot || !isSlotAvailable(slot))
2014
2085
  break;
@@ -2130,9 +2201,37 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
2130
2201
  // fiction. Without promotion there is nothing to commit, so falling
2131
2202
  // back to the pre-existing candidate paths is safe.
2132
2203
  const promotedCandidates = [];
2204
+ const spreadReserve = resolveMinSpreadOrdersReserve(manager);
2205
+ // STRANDING DEPTH CAP: the post-commit geometry re-derives the band
2206
+ // from the new boundary, sliding it over opposite-rail slots. A
2207
+ // slide may never swallow an already-PLACED order into that band, so
2208
+ // promotion depth is capped up-front — constraining depth keeps the
2209
+ // derived boundary consistent with every promoted slot's placement;
2210
+ // clamping the boundary afterwards would not.
2211
+ let maxPromotionDepth = Infinity;
2212
+ if (railType === ORDER_TYPES.BUY) {
2213
+ for (let idx = Math.max(sellStartIdx, 0); idx < allSlotsByPrice.length; idx++) {
2214
+ if (allSlotsByPrice[idx]?.orderId) {
2215
+ // Implied sell zone must start at/below the lowest placed
2216
+ // sell: B' + gapSlots + 1 <= idx => depth <= idx − G − 1 − B.
2217
+ maxPromotionDepth = idx - gapSlots - 1 - buyEndIdx;
2218
+ break;
2219
+ }
2220
+ }
2221
+ }
2222
+ else {
2223
+ for (let idx = Math.min(buyEndIdx, allSlotsByPrice.length - 1); idx >= 0; idx--) {
2224
+ if (allSlotsByPrice[idx]?.orderId) {
2225
+ // Boundary may not drop below the highest placed buy:
2226
+ // B' = B − depth >= idx => depth <= B − idx.
2227
+ maxPromotionDepth = buyEndIdx - idx;
2228
+ break;
2229
+ }
2230
+ }
2231
+ }
2133
2232
  if (boundaryKnown && orphanedVirtualCandidates.length + typedSpreadCandidates.length < missingSlots) {
2134
2233
  const rawQuota = missingSlots - orphanedVirtualCandidates.length - typedSpreadCandidates.length;
2135
- promotedCandidates.push(..._collectPromotableBoundarySlots(allSlotsByPrice, railType, buyEndIdx, sellStartIdx, rawQuota));
2234
+ promotedCandidates.push(..._collectPromotableBoundarySlots(allSlotsByPrice, railType, buyEndIdx, sellStartIdx, rawQuota, spreadReserve, maxPromotionDepth));
2136
2235
  if (promotedCandidates.length > 0) {
2137
2236
  manager.logger?.log?.(`[SPREAD-CORRECTION] ${promotedCandidates.length} gap slot(s) available for boundary promotion on ${sideName}`, 'info');
2138
2237
  }
@@ -2316,15 +2415,30 @@ export async function prepareSpreadCorrectionOrders(manager, preferredSide, outO
2316
2415
  boundaryIdx = railType === ORDER_TYPES.BUY
2317
2416
  ? buyEndIdx + maxDist
2318
2417
  : buyEndIdx - maxDist;
2418
+ // PROMOTION BOUNDARY BACKSTOP (refusal-only): the boundary is
2419
+ // DERIVED from the placed promoted set (buyEndIdx ± maxDist),
2420
+ // so shifting it here would silently strand those fresh orders
2421
+ // inside the implied band. All real constraints — the
2422
+ // MIN_SPREAD_ORDERS reserve and opposite-rail stranding — are
2423
+ // enforced UPSTREAM as walk-depth caps. This backstop only
2424
+ // verifies array limits plus the reserve ceiling; any
2425
+ // violation signals an internal inconsistency, so promotion is
2426
+ // refused loudly instead of silently re-geometried.
2319
2427
  const maxIdx = allSlotsByPrice.length - 1;
2320
- const clamped = clamp(boundaryIdx, 0, maxIdx);
2321
- if (clamped !== boundaryIdx) {
2322
- manager.logger?.log?.(`[SPREAD-CORRECTION] Boundary promotion clamped on ${sideName}: ` +
2323
- `${boundaryIdx} -> ${clamped}`, 'warn');
2324
- boundaryIdx = clamped;
2428
+ const lo = 0;
2429
+ const hi = railType === ORDER_TYPES.BUY
2430
+ ? Math.min(maxIdx, sellStartIdx - 1 - spreadReserve)
2431
+ : maxIdx;
2432
+ if (lo > hi || boundaryIdx < lo || boundaryIdx > hi) {
2433
+ manager.logger?.log?.(`[SPREAD-CORRECTION] Boundary promotion refused on ${sideName}: ` +
2434
+ `derived boundary ${boundaryIdx} violates safe range [${lo}, ${hi}] ` +
2435
+ `(band ${buyEndIdx + 1}..${sellStartIdx - 1})`, 'warn');
2436
+ boundaryIdx = undefined;
2437
+ }
2438
+ else {
2439
+ manager.logger?.log?.(`[SPREAD-CORRECTION] Boundary promotion on ${sideName}: ` +
2440
+ `${buyEndIdx} -> ${boundaryIdx} (${placedPromotedIds.size} placed, maxDist ${maxDist})`, 'info');
2325
2441
  }
2326
- manager.logger?.log?.(`[SPREAD-CORRECTION] Boundary promotion on ${sideName}: ` +
2327
- `${buyEndIdx} -> ${boundaryIdx} (${placedPromotedIds.size} placed, maxDist ${maxDist})`, 'info');
2328
2442
  }
2329
2443
  else {
2330
2444
  boundaryIdx = undefined;