dexbot 1.1.10

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 (807) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -0
  3. package/claw/README.md +513 -0
  4. package/claw/scripts/memu_runner.py +385 -0
  5. package/claw/skills/bitshares-guide/SKILL.md +14 -0
  6. package/claw/skills/bitshares-guide/references/example-skill-shells.md +18 -0
  7. package/claw/skills/bitshares-guide/references/presentation-patterns.md +19 -0
  8. package/claw/skills/bitshares-guide/references/scope-guardrails.md +9 -0
  9. package/claw/skills/launcher-ops/SKILL.md +18 -0
  10. package/claw/skills/launcher-ops/references/launcher-workflow.md +23 -0
  11. package/claw/skills/margin-trading/SKILL.md +23 -0
  12. package/claw/skills/margin-trading/references/honest-asset-list.md +74 -0
  13. package/claw/skills/margin-trading/references/honest-assets.md +44 -0
  14. package/claw/skills/margin-trading/references/position-management.md +203 -0
  15. package/claw/skills/margin-trading/references/trading-concepts.md +80 -0
  16. package/claw/skills/memu-memory/SKILL.md +218 -0
  17. package/claw/skills/shared/references/js-automation-overview.md +24 -0
  18. package/claw/skills/shared/references/safety-and-staleness.md +11 -0
  19. package/claw/skills/shared/references/skill-boundaries.md +17 -0
  20. package/claw/skills/trend-detection/SKILL.md +33 -0
  21. package/claw/skills/trend-detection/agents/openai.yaml +3 -0
  22. package/claw/skills/trend-detection/references/service.md +45 -0
  23. package/dist/analysis/ama_fitting/analyze_ama_price_changes.d.ts +3 -0
  24. package/dist/analysis/ama_fitting/analyze_ama_price_changes.d.ts.map +1 -0
  25. package/dist/analysis/ama_fitting/analyze_ama_price_changes.js +149 -0
  26. package/dist/analysis/ama_fitting/analyze_ama_price_changes.js.map +1 -0
  27. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.d.ts +3 -0
  28. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.d.ts.map +1 -0
  29. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js +387 -0
  30. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js.map +1 -0
  31. package/dist/analysis/ama_fitting/calibrate_convergence_er.d.ts +3 -0
  32. package/dist/analysis/ama_fitting/calibrate_convergence_er.d.ts.map +1 -0
  33. package/dist/analysis/ama_fitting/calibrate_convergence_er.js +200 -0
  34. package/dist/analysis/ama_fitting/calibrate_convergence_er.js.map +1 -0
  35. package/dist/analysis/ama_fitting/fetch_lp_candles.d.ts +3 -0
  36. package/dist/analysis/ama_fitting/fetch_lp_candles.d.ts.map +1 -0
  37. package/dist/analysis/ama_fitting/fetch_lp_candles.js +212 -0
  38. package/dist/analysis/ama_fitting/fetch_lp_candles.js.map +1 -0
  39. package/dist/analysis/ama_fitting/generate_unified_comparison_chart.d.ts +31 -0
  40. package/dist/analysis/ama_fitting/generate_unified_comparison_chart.d.ts.map +1 -0
  41. package/dist/analysis/ama_fitting/generate_unified_comparison_chart.js +249 -0
  42. package/dist/analysis/ama_fitting/generate_unified_comparison_chart.js.map +1 -0
  43. package/dist/analysis/ama_fitting/optimizer_high_resolution.d.ts +2 -0
  44. package/dist/analysis/ama_fitting/optimizer_high_resolution.d.ts.map +1 -0
  45. package/dist/analysis/ama_fitting/optimizer_high_resolution.js +782 -0
  46. package/dist/analysis/ama_fitting/optimizer_high_resolution.js.map +1 -0
  47. package/dist/analysis/analyze_derivatives.d.ts +95 -0
  48. package/dist/analysis/analyze_derivatives.d.ts.map +1 -0
  49. package/dist/analysis/analyze_derivatives.js +295 -0
  50. package/dist/analysis/analyze_derivatives.js.map +1 -0
  51. package/dist/analysis/analyze_dynamic_weight.d.ts +14 -0
  52. package/dist/analysis/analyze_dynamic_weight.d.ts.map +1 -0
  53. package/dist/analysis/analyze_dynamic_weight.js +208 -0
  54. package/dist/analysis/analyze_dynamic_weight.js.map +1 -0
  55. package/dist/analysis/analyze_kalman.d.ts +13 -0
  56. package/dist/analysis/analyze_kalman.d.ts.map +1 -0
  57. package/dist/analysis/analyze_kalman.js +123 -0
  58. package/dist/analysis/analyze_kalman.js.map +1 -0
  59. package/dist/analysis/analyze_regime.d.ts +20 -0
  60. package/dist/analysis/analyze_regime.d.ts.map +1 -0
  61. package/dist/analysis/analyze_regime.js +139 -0
  62. package/dist/analysis/analyze_regime.js.map +1 -0
  63. package/dist/analysis/analyze_regime_windows.d.ts +19 -0
  64. package/dist/analysis/analyze_regime_windows.d.ts.map +1 -0
  65. package/dist/analysis/analyze_regime_windows.js +413 -0
  66. package/dist/analysis/analyze_regime_windows.js.map +1 -0
  67. package/dist/analysis/analyze_risk_profile.d.ts +3 -0
  68. package/dist/analysis/analyze_risk_profile.d.ts.map +1 -0
  69. package/dist/analysis/analyze_risk_profile.js +159 -0
  70. package/dist/analysis/analyze_risk_profile.js.map +1 -0
  71. package/dist/analysis/analyze_trade_heatmap.d.ts +3 -0
  72. package/dist/analysis/analyze_trade_heatmap.d.ts.map +1 -0
  73. package/dist/analysis/analyze_trade_heatmap.js +367 -0
  74. package/dist/analysis/analyze_trade_heatmap.js.map +1 -0
  75. package/dist/analysis/analyze_volatility.d.ts +22 -0
  76. package/dist/analysis/analyze_volatility.d.ts.map +1 -0
  77. package/dist/analysis/analyze_volatility.js +172 -0
  78. package/dist/analysis/analyze_volatility.js.map +1 -0
  79. package/dist/analysis/bot_fitting/backtest_ama_sweep.d.ts +99 -0
  80. package/dist/analysis/bot_fitting/backtest_ama_sweep.d.ts.map +1 -0
  81. package/dist/analysis/bot_fitting/backtest_ama_sweep.js +804 -0
  82. package/dist/analysis/bot_fitting/backtest_ama_sweep.js.map +1 -0
  83. package/dist/analysis/bot_fitting/backtest_bot_fitting.d.ts +2 -0
  84. package/dist/analysis/bot_fitting/backtest_bot_fitting.d.ts.map +1 -0
  85. package/dist/analysis/bot_fitting/backtest_bot_fitting.js +347 -0
  86. package/dist/analysis/bot_fitting/backtest_bot_fitting.js.map +1 -0
  87. package/dist/analysis/bot_fitting/shared_utils.d.ts +15 -0
  88. package/dist/analysis/bot_fitting/shared_utils.d.ts.map +1 -0
  89. package/dist/analysis/bot_fitting/shared_utils.js +48 -0
  90. package/dist/analysis/bot_fitting/shared_utils.js.map +1 -0
  91. package/dist/analysis/bot_key_utils.d.ts +16 -0
  92. package/dist/analysis/bot_key_utils.d.ts.map +1 -0
  93. package/dist/analysis/bot_key_utils.js +61 -0
  94. package/dist/analysis/bot_key_utils.js.map +1 -0
  95. package/dist/analysis/bot_usage/discover_bot_accounts.d.ts +3 -0
  96. package/dist/analysis/bot_usage/discover_bot_accounts.d.ts.map +1 -0
  97. package/dist/analysis/bot_usage/discover_bot_accounts.js +498 -0
  98. package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -0
  99. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +546 -0
  100. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -0
  101. package/dist/analysis/bot_usage/kibana_bot_queries.js +427 -0
  102. package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -0
  103. package/dist/analysis/chart_utils.d.ts +13 -0
  104. package/dist/analysis/chart_utils.d.ts.map +1 -0
  105. package/dist/analysis/chart_utils.js +142 -0
  106. package/dist/analysis/chart_utils.js.map +1 -0
  107. package/dist/analysis/derivative_chart_generator.d.ts +18 -0
  108. package/dist/analysis/derivative_chart_generator.d.ts.map +1 -0
  109. package/dist/analysis/derivative_chart_generator.js +896 -0
  110. package/dist/analysis/derivative_chart_generator.js.map +1 -0
  111. package/dist/analysis/math_utils.d.ts +35 -0
  112. package/dist/analysis/math_utils.d.ts.map +1 -0
  113. package/dist/analysis/math_utils.js +121 -0
  114. package/dist/analysis/math_utils.js.map +1 -0
  115. package/dist/analysis/price_sources.d.ts +34 -0
  116. package/dist/analysis/price_sources.d.ts.map +1 -0
  117. package/dist/analysis/price_sources.js +103 -0
  118. package/dist/analysis/price_sources.js.map +1 -0
  119. package/dist/analysis/trade_profitability.d.ts +115 -0
  120. package/dist/analysis/trade_profitability.d.ts.map +1 -0
  121. package/dist/analysis/trade_profitability.js +1247 -0
  122. package/dist/analysis/trade_profitability.js.map +1 -0
  123. package/dist/analysis/tradingview/analyze_tradingview.d.ts +38 -0
  124. package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -0
  125. package/dist/analysis/tradingview/analyze_tradingview.js +212 -0
  126. package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -0
  127. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts +9 -0
  128. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -0
  129. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +1717 -0
  130. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -0
  131. package/dist/analysis/trend_detection/derivative_analyzer.d.ts +250 -0
  132. package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +1 -0
  133. package/dist/analysis/trend_detection/derivative_analyzer.js +903 -0
  134. package/dist/analysis/trend_detection/derivative_analyzer.js.map +1 -0
  135. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts +6 -0
  136. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -0
  137. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +1449 -0
  138. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -0
  139. package/dist/analysis/trend_detection/hurst_analyzer.d.ts +42 -0
  140. package/dist/analysis/trend_detection/hurst_analyzer.d.ts.map +1 -0
  141. package/dist/analysis/trend_detection/hurst_analyzer.js +159 -0
  142. package/dist/analysis/trend_detection/hurst_analyzer.js.map +1 -0
  143. package/dist/analysis/trend_detection/kalman_chart_generator.d.ts +6 -0
  144. package/dist/analysis/trend_detection/kalman_chart_generator.d.ts.map +1 -0
  145. package/dist/analysis/trend_detection/kalman_chart_generator.js +408 -0
  146. package/dist/analysis/trend_detection/kalman_chart_generator.js.map +1 -0
  147. package/dist/analysis/trend_detection/kalman_trend_analyzer.d.ts +103 -0
  148. package/dist/analysis/trend_detection/kalman_trend_analyzer.d.ts.map +1 -0
  149. package/dist/analysis/trend_detection/kalman_trend_analyzer.js +229 -0
  150. package/dist/analysis/trend_detection/kalman_trend_analyzer.js.map +1 -0
  151. package/dist/analysis/trend_detection/kalman_velocity_smoothing.d.ts +34 -0
  152. package/dist/analysis/trend_detection/kalman_velocity_smoothing.d.ts.map +1 -0
  153. package/dist/analysis/trend_detection/kalman_velocity_smoothing.js +89 -0
  154. package/dist/analysis/trend_detection/kalman_velocity_smoothing.js.map +1 -0
  155. package/dist/analysis/trend_detection/permutation_entropy_analyzer.d.ts +39 -0
  156. package/dist/analysis/trend_detection/permutation_entropy_analyzer.d.ts.map +1 -0
  157. package/dist/analysis/trend_detection/permutation_entropy_analyzer.js +132 -0
  158. package/dist/analysis/trend_detection/permutation_entropy_analyzer.js.map +1 -0
  159. package/dist/analysis/trend_detection/regime_chart_generator.d.ts +6 -0
  160. package/dist/analysis/trend_detection/regime_chart_generator.d.ts.map +1 -0
  161. package/dist/analysis/trend_detection/regime_chart_generator.js +345 -0
  162. package/dist/analysis/trend_detection/regime_chart_generator.js.map +1 -0
  163. package/dist/analysis/trend_detection/tests/test_kalman_trend.d.ts +2 -0
  164. package/dist/analysis/trend_detection/tests/test_kalman_trend.d.ts.map +1 -0
  165. package/dist/analysis/trend_detection/tests/test_kalman_trend.js +130 -0
  166. package/dist/analysis/trend_detection/tests/test_kalman_trend.js.map +1 -0
  167. package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.d.ts +2 -0
  168. package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.d.ts.map +1 -0
  169. package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.js +38 -0
  170. package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.js.map +1 -0
  171. package/dist/analysis/trend_detection/volatility_chart_generator.d.ts +6 -0
  172. package/dist/analysis/trend_detection/volatility_chart_generator.d.ts.map +1 -0
  173. package/dist/analysis/trend_detection/volatility_chart_generator.js +732 -0
  174. package/dist/analysis/trend_detection/volatility_chart_generator.js.map +1 -0
  175. package/dist/bot.d.ts +49 -0
  176. package/dist/bot.d.ts.map +1 -0
  177. package/dist/bot.js +221 -0
  178. package/dist/bot.js.map +1 -0
  179. package/dist/credential-daemon.d.ts +64 -0
  180. package/dist/credential-daemon.d.ts.map +1 -0
  181. package/dist/credential-daemon.js +987 -0
  182. package/dist/credential-daemon.js.map +1 -0
  183. package/dist/dexbot.d.ts +87 -0
  184. package/dist/dexbot.d.ts.map +1 -0
  185. package/dist/dexbot.js +1222 -0
  186. package/dist/dexbot.js.map +1 -0
  187. package/dist/market_adapter/ama_signal_runner.d.ts +3 -0
  188. package/dist/market_adapter/ama_signal_runner.d.ts.map +1 -0
  189. package/dist/market_adapter/ama_signal_runner.js +175 -0
  190. package/dist/market_adapter/ama_signal_runner.js.map +1 -0
  191. package/dist/market_adapter/candle_utils.d.ts +57 -0
  192. package/dist/market_adapter/candle_utils.d.ts.map +1 -0
  193. package/dist/market_adapter/candle_utils.js +252 -0
  194. package/dist/market_adapter/candle_utils.js.map +1 -0
  195. package/dist/market_adapter/core/asymmetric_bounds.d.ts +20 -0
  196. package/dist/market_adapter/core/asymmetric_bounds.d.ts.map +1 -0
  197. package/dist/market_adapter/core/asymmetric_bounds.js +88 -0
  198. package/dist/market_adapter/core/asymmetric_bounds.js.map +1 -0
  199. package/dist/market_adapter/core/config_normalizers.d.ts +10 -0
  200. package/dist/market_adapter/core/config_normalizers.d.ts.map +1 -0
  201. package/dist/market_adapter/core/config_normalizers.js +24 -0
  202. package/dist/market_adapter/core/config_normalizers.js.map +1 -0
  203. package/dist/market_adapter/core/kibana_candles.d.ts +74 -0
  204. package/dist/market_adapter/core/kibana_candles.d.ts.map +1 -0
  205. package/dist/market_adapter/core/kibana_candles.js +249 -0
  206. package/dist/market_adapter/core/kibana_candles.js.map +1 -0
  207. package/dist/market_adapter/core/kibana_client.d.ts +27 -0
  208. package/dist/market_adapter/core/kibana_client.d.ts.map +1 -0
  209. package/dist/market_adapter/core/kibana_client.js +120 -0
  210. package/dist/market_adapter/core/kibana_client.js.map +1 -0
  211. package/dist/market_adapter/core/kibana_market_candles.d.ts +143 -0
  212. package/dist/market_adapter/core/kibana_market_candles.d.ts.map +1 -0
  213. package/dist/market_adapter/core/kibana_market_candles.js +170 -0
  214. package/dist/market_adapter/core/kibana_market_candles.js.map +1 -0
  215. package/dist/market_adapter/core/market_adapter_service.d.ts +210 -0
  216. package/dist/market_adapter/core/market_adapter_service.d.ts.map +1 -0
  217. package/dist/market_adapter/core/market_adapter_service.js +2328 -0
  218. package/dist/market_adapter/core/market_adapter_service.js.map +1 -0
  219. package/dist/market_adapter/core/strategies/ama.d.ts +2 -0
  220. package/dist/market_adapter/core/strategies/ama.d.ts.map +1 -0
  221. package/dist/market_adapter/core/strategies/ama.js +138 -0
  222. package/dist/market_adapter/core/strategies/ama.js.map +1 -0
  223. package/dist/market_adapter/core/strategies/ama_slope_model.d.ts +50 -0
  224. package/dist/market_adapter/core/strategies/ama_slope_model.d.ts.map +1 -0
  225. package/dist/market_adapter/core/strategies/ama_slope_model.js +143 -0
  226. package/dist/market_adapter/core/strategies/ama_slope_model.js.map +1 -0
  227. package/dist/market_adapter/core/strategies/atr/calculator.d.ts +12 -0
  228. package/dist/market_adapter/core/strategies/atr/calculator.d.ts.map +1 -0
  229. package/dist/market_adapter/core/strategies/atr/calculator.js +47 -0
  230. package/dist/market_adapter/core/strategies/atr/calculator.js.map +1 -0
  231. package/dist/market_adapter/core/strategies/collateral_manager.d.ts +32 -0
  232. package/dist/market_adapter/core/strategies/collateral_manager.d.ts.map +1 -0
  233. package/dist/market_adapter/core/strategies/collateral_manager.js +68 -0
  234. package/dist/market_adapter/core/strategies/collateral_manager.js.map +1 -0
  235. package/dist/market_adapter/core/strategies/regime_gate.d.ts +66 -0
  236. package/dist/market_adapter/core/strategies/regime_gate.d.ts.map +1 -0
  237. package/dist/market_adapter/core/strategies/regime_gate.js +189 -0
  238. package/dist/market_adapter/core/strategies/regime_gate.js.map +1 -0
  239. package/dist/market_adapter/index.d.ts +10 -0
  240. package/dist/market_adapter/index.d.ts.map +1 -0
  241. package/dist/market_adapter/index.js +71 -0
  242. package/dist/market_adapter/index.js.map +1 -0
  243. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.d.ts +3 -0
  244. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.d.ts.map +1 -0
  245. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +1165 -0
  246. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -0
  247. package/dist/market_adapter/inputs/fetch_lp_data.d.ts +54 -0
  248. package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -0
  249. package/dist/market_adapter/inputs/fetch_lp_data.js +743 -0
  250. package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -0
  251. package/dist/market_adapter/inputs/kibana_source.d.ts +68 -0
  252. package/dist/market_adapter/inputs/kibana_source.d.ts.map +1 -0
  253. package/dist/market_adapter/inputs/kibana_source.js +143 -0
  254. package/dist/market_adapter/inputs/kibana_source.js.map +1 -0
  255. package/dist/market_adapter/interval_utils.d.ts +6 -0
  256. package/dist/market_adapter/interval_utils.d.ts.map +1 -0
  257. package/dist/market_adapter/interval_utils.js +14 -0
  258. package/dist/market_adapter/interval_utils.js.map +1 -0
  259. package/dist/market_adapter/log_format.d.ts +16 -0
  260. package/dist/market_adapter/log_format.d.ts.map +1 -0
  261. package/dist/market_adapter/log_format.js +72 -0
  262. package/dist/market_adapter/log_format.js.map +1 -0
  263. package/dist/market_adapter/lp_chart_core.d.ts +6 -0
  264. package/dist/market_adapter/lp_chart_core.d.ts.map +1 -0
  265. package/dist/market_adapter/lp_chart_core.js +874 -0
  266. package/dist/market_adapter/lp_chart_core.js.map +1 -0
  267. package/dist/market_adapter/lp_chart_runner.d.ts +135 -0
  268. package/dist/market_adapter/lp_chart_runner.d.ts.map +1 -0
  269. package/dist/market_adapter/lp_chart_runner.js +337 -0
  270. package/dist/market_adapter/lp_chart_runner.js.map +1 -0
  271. package/dist/market_adapter/lp_chart_strategy_loader.d.ts +23 -0
  272. package/dist/market_adapter/lp_chart_strategy_loader.d.ts.map +1 -0
  273. package/dist/market_adapter/lp_chart_strategy_loader.js +169 -0
  274. package/dist/market_adapter/lp_chart_strategy_loader.js.map +1 -0
  275. package/dist/market_adapter/market_adapter.d.ts +133 -0
  276. package/dist/market_adapter/market_adapter.d.ts.map +1 -0
  277. package/dist/market_adapter/market_adapter.js +1331 -0
  278. package/dist/market_adapter/market_adapter.js.map +1 -0
  279. package/dist/market_adapter/merge_lp_data.d.ts +3 -0
  280. package/dist/market_adapter/merge_lp_data.d.ts.map +1 -0
  281. package/dist/market_adapter/merge_lp_data.js +125 -0
  282. package/dist/market_adapter/merge_lp_data.js.map +1 -0
  283. package/dist/market_adapter/test_helpers.d.ts +14 -0
  284. package/dist/market_adapter/test_helpers.d.ts.map +1 -0
  285. package/dist/market_adapter/test_helpers.js +22 -0
  286. package/dist/market_adapter/test_helpers.js.map +1 -0
  287. package/dist/market_adapter/utils/adapter_client.d.ts +20 -0
  288. package/dist/market_adapter/utils/adapter_client.d.ts.map +1 -0
  289. package/dist/market_adapter/utils/adapter_client.js +88 -0
  290. package/dist/market_adapter/utils/adapter_client.js.map +1 -0
  291. package/dist/market_adapter/utils/atomic_write.d.ts +9 -0
  292. package/dist/market_adapter/utils/atomic_write.d.ts.map +1 -0
  293. package/dist/market_adapter/utils/atomic_write.js +13 -0
  294. package/dist/market_adapter/utils/atomic_write.js.map +1 -0
  295. package/dist/market_adapter/utils/chain.d.ts +50 -0
  296. package/dist/market_adapter/utils/chain.d.ts.map +1 -0
  297. package/dist/market_adapter/utils/chain.js +196 -0
  298. package/dist/market_adapter/utils/chain.js.map +1 -0
  299. package/dist/market_adapter/utils/data_discovery.d.ts +6 -0
  300. package/dist/market_adapter/utils/data_discovery.d.ts.map +1 -0
  301. package/dist/market_adapter/utils/data_discovery.js +35 -0
  302. package/dist/market_adapter/utils/data_discovery.js.map +1 -0
  303. package/dist/market_adapter/utils/dynamic_grid_snapshot.d.ts +15 -0
  304. package/dist/market_adapter/utils/dynamic_grid_snapshot.d.ts.map +1 -0
  305. package/dist/market_adapter/utils/dynamic_grid_snapshot.js +60 -0
  306. package/dist/market_adapter/utils/dynamic_grid_snapshot.js.map +1 -0
  307. package/dist/market_adapter/utils/file_lock.d.ts +19 -0
  308. package/dist/market_adapter/utils/file_lock.d.ts.map +1 -0
  309. package/dist/market_adapter/utils/file_lock.js +191 -0
  310. package/dist/market_adapter/utils/file_lock.js.map +1 -0
  311. package/dist/market_adapter/utils/native_history.d.ts +19 -0
  312. package/dist/market_adapter/utils/native_history.d.ts.map +1 -0
  313. package/dist/market_adapter/utils/native_history.js +172 -0
  314. package/dist/market_adapter/utils/native_history.js.map +1 -0
  315. package/dist/market_adapter/utils/paths.d.ts +5 -0
  316. package/dist/market_adapter/utils/paths.d.ts.map +1 -0
  317. package/dist/market_adapter/utils/paths.js +7 -0
  318. package/dist/market_adapter/utils/paths.js.map +1 -0
  319. package/dist/modules/account_bots.d.ts +112 -0
  320. package/dist/modules/account_bots.d.ts.map +1 -0
  321. package/dist/modules/account_bots.js +1259 -0
  322. package/dist/modules/account_bots.js.map +1 -0
  323. package/dist/modules/account_orders.d.ts +273 -0
  324. package/dist/modules/account_orders.d.ts.map +1 -0
  325. package/dist/modules/account_orders.js +662 -0
  326. package/dist/modules/account_orders.js.map +1 -0
  327. package/dist/modules/authority_resolver.d.ts +2 -0
  328. package/dist/modules/authority_resolver.d.ts.map +1 -0
  329. package/dist/modules/authority_resolver.js +231 -0
  330. package/dist/modules/authority_resolver.js.map +1 -0
  331. package/dist/modules/bitshares-native/chain_client.d.ts +38 -0
  332. package/dist/modules/bitshares-native/chain_client.d.ts.map +1 -0
  333. package/dist/modules/bitshares-native/chain_client.js +322 -0
  334. package/dist/modules/bitshares-native/chain_client.js.map +1 -0
  335. package/dist/modules/bitshares-native/crypto/ecc.browser.d.ts +59 -0
  336. package/dist/modules/bitshares-native/crypto/ecc.browser.d.ts.map +1 -0
  337. package/dist/modules/bitshares-native/crypto/ecc.browser.js +456 -0
  338. package/dist/modules/bitshares-native/crypto/ecc.browser.js.map +1 -0
  339. package/dist/modules/bitshares-native/crypto/ecc.d.ts +66 -0
  340. package/dist/modules/bitshares-native/crypto/ecc.d.ts.map +1 -0
  341. package/dist/modules/bitshares-native/crypto/ecc.js +557 -0
  342. package/dist/modules/bitshares-native/crypto/ecc.js.map +1 -0
  343. package/dist/modules/bitshares-native/crypto/ecc_selector.d.ts +2 -0
  344. package/dist/modules/bitshares-native/crypto/ecc_selector.d.ts.map +1 -0
  345. package/dist/modules/bitshares-native/crypto/ecc_selector.js +20 -0
  346. package/dist/modules/bitshares-native/crypto/ecc_selector.js.map +1 -0
  347. package/dist/modules/bitshares-native/index.d.ts +3 -0
  348. package/dist/modules/bitshares-native/index.d.ts.map +1 -0
  349. package/dist/modules/bitshares-native/index.js +32 -0
  350. package/dist/modules/bitshares-native/index.js.map +1 -0
  351. package/dist/modules/bitshares-native/resolvers.d.ts +40 -0
  352. package/dist/modules/bitshares-native/resolvers.d.ts.map +1 -0
  353. package/dist/modules/bitshares-native/resolvers.js +161 -0
  354. package/dist/modules/bitshares-native/resolvers.js.map +1 -0
  355. package/dist/modules/bitshares-native/serial/chain_constants.d.ts +56 -0
  356. package/dist/modules/bitshares-native/serial/chain_constants.d.ts.map +1 -0
  357. package/dist/modules/bitshares-native/serial/chain_constants.js +92 -0
  358. package/dist/modules/bitshares-native/serial/chain_constants.js.map +1 -0
  359. package/dist/modules/bitshares-native/serial/index.d.ts +10 -0
  360. package/dist/modules/bitshares-native/serial/index.d.ts.map +1 -0
  361. package/dist/modules/bitshares-native/serial/index.js +14 -0
  362. package/dist/modules/bitshares-native/serial/index.js.map +1 -0
  363. package/dist/modules/bitshares-native/serial/operations.d.ts +102 -0
  364. package/dist/modules/bitshares-native/serial/operations.d.ts.map +1 -0
  365. package/dist/modules/bitshares-native/serial/operations.js +615 -0
  366. package/dist/modules/bitshares-native/serial/operations.js.map +1 -0
  367. package/dist/modules/bitshares-native/serial/serializer.d.ts +64 -0
  368. package/dist/modules/bitshares-native/serial/serializer.d.ts.map +1 -0
  369. package/dist/modules/bitshares-native/serial/serializer.js +252 -0
  370. package/dist/modules/bitshares-native/serial/serializer.js.map +1 -0
  371. package/dist/modules/bitshares-native/serial/types.d.ts +120 -0
  372. package/dist/modules/bitshares-native/serial/types.d.ts.map +1 -0
  373. package/dist/modules/bitshares-native/serial/types.js +837 -0
  374. package/dist/modules/bitshares-native/serial/types.js.map +1 -0
  375. package/dist/modules/bitshares-native/signing_client.d.ts +6 -0
  376. package/dist/modules/bitshares-native/signing_client.d.ts.map +1 -0
  377. package/dist/modules/bitshares-native/signing_client.js +141 -0
  378. package/dist/modules/bitshares-native/signing_client.js.map +1 -0
  379. package/dist/modules/bitshares-native/subscriptions.d.ts +6 -0
  380. package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -0
  381. package/dist/modules/bitshares-native/subscriptions.js +865 -0
  382. package/dist/modules/bitshares-native/subscriptions.js.map +1 -0
  383. package/dist/modules/bitshares-native/transport.d.ts +51 -0
  384. package/dist/modules/bitshares-native/transport.d.ts.map +1 -0
  385. package/dist/modules/bitshares-native/transport.js +447 -0
  386. package/dist/modules/bitshares-native/transport.js.map +1 -0
  387. package/dist/modules/bitshares-native/tx/builder.d.ts +30 -0
  388. package/dist/modules/bitshares-native/tx/builder.d.ts.map +1 -0
  389. package/dist/modules/bitshares-native/tx/builder.js +262 -0
  390. package/dist/modules/bitshares-native/tx/builder.js.map +1 -0
  391. package/dist/modules/bitshares_client.d.ts +129 -0
  392. package/dist/modules/bitshares_client.d.ts.map +1 -0
  393. package/dist/modules/bitshares_client.js +627 -0
  394. package/dist/modules/bitshares_client.js.map +1 -0
  395. package/dist/modules/bot_settings.d.ts +33 -0
  396. package/dist/modules/bot_settings.d.ts.map +1 -0
  397. package/dist/modules/bot_settings.js +328 -0
  398. package/dist/modules/bot_settings.js.map +1 -0
  399. package/dist/modules/bots_file_lock.d.ts +88 -0
  400. package/dist/modules/bots_file_lock.d.ts.map +1 -0
  401. package/dist/modules/bots_file_lock.js +161 -0
  402. package/dist/modules/bots_file_lock.js.map +1 -0
  403. package/dist/modules/chain_keys.d.ts +302 -0
  404. package/dist/modules/chain_keys.d.ts.map +1 -0
  405. package/dist/modules/chain_keys.js +1095 -0
  406. package/dist/modules/chain_keys.js.map +1 -0
  407. package/dist/modules/chain_orders.d.ts +423 -0
  408. package/dist/modules/chain_orders.d.ts.map +1 -0
  409. package/dist/modules/chain_orders.js +1201 -0
  410. package/dist/modules/chain_orders.js.map +1 -0
  411. package/dist/modules/cli_whitelist_args.d.ts +6 -0
  412. package/dist/modules/cli_whitelist_args.d.ts.map +1 -0
  413. package/dist/modules/cli_whitelist_args.js +13 -0
  414. package/dist/modules/cli_whitelist_args.js.map +1 -0
  415. package/dist/modules/config.d.ts +57 -0
  416. package/dist/modules/config.d.ts.map +1 -0
  417. package/dist/modules/config.js +107 -0
  418. package/dist/modules/config.js.map +1 -0
  419. package/dist/modules/constants.d.ts +635 -0
  420. package/dist/modules/constants.d.ts.map +1 -0
  421. package/dist/modules/constants.js +1464 -0
  422. package/dist/modules/constants.js.map +1 -0
  423. package/dist/modules/cr_planner.d.ts +51 -0
  424. package/dist/modules/cr_planner.d.ts.map +1 -0
  425. package/dist/modules/cr_planner.js +299 -0
  426. package/dist/modules/cr_planner.js.map +1 -0
  427. package/dist/modules/credential_policy.d.ts +188 -0
  428. package/dist/modules/credential_policy.d.ts.map +1 -0
  429. package/dist/modules/credential_policy.js +1452 -0
  430. package/dist/modules/credential_policy.js.map +1 -0
  431. package/dist/modules/credential_runtime.d.ts +42 -0
  432. package/dist/modules/credential_runtime.d.ts.map +1 -0
  433. package/dist/modules/credential_runtime.js +148 -0
  434. package/dist/modules/credential_runtime.js.map +1 -0
  435. package/dist/modules/credential_session_cache.d.ts +36 -0
  436. package/dist/modules/credential_session_cache.d.ts.map +1 -0
  437. package/dist/modules/credential_session_cache.js +106 -0
  438. package/dist/modules/credential_session_cache.js.map +1 -0
  439. package/dist/modules/credit_runtime.d.ts +412 -0
  440. package/dist/modules/credit_runtime.d.ts.map +1 -0
  441. package/dist/modules/credit_runtime.js +2866 -0
  442. package/dist/modules/credit_runtime.js.map +1 -0
  443. package/dist/modules/crypto/browser_provider.d.ts +15 -0
  444. package/dist/modules/crypto/browser_provider.d.ts.map +1 -0
  445. package/dist/modules/crypto/browser_provider.js +113 -0
  446. package/dist/modules/crypto/browser_provider.js.map +1 -0
  447. package/dist/modules/crypto/index.d.ts +9 -0
  448. package/dist/modules/crypto/index.d.ts.map +1 -0
  449. package/dist/modules/crypto/index.js +44 -0
  450. package/dist/modules/crypto/index.js.map +1 -0
  451. package/dist/modules/crypto/node_provider.d.ts +15 -0
  452. package/dist/modules/crypto/node_provider.d.ts.map +1 -0
  453. package/dist/modules/crypto/node_provider.js +74 -0
  454. package/dist/modules/crypto/node_provider.js.map +1 -0
  455. package/dist/modules/crypto/provider.d.ts +34 -0
  456. package/dist/modules/crypto/provider.d.ts.map +1 -0
  457. package/dist/modules/crypto/provider.js +3 -0
  458. package/dist/modules/crypto/provider.js.map +1 -0
  459. package/dist/modules/crypto/pure_ripemd160.d.ts +8 -0
  460. package/dist/modules/crypto/pure_ripemd160.d.ts.map +1 -0
  461. package/dist/modules/crypto/pure_ripemd160.js +142 -0
  462. package/dist/modules/crypto/pure_ripemd160.js.map +1 -0
  463. package/dist/modules/crypto/pure_scrypt.d.ts +23 -0
  464. package/dist/modules/crypto/pure_scrypt.d.ts.map +1 -0
  465. package/dist/modules/crypto/pure_scrypt.js +161 -0
  466. package/dist/modules/crypto/pure_scrypt.js.map +1 -0
  467. package/dist/modules/crypto/pure_secp256k1.d.ts +29 -0
  468. package/dist/modules/crypto/pure_secp256k1.d.ts.map +1 -0
  469. package/dist/modules/crypto/pure_secp256k1.js +195 -0
  470. package/dist/modules/crypto/pure_secp256k1.js.map +1 -0
  471. package/dist/modules/crypto/sync.d.ts +13 -0
  472. package/dist/modules/crypto/sync.d.ts.map +1 -0
  473. package/dist/modules/crypto/sync.js +37 -0
  474. package/dist/modules/crypto/sync.js.map +1 -0
  475. package/dist/modules/dexbot_class.d.ts +1074 -0
  476. package/dist/modules/dexbot_class.d.ts.map +1 -0
  477. package/dist/modules/dexbot_class.js +4613 -0
  478. package/dist/modules/dexbot_class.js.map +1 -0
  479. package/dist/modules/dexbot_credential_client.d.ts +71 -0
  480. package/dist/modules/dexbot_credential_client.d.ts.map +1 -0
  481. package/dist/modules/dexbot_credential_client.js +224 -0
  482. package/dist/modules/dexbot_credential_client.js.map +1 -0
  483. package/dist/modules/dexbot_fill_runtime.d.ts +173 -0
  484. package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -0
  485. package/dist/modules/dexbot_fill_runtime.js +217 -0
  486. package/dist/modules/dexbot_fill_runtime.js.map +1 -0
  487. package/dist/modules/dexbot_maintenance_runtime.d.ts +279 -0
  488. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -0
  489. package/dist/modules/dexbot_maintenance_runtime.js +1613 -0
  490. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -0
  491. package/dist/modules/env.d.ts +12 -0
  492. package/dist/modules/env.d.ts.map +1 -0
  493. package/dist/modules/env.js +57 -0
  494. package/dist/modules/env.js.map +1 -0
  495. package/dist/modules/fund_registry.d.ts +96 -0
  496. package/dist/modules/fund_registry.d.ts.map +1 -0
  497. package/dist/modules/fund_registry.js +261 -0
  498. package/dist/modules/fund_registry.js.map +1 -0
  499. package/dist/modules/general_settings.d.ts +36 -0
  500. package/dist/modules/general_settings.d.ts.map +1 -0
  501. package/dist/modules/general_settings.js +63 -0
  502. package/dist/modules/general_settings.js.map +1 -0
  503. package/dist/modules/graceful_shutdown.d.ts +86 -0
  504. package/dist/modules/graceful_shutdown.d.ts.map +1 -0
  505. package/dist/modules/graceful_shutdown.js +164 -0
  506. package/dist/modules/graceful_shutdown.js.map +1 -0
  507. package/dist/modules/key_store.d.ts +60 -0
  508. package/dist/modules/key_store.d.ts.map +1 -0
  509. package/dist/modules/key_store.js +215 -0
  510. package/dist/modules/key_store.js.map +1 -0
  511. package/dist/modules/launcher/bot_supervisor.d.ts +84 -0
  512. package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -0
  513. package/dist/modules/launcher/bot_supervisor.js +1067 -0
  514. package/dist/modules/launcher/bot_supervisor.js.map +1 -0
  515. package/dist/modules/launcher/child_env.d.ts +9 -0
  516. package/dist/modules/launcher/child_env.d.ts.map +1 -0
  517. package/dist/modules/launcher/child_env.js +78 -0
  518. package/dist/modules/launcher/child_env.js.map +1 -0
  519. package/dist/modules/launcher/credential_bootstrap.d.ts +22 -0
  520. package/dist/modules/launcher/credential_bootstrap.d.ts.map +1 -0
  521. package/dist/modules/launcher/credential_bootstrap.js +303 -0
  522. package/dist/modules/launcher/credential_bootstrap.js.map +1 -0
  523. package/dist/modules/launcher/credential_daemon.d.ts +32 -0
  524. package/dist/modules/launcher/credential_daemon.d.ts.map +1 -0
  525. package/dist/modules/launcher/credential_daemon.js +173 -0
  526. package/dist/modules/launcher/credential_daemon.js.map +1 -0
  527. package/dist/modules/launcher/credential_secret.d.ts +6 -0
  528. package/dist/modules/launcher/credential_secret.d.ts.map +1 -0
  529. package/dist/modules/launcher/credential_secret.js +15 -0
  530. package/dist/modules/launcher/credential_secret.js.map +1 -0
  531. package/dist/modules/launcher/foreign_cred_daemon.d.ts +2 -0
  532. package/dist/modules/launcher/foreign_cred_daemon.d.ts.map +1 -0
  533. package/dist/modules/launcher/foreign_cred_daemon.js +166 -0
  534. package/dist/modules/launcher/foreign_cred_daemon.js.map +1 -0
  535. package/dist/modules/launcher/headless_password.d.ts +8 -0
  536. package/dist/modules/launcher/headless_password.d.ts.map +1 -0
  537. package/dist/modules/launcher/headless_password.js +33 -0
  538. package/dist/modules/launcher/headless_password.js.map +1 -0
  539. package/dist/modules/launcher/launch_modes.d.ts +36 -0
  540. package/dist/modules/launcher/launch_modes.d.ts.map +1 -0
  541. package/dist/modules/launcher/launch_modes.js +129 -0
  542. package/dist/modules/launcher/launch_modes.js.map +1 -0
  543. package/dist/modules/launcher/market_adapter_runtime.d.ts +43 -0
  544. package/dist/modules/launcher/market_adapter_runtime.d.ts.map +1 -0
  545. package/dist/modules/launcher/market_adapter_runtime.js +217 -0
  546. package/dist/modules/launcher/market_adapter_runtime.js.map +1 -0
  547. package/dist/modules/launcher/market_adapter_watchdog.d.ts +26 -0
  548. package/dist/modules/launcher/market_adapter_watchdog.d.ts.map +1 -0
  549. package/dist/modules/launcher/market_adapter_watchdog.js +214 -0
  550. package/dist/modules/launcher/market_adapter_watchdog.js.map +1 -0
  551. package/dist/modules/launcher/monolithic_runtime.d.ts +89 -0
  552. package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -0
  553. package/dist/modules/launcher/monolithic_runtime.js +425 -0
  554. package/dist/modules/launcher/monolithic_runtime.js.map +1 -0
  555. package/dist/modules/launcher/runtime_entry.d.ts +26 -0
  556. package/dist/modules/launcher/runtime_entry.d.ts.map +1 -0
  557. package/dist/modules/launcher/runtime_entry.js +49 -0
  558. package/dist/modules/launcher/runtime_entry.js.map +1 -0
  559. package/dist/modules/launcher/status_reporting.d.ts +48 -0
  560. package/dist/modules/launcher/status_reporting.d.ts.map +1 -0
  561. package/dist/modules/launcher/status_reporting.js +113 -0
  562. package/dist/modules/launcher/status_reporting.js.map +1 -0
  563. package/dist/modules/launcher/supervisor_control.d.ts +6 -0
  564. package/dist/modules/launcher/supervisor_control.d.ts.map +1 -0
  565. package/dist/modules/launcher/supervisor_control.js +59 -0
  566. package/dist/modules/launcher/supervisor_control.js.map +1 -0
  567. package/dist/modules/logger.d.ts +2 -0
  568. package/dist/modules/logger.d.ts.map +1 -0
  569. package/dist/modules/logger.js +71 -0
  570. package/dist/modules/logger.js.map +1 -0
  571. package/dist/modules/market_adapter_whitelist.d.ts +22 -0
  572. package/dist/modules/market_adapter_whitelist.d.ts.map +1 -0
  573. package/dist/modules/market_adapter_whitelist.js +82 -0
  574. package/dist/modules/market_adapter_whitelist.js.map +1 -0
  575. package/dist/modules/node_health_cache.d.ts +55 -0
  576. package/dist/modules/node_health_cache.d.ts.map +1 -0
  577. package/dist/modules/node_health_cache.js +134 -0
  578. package/dist/modules/node_health_cache.js.map +1 -0
  579. package/dist/modules/node_manager.d.ts +215 -0
  580. package/dist/modules/node_manager.d.ts.map +1 -0
  581. package/dist/modules/node_manager.js +628 -0
  582. package/dist/modules/node_manager.js.map +1 -0
  583. package/dist/modules/order/accounting.d.ts +349 -0
  584. package/dist/modules/order/accounting.d.ts.map +1 -0
  585. package/dist/modules/order/accounting.js +1191 -0
  586. package/dist/modules/order/accounting.js.map +1 -0
  587. package/dist/modules/order/async_lock.d.ts +126 -0
  588. package/dist/modules/order/async_lock.d.ts.map +1 -0
  589. package/dist/modules/order/async_lock.js +195 -0
  590. package/dist/modules/order/async_lock.js.map +1 -0
  591. package/dist/modules/order/export.d.ts +129 -0
  592. package/dist/modules/order/export.d.ts.map +1 -0
  593. package/dist/modules/order/export.js +316 -0
  594. package/dist/modules/order/export.js.map +1 -0
  595. package/dist/modules/order/format.d.ts +158 -0
  596. package/dist/modules/order/format.d.ts.map +1 -0
  597. package/dist/modules/order/format.js +232 -0
  598. package/dist/modules/order/format.js.map +1 -0
  599. package/dist/modules/order/grid.d.ts +549 -0
  600. package/dist/modules/order/grid.d.ts.map +1 -0
  601. package/dist/modules/order/grid.js +1746 -0
  602. package/dist/modules/order/grid.js.map +1 -0
  603. package/dist/modules/order/grid_reconcile.d.ts +154 -0
  604. package/dist/modules/order/grid_reconcile.d.ts.map +1 -0
  605. package/dist/modules/order/grid_reconcile.js +1192 -0
  606. package/dist/modules/order/grid_reconcile.js.map +1 -0
  607. package/dist/modules/order/index.d.ts +55 -0
  608. package/dist/modules/order/index.d.ts.map +1 -0
  609. package/dist/modules/order/index.js +79 -0
  610. package/dist/modules/order/index.js.map +1 -0
  611. package/dist/modules/order/logger.d.ts +136 -0
  612. package/dist/modules/order/logger.d.ts.map +1 -0
  613. package/dist/modules/order/logger.js +614 -0
  614. package/dist/modules/order/logger.js.map +1 -0
  615. package/dist/modules/order/logger_state.d.ts +133 -0
  616. package/dist/modules/order/logger_state.d.ts.map +1 -0
  617. package/dist/modules/order/logger_state.js +170 -0
  618. package/dist/modules/order/logger_state.js.map +1 -0
  619. package/dist/modules/order/manager.d.ts +413 -0
  620. package/dist/modules/order/manager.d.ts.map +1 -0
  621. package/dist/modules/order/manager.js +1605 -0
  622. package/dist/modules/order/manager.js.map +1 -0
  623. package/dist/modules/order/processed_fill_store.d.ts +135 -0
  624. package/dist/modules/order/processed_fill_store.d.ts.map +1 -0
  625. package/dist/modules/order/processed_fill_store.js +288 -0
  626. package/dist/modules/order/processed_fill_store.js.map +1 -0
  627. package/dist/modules/order/runner.d.ts +70 -0
  628. package/dist/modules/order/runner.d.ts.map +1 -0
  629. package/dist/modules/order/runner.js +140 -0
  630. package/dist/modules/order/runner.js.map +1 -0
  631. package/dist/modules/order/strategy.d.ts +196 -0
  632. package/dist/modules/order/strategy.d.ts.map +1 -0
  633. package/dist/modules/order/strategy.js +398 -0
  634. package/dist/modules/order/strategy.js.map +1 -0
  635. package/dist/modules/order/sync_engine.d.ts +386 -0
  636. package/dist/modules/order/sync_engine.d.ts.map +1 -0
  637. package/dist/modules/order/sync_engine.js +1459 -0
  638. package/dist/modules/order/sync_engine.js.map +1 -0
  639. package/dist/modules/order/utils/math.d.ts +677 -0
  640. package/dist/modules/order/utils/math.d.ts.map +1 -0
  641. package/dist/modules/order/utils/math.js +1172 -0
  642. package/dist/modules/order/utils/math.js.map +1 -0
  643. package/dist/modules/order/utils/order.d.ts +572 -0
  644. package/dist/modules/order/utils/order.d.ts.map +1 -0
  645. package/dist/modules/order/utils/order.js +1145 -0
  646. package/dist/modules/order/utils/order.js.map +1 -0
  647. package/dist/modules/order/utils/system.d.ts +268 -0
  648. package/dist/modules/order/utils/system.d.ts.map +1 -0
  649. package/dist/modules/order/utils/system.js +1261 -0
  650. package/dist/modules/order/utils/system.js.map +1 -0
  651. package/dist/modules/order/utils/validate.d.ts +280 -0
  652. package/dist/modules/order/utils/validate.d.ts.map +1 -0
  653. package/dist/modules/order/utils/validate.js +918 -0
  654. package/dist/modules/order/utils/validate.js.map +1 -0
  655. package/dist/modules/order/working_grid.d.ts +212 -0
  656. package/dist/modules/order/working_grid.d.ts.map +1 -0
  657. package/dist/modules/order/working_grid.js +279 -0
  658. package/dist/modules/order/working_grid.js.map +1 -0
  659. package/dist/modules/path_api.d.ts +32 -0
  660. package/dist/modules/path_api.d.ts.map +1 -0
  661. package/dist/modules/path_api.js +148 -0
  662. package/dist/modules/path_api.js.map +1 -0
  663. package/dist/modules/paths.d.ts +58 -0
  664. package/dist/modules/paths.d.ts.map +1 -0
  665. package/dist/modules/paths.js +82 -0
  666. package/dist/modules/paths.js.map +1 -0
  667. package/dist/modules/process_discovery.d.ts +58 -0
  668. package/dist/modules/process_discovery.d.ts.map +1 -0
  669. package/dist/modules/process_discovery.js +265 -0
  670. package/dist/modules/process_discovery.js.map +1 -0
  671. package/dist/modules/runtime.d.ts +34 -0
  672. package/dist/modules/runtime.d.ts.map +1 -0
  673. package/dist/modules/runtime.js +98 -0
  674. package/dist/modules/runtime.js.map +1 -0
  675. package/dist/modules/runtime_settings.d.ts +12 -0
  676. package/dist/modules/runtime_settings.d.ts.map +1 -0
  677. package/dist/modules/runtime_settings.js +122 -0
  678. package/dist/modules/runtime_settings.js.map +1 -0
  679. package/dist/modules/settings_merge.d.ts +38 -0
  680. package/dist/modules/settings_merge.d.ts.map +1 -0
  681. package/dist/modules/settings_merge.js +246 -0
  682. package/dist/modules/settings_merge.js.map +1 -0
  683. package/dist/modules/storage/browser_adapter.d.ts +56 -0
  684. package/dist/modules/storage/browser_adapter.d.ts.map +1 -0
  685. package/dist/modules/storage/browser_adapter.js +231 -0
  686. package/dist/modules/storage/browser_adapter.js.map +1 -0
  687. package/dist/modules/storage/index.d.ts +28 -0
  688. package/dist/modules/storage/index.d.ts.map +1 -0
  689. package/dist/modules/storage/index.js +52 -0
  690. package/dist/modules/storage/index.js.map +1 -0
  691. package/dist/modules/storage/node_adapter.d.ts +49 -0
  692. package/dist/modules/storage/node_adapter.d.ts.map +1 -0
  693. package/dist/modules/storage/node_adapter.js +167 -0
  694. package/dist/modules/storage/node_adapter.js.map +1 -0
  695. package/dist/modules/storage/types.d.ts +92 -0
  696. package/dist/modules/storage/types.d.ts.map +1 -0
  697. package/dist/modules/storage/types.js +3 -0
  698. package/dist/modules/storage/types.js.map +1 -0
  699. package/dist/modules/types.d.ts +1382 -0
  700. package/dist/modules/types.d.ts.map +1 -0
  701. package/dist/modules/types.js +11 -0
  702. package/dist/modules/types.js.map +1 -0
  703. package/dist/modules/utils/base58check.d.ts +12 -0
  704. package/dist/modules/utils/base58check.d.ts.map +1 -0
  705. package/dist/modules/utils/base58check.js +112 -0
  706. package/dist/modules/utils/base58check.js.map +1 -0
  707. package/dist/modules/utils/build_dir.d.ts +7 -0
  708. package/dist/modules/utils/build_dir.d.ts.map +1 -0
  709. package/dist/modules/utils/build_dir.js +8 -0
  710. package/dist/modules/utils/build_dir.js.map +1 -0
  711. package/dist/modules/utils/fs_utils.d.ts +25 -0
  712. package/dist/modules/utils/fs_utils.d.ts.map +1 -0
  713. package/dist/modules/utils/fs_utils.js +27 -0
  714. package/dist/modules/utils/fs_utils.js.map +1 -0
  715. package/dist/modules/utils/math_utils.d.ts +10 -0
  716. package/dist/modules/utils/math_utils.d.ts.map +1 -0
  717. package/dist/modules/utils/math_utils.js +17 -0
  718. package/dist/modules/utils/math_utils.js.map +1 -0
  719. package/dist/modules/validate_profiles.d.ts +37 -0
  720. package/dist/modules/validate_profiles.d.ts.map +1 -0
  721. package/dist/modules/validate_profiles.js +369 -0
  722. package/dist/modules/validate_profiles.js.map +1 -0
  723. package/dist/pm2.d.ts +167 -0
  724. package/dist/pm2.d.ts.map +1 -0
  725. package/dist/pm2.js +998 -0
  726. package/dist/pm2.js.map +1 -0
  727. package/dist/scripts/analyze-git.d.ts +11 -0
  728. package/dist/scripts/analyze-git.d.ts.map +1 -0
  729. package/dist/scripts/analyze-git.js +851 -0
  730. package/dist/scripts/analyze-git.js.map +1 -0
  731. package/dist/scripts/analyze-orders.d.ts +17 -0
  732. package/dist/scripts/analyze-orders.d.ts.map +1 -0
  733. package/dist/scripts/analyze-orders.js +1568 -0
  734. package/dist/scripts/analyze-orders.js.map +1 -0
  735. package/dist/scripts/diagnose-kibana-candles.d.ts +3 -0
  736. package/dist/scripts/diagnose-kibana-candles.d.ts.map +1 -0
  737. package/dist/scripts/diagnose-kibana-candles.js +82 -0
  738. package/dist/scripts/diagnose-kibana-candles.js.map +1 -0
  739. package/dist/scripts/diagnose-pool-history.d.ts +3 -0
  740. package/dist/scripts/diagnose-pool-history.d.ts.map +1 -0
  741. package/dist/scripts/diagnose-pool-history.js +296 -0
  742. package/dist/scripts/diagnose-pool-history.js.map +1 -0
  743. package/dist/scripts/divergence-calc.d.ts +26 -0
  744. package/dist/scripts/divergence-calc.d.ts.map +1 -0
  745. package/dist/scripts/divergence-calc.js +119 -0
  746. package/dist/scripts/divergence-calc.js.map +1 -0
  747. package/dist/scripts/generate_lp_chart.d.ts +31 -0
  748. package/dist/scripts/generate_lp_chart.d.ts.map +1 -0
  749. package/dist/scripts/generate_lp_chart.js +92 -0
  750. package/dist/scripts/generate_lp_chart.js.map +1 -0
  751. package/dist/scripts/generate_mainnet_corpus_report.d.ts +24 -0
  752. package/dist/scripts/generate_mainnet_corpus_report.d.ts.map +1 -0
  753. package/dist/scripts/generate_mainnet_corpus_report.js +237 -0
  754. package/dist/scripts/generate_mainnet_corpus_report.js.map +1 -0
  755. package/dist/scripts/generate_market_adapter_whitelist.d.ts +2 -0
  756. package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +1 -0
  757. package/dist/scripts/generate_market_adapter_whitelist.js +111 -0
  758. package/dist/scripts/generate_market_adapter_whitelist.js.map +1 -0
  759. package/dist/scripts/migrate_bot_keys.d.ts +6 -0
  760. package/dist/scripts/migrate_bot_keys.d.ts.map +1 -0
  761. package/dist/scripts/migrate_bot_keys.js +301 -0
  762. package/dist/scripts/migrate_bot_keys.js.map +1 -0
  763. package/dist/scripts/native_release_gates.d.ts +3 -0
  764. package/dist/scripts/native_release_gates.d.ts.map +1 -0
  765. package/dist/scripts/native_release_gates.js +52 -0
  766. package/dist/scripts/native_release_gates.js.map +1 -0
  767. package/dist/scripts/print_grid.d.ts +16 -0
  768. package/dist/scripts/print_grid.d.ts.map +1 -0
  769. package/dist/scripts/print_grid.js +80 -0
  770. package/dist/scripts/print_grid.js.map +1 -0
  771. package/dist/scripts/run-tests.d.ts +2 -0
  772. package/dist/scripts/run-tests.d.ts.map +1 -0
  773. package/dist/scripts/run-tests.js +49 -0
  774. package/dist/scripts/run-tests.js.map +1 -0
  775. package/dist/scripts/sync-version.d.ts +14 -0
  776. package/dist/scripts/sync-version.d.ts.map +1 -0
  777. package/dist/scripts/sync-version.js +158 -0
  778. package/dist/scripts/sync-version.js.map +1 -0
  779. package/dist/scripts/test-credit-renewal.d.ts +2 -0
  780. package/dist/scripts/test-credit-renewal.d.ts.map +1 -0
  781. package/dist/scripts/test-credit-renewal.js +329 -0
  782. package/dist/scripts/test-credit-renewal.js.map +1 -0
  783. package/dist/scripts/update.d.ts +24 -0
  784. package/dist/scripts/update.d.ts.map +1 -0
  785. package/dist/scripts/update.js +650 -0
  786. package/dist/scripts/update.js.map +1 -0
  787. package/dist/scripts/validate_bots.d.ts +16 -0
  788. package/dist/scripts/validate_bots.d.ts.map +1 -0
  789. package/dist/scripts/validate_bots.js +106 -0
  790. package/dist/scripts/validate_bots.js.map +1 -0
  791. package/dist/scripts/verify-browser-bundle.d.ts +22 -0
  792. package/dist/scripts/verify-browser-bundle.d.ts.map +1 -0
  793. package/dist/scripts/verify-browser-bundle.js +339 -0
  794. package/dist/scripts/verify-browser-bundle.js.map +1 -0
  795. package/dist/unlock.d.ts +48 -0
  796. package/dist/unlock.d.ts.map +1 -0
  797. package/dist/unlock.js +925 -0
  798. package/dist/unlock.js.map +1 -0
  799. package/package.json +112 -0
  800. package/scripts/README.md +356 -0
  801. package/scripts/clear-all.sh +195 -0
  802. package/scripts/clear-logs.sh +95 -0
  803. package/scripts/clear-market-adapter.sh +196 -0
  804. package/scripts/clear-orders.sh +113 -0
  805. package/scripts/create-bot-symlinks.sh +51 -0
  806. package/scripts/postinstall.js +9 -0
  807. package/scripts/reset-settings.sh +118 -0
@@ -0,0 +1,1746 @@
1
+ "use strict";
2
+ /**
3
+ * modules/order/grid.ts - Grid Engine
4
+ *
5
+ * Order grid creation, synchronization, and health management.
6
+ * Exports a single Grid class with static methods for grid operations.
7
+ *
8
+ * Manages the complete lifecycle of the order grid:
9
+ * - Creates geometric price grids with configurable spacing (increments)
10
+ * - Synchronizes grid state with blockchain and fund changes
11
+ * - Monitors grid health and handles spread corrections
12
+ * - Calculates order sizes and allocations based on funds
13
+ * - Detects and flags out-of-spread conditions
14
+ *
15
+ * ===============================================================================
16
+ * TABLE OF CONTENTS - Grid Class (25 static methods)
17
+ * ===============================================================================
18
+ *
19
+ * CONFIGURATION & CALCULATION (2 methods)
20
+ * 1. calculateGapSlots(incrementPercent, targetSpreadPercent) - Calculate spread gap size
21
+ * 2. getSizingContext(manager, side) - Get budget and sizing parameters (public wrapper)
22
+ *
23
+ * GRID SIZING & CONTEXT (1 method)
24
+ * 3. _getSizingContext(manager, side) - Get budget and sizing parameters (internal)
25
+ * Determines budget from allocated funds, deducts BTS fees if needed
26
+ *
27
+ * GRID CREATION (1 method)
28
+ * 4. createOrderGrid(config) - Create geometric price grid
29
+ * Returns price levels from minPrice to maxPrice with increment spacing
30
+ *
31
+ * ORDER CACHE MANAGEMENT (1 method - internal)
32
+ * 5. _clearOrderCachesLogic(manager) - Clear order caches (_ordersByType, _ordersByState)
33
+ *
34
+ * GRID LOADING & INITIALIZATION (2 methods - async)
35
+ * 6. loadGrid(manager, grid, boundaryIdx) - Load grid into manager orders
36
+ * 7. initializeGrid(manager) - Full grid initialization from config
37
+ *
38
+ * GRID RECALCULATION (1 method - async)
39
+ * 8. recalculateGrid(manager, opts) - Recalculate grid based on current state
40
+ *
41
+ * GRID STATE CHECKING (1 method)
42
+ * 9. checkAndUpdateGridIfNeeded(manager) - Check if grid needs update
43
+ *
44
+ * BLOCKCHAIN SYNCHRONIZATION (2 methods - async)
45
+ * 10. _recalculateGridOrderSizesFromBlockchain(manager, orderType) - Recalculate sizes from blockchain
46
+ * 11. updateGridFromBlockchainSnapshot(manager, orderType, fromBlockchainTimer) - Update grid from blockchain
47
+ *
48
+ * GRID COMPARISON (2 methods - async)
49
+ * 12. compareGrids(calculatedGrid, persistedGrid, manager) - Compare two grids
50
+ * Validates grid structure and reports divergence metrics
51
+ * 13. monitorDivergence(manager, calculatedGrid, persistedGrid) - Unified divergence check
52
+ * Runs ratio-based + RMS-based checks and returns combined result
53
+ *
54
+ * ON-CHAIN ORDER FETCHING (1 method - async)
55
+ * 14. _getOnChainOrders(manager) - Collect on-chain buy/sell orders from manager
56
+ *
57
+ * SPREAD MANAGEMENT (2 methods - async)
58
+ * 15. calculateCurrentSpread(manager) - Calculate current bid-ask spread
59
+ * 16. checkSpreadCondition(manager, BitShares, updateOrdersOnChainBatch) - Check and flag spread condition
60
+ *
61
+ * GRID HEALTH MONITORING (6 methods)
62
+ * 17. checkGridHealth(manager, updateOrdersOnChainBatch) - Monitor grid health (async)
63
+ * 18. checkWindowDust(manager) - Dust check scoped to the active buy/sell window (async)
64
+ * 19. _hasAnyDust(manager, partials, type) - Check for dust orders (internal)
65
+ * 20. hasAnyDust(manager, partials, side) - Check for dust orders (public)
66
+ * 21. getDustOrders(manager, partials, side) - Get all dust order IDs (public)
67
+ * 22. determineOrderSideByFunds(manager, currentMarketPrice) - Determine priority side
68
+ *
69
+ * SPREAD CORRECTION (2 methods)
70
+ * 23. calculateGeometricSizeForSpreadCorrection(manager, targetType) - Calculate correction size
71
+ * 24. prepareSpreadCorrectionOrders(manager, preferredSide) - Prepare correction orders
72
+ *
73
+ * DUST DETECTION (1 method - internal)
74
+ * 25. _getDustOrders(manager, partials, type) - Internal dust detection helper
75
+ *
76
+ * ===============================================================================
77
+ *
78
+ * GRID STRUCTURE:
79
+ * Grid = Array of slots with:
80
+ * - id: Order ID (null for virtual)
81
+ * - price: Price level
82
+ * - size: Grid allocation
83
+ * - grid: In-grid size (ACTIVE + PARTIAL orders)
84
+ * - blockchain: On-blockchain size
85
+ * - type: BUY, SELL, or SPREAD
86
+ * - state: VIRTUAL, ACTIVE, PARTIAL
87
+ *
88
+ * GRID LIFECYCLE:
89
+ * 1. createOrderGrid(config) - Generate price levels
90
+ * 2. assignGridRoles() - Assign BUY/SELL/SPREAD roles based on boundary
91
+ * 3. calculateOrderSizes() - Allocate funds to slots
92
+ * 4. loadGrid() - Create grid Order objects in manager
93
+ * 5. syncFromOpenOrders() - Load blockchain state
94
+ * 6. recalculateGrid() - Keep in sync as market/funds change
95
+ *
96
+ * ===============================================================================
97
+ */
98
+ const { ORDER_TYPES, ORDER_STATES, COW_ACTIONS, DEFAULT_CONFIG, GRID_LIMITS, TIMING, MARKET_ADAPTER } = require('../constants');
99
+ const { GRID_COMPARISON } = GRID_LIMITS;
100
+ const Format = require('./format');
101
+ const { resolveMaxAsymmetryFactor, applyAsymmetricBounds, } = require('../../market_adapter/core/asymmetric_bounds');
102
+ // FIX: Extract magic numbers to named constants for maintainability
103
+ const GRID_CONSTANTS = {
104
+ RMS_PERCENTAGE_SCALE: 100, // Convert RMS percentage threshold from percent to decimal
105
+ };
106
+ function _snapshotFundState(manager) {
107
+ return {
108
+ buyFree: Number(manager.accountTotals?.buyFree || 0),
109
+ sellFree: Number(manager.accountTotals?.sellFree || 0),
110
+ buyLocked: Number(manager.accountTotals?.buyLocked || 0),
111
+ sellLocked: Number(manager.accountTotals?.sellLocked || 0),
112
+ };
113
+ }
114
+ const { floatToBlockchainInt, blockchainToFloat, getPrecisionByOrderType, getPrecisionsForManager, calculateOrderCreationFees, calculateOrderSizes, calculateRotationOrderSizes, calculateAvailableFundsValue, calculateGridSideDivergenceMetric, getPrecisionSlack, getMinAbsoluteOrderSize, getSingleDustThreshold, getGridBestPrices, calculateSpreadFromOrders, allocateFundsByWeights, calculateGapSlots, calculatePriceTolerance } = require('./utils/math');
115
+ const { filterOrdersByType, checkSizesBeforeMinimum, checkSizeThreshold, resolveConfiguredPriceBound, shouldFlagOutOfSpread, isOrderHealthy, isPhantomOrder, isSlotAvailable, isOrderOnChain, hasOnChainId, calculateIdealBoundary, assignGridRoles } = require('./utils/order');
116
+ const { derivePrice, loadAmaCenterPrice, loadAmaCenterSnapshot } = require('./utils/system');
117
+ const { getWhitelistFlags } = require('../market_adapter_whitelist');
118
+ class Grid {
119
+ /**
120
+ * Calculate the spread gap size (number of empty slots between BUY and SELL rails).
121
+ * Delegates to utils/math for pure calculation logic.
122
+ *
123
+ * @param {number} incrementPercent
124
+ * @param {number} targetSpreadPercent
125
+ * @returns {number}
126
+ */
127
+ static calculateGapSlots(incrementPercent, targetSpreadPercent, gridLimitsOverride) {
128
+ return calculateGapSlots(incrementPercent, targetSpreadPercent, gridLimitsOverride ?? GRID_LIMITS);
129
+ }
130
+ /**
131
+ * Public wrapper for side sizing context.
132
+ * Keeps StrategyEngine decoupled from Grid private internals.
133
+ *
134
+ * @param {import('./types').OrderManager} manager
135
+ * @param {'buy'|'sell'} side
136
+ * @returns {Promise<import('./types').SizingContext|null>}
137
+ */
138
+ static async getSizingContext(manager, side) {
139
+ return await Grid._getSizingContext(manager, side);
140
+ }
141
+ /**
142
+ * Unifies budget calculation and fee deduction for all grid sizing scenarios.
143
+ * Ensures consistent fund context (Allocated vs Total) across the bot.
144
+ *
145
+ * @param {import('./types').OrderManager} manager - OrderManager instance
146
+ * @param {string} side - 'buy' or 'sell'
147
+ * @returns {Promise<import('./types').SizingContext|null>}
148
+ * @private
149
+ */
150
+ static async _getSizingContext(manager, side, { skipRecalc = false } = {}) {
151
+ if (!manager || !manager.assets)
152
+ return null;
153
+ // 1. Ensure fund state is fresh before sizing
154
+ if (!skipRecalc) {
155
+ await manager.recalculateFunds();
156
+ }
157
+ const snap = manager.getChainFundsSnapshot ? manager.getChainFundsSnapshot() : {};
158
+ const isBuy = side === 'buy';
159
+ const type = isBuy ? ORDER_TYPES.BUY : ORDER_TYPES.SELL;
160
+ // 2. Determine base budget: Always use ALLOCATED funds (respects botFunds %)
161
+ // This ensures the bot only "thinks" about the capital it is allowed to use.
162
+ let budget = isBuy ? (snap.allocatedBuy || 0) : (snap.allocatedSell || 0);
163
+ // 3. Standardize BTS Fee Deduction (Issue #15 consistency)
164
+ // BTS fees are paid for ALL order operations regardless of side, so the
165
+ // BTS-holding side reserves fees for both buy and sell target counts.
166
+ const isBtsSide = (isBuy && manager.config.assetB === 'BTS') || (!isBuy && manager.config.assetA === 'BTS');
167
+ const btsReservationMultiplier = manager.config?.feeParams?.BTS_RESERVATION_MULTIPLIER;
168
+ if (isBtsSide && budget > 0) {
169
+ const targetBuy = Math.max(0, manager.config.activeOrders?.buy ?? 1);
170
+ const targetSell = Math.max(0, manager.config.activeOrders?.sell ?? 1);
171
+ const totalTarget = targetBuy + targetSell;
172
+ const btsFees = calculateOrderCreationFees(manager.config.assetA, manager.config.assetB, totalTarget, btsReservationMultiplier);
173
+ budget = Math.max(0, budget - btsFees);
174
+ }
175
+ // Non-BTS pair: reserve proportional share for BTS fee budget
176
+ if (!isBtsSide && budget > 0) {
177
+ const targetBuy = Math.max(0, manager.config.activeOrders?.buy ?? 1);
178
+ const targetSell = Math.max(0, manager.config.activeOrders?.sell ?? 1);
179
+ const totalTarget = targetBuy + targetSell;
180
+ const formulaBudget = calculateOrderCreationFees(manager.config.assetA, manager.config.assetB, totalTarget, btsReservationMultiplier);
181
+ const configMin = manager.config.min_BTS_value;
182
+ const effectiveMin = (configMin > 0) ? configMin : formulaBudget;
183
+ const btsFree = Format.toFiniteNumber(manager.funds?.btsBalance?.free, 0);
184
+ const btsDeficit = Math.max(0, effectiveMin - btsFree);
185
+ if (btsDeficit > 0) {
186
+ const sideFree = Format.toFiniteNumber(isBuy ? manager.accountTotals?.buyFree : manager.accountTotals?.sellFree, 0);
187
+ const totalFree = Format.toFiniteNumber(manager.accountTotals?.buyFree, 0)
188
+ + Format.toFiniteNumber(manager.accountTotals?.sellFree, 0);
189
+ const share = totalFree > 0 ? sideFree / totalFree : 0.5;
190
+ budget = Math.max(0, budget - btsDeficit * share);
191
+ }
192
+ }
193
+ return {
194
+ budget,
195
+ precision: getPrecisionByOrderType(manager.assets, type),
196
+ config: manager.config
197
+ };
198
+ }
199
+ /**
200
+ * Create the initial order grid structure based on configuration.
201
+ *
202
+ * ALGORITHM: Geometric Grid Creation with Fixed Spread Gap
203
+ * =========================================================
204
+ * This method generates a unified "Master Rail" of price levels with geometric spacing.
205
+ * The grid is centered around startPrice with a fixed-size spread gap.
206
+ *
207
+ * KEY CONCEPTS:
208
+ * - Geometric Spacing: Each price level is incrementPercent% away from neighbors
209
+ * - Master Rail: Single unified array (not separate buy/sell rails)
210
+ * - Spread Gap: Fixed-size buffer between best buy and best sell
211
+ * - Role Assignment: BUY / SPREAD / SELL based on position relative to startPrice
212
+ *
213
+ * SPREAD GAP FORMULA:
214
+ * ===================
215
+ * The spread gap size is calculated to match the target spread percentage:
216
+ *
217
+ * 1. Step Factor (s): s = 1 + (incrementPercent / 100)
218
+ * Example: If incrementPercent = 0.5%, then s = 1.005
219
+ *
220
+ * 2. Minimum Spread: minSpread = incrementPercent × MIN_SPREAD_FACTOR
221
+ * This ensures spread is at least 2× the increment (prevents too-narrow spread)
222
+ *
223
+ * 3. Target Steps (n): Number of price levels needed to achieve target spread
224
+ * Formula: n = ceil(ln(1 + targetSpread/100) / ln(s))
225
+ *
226
+ * Derivation: If we want price to grow by targetSpread% over n steps:
227
+ * - Final price = startPrice × s^n
228
+ * - Growth factor = (1 + targetSpread/100)
229
+ * - Therefore: s^n = (1 + targetSpread/100)
230
+ * - Taking ln: n × ln(s) = ln(1 + targetSpread/100)
231
+ * - Solving: n = ln(1 + targetSpread/100) / ln(s)
232
+ *
233
+ * 4. Gap Slots (G): G = max(MIN_SPREAD_ORDERS, n)
234
+ * Ensures at least MIN_SPREAD_ORDERS slots even if target spread is small
235
+ *
236
+ * EXAMPLE:
237
+ * --------
238
+ * incrementPercent = 0.5%, targetSpread = 2%
239
+ * - s = 1.005
240
+ * - minSpread = 0.5% × 2 = 1%
241
+ * - targetSpread = max(2%, 1%) = 2%
242
+ * - n = ceil(ln(1.02) / ln(1.005)) = ceil(3.98) = 4 steps
243
+ * - G = max(2, 4) = 4 slots
244
+ *
245
+ * @param {import('./types').GridConfig} config - Grid configuration
246
+ * @returns {import('./types').GridCreationResult}
247
+ */
248
+ static createOrderGrid(config) {
249
+ const { startPrice, minPrice, maxPrice, incrementPercent } = config;
250
+ // FIX: Add comprehensive input validation to prevent silent grid creation failures
251
+ if (!Number.isFinite(startPrice)) {
252
+ throw new Error(`Invalid startPrice: ${startPrice}. Must be a finite number.`);
253
+ }
254
+ if (!Number.isFinite(minPrice)) {
255
+ throw new Error(`Invalid minPrice: ${minPrice}. Must be a finite number.`);
256
+ }
257
+ if (minPrice <= 0) {
258
+ throw new Error(`Invalid minPrice: ${minPrice}. Must be positive.`);
259
+ }
260
+ if (!Number.isFinite(maxPrice)) {
261
+ throw new Error(`Invalid maxPrice: ${maxPrice}. Must be a finite number.`);
262
+ }
263
+ if (minPrice >= maxPrice) {
264
+ throw new Error(`Invalid price bounds: minPrice (${minPrice}) must be < maxPrice (${maxPrice}).`);
265
+ }
266
+ if (!(minPrice <= startPrice && startPrice <= maxPrice)) {
267
+ throw new Error(`startPrice (${startPrice}) must be within bounds [${minPrice}, ${maxPrice}].`);
268
+ }
269
+ if (maxPrice <= 0) {
270
+ throw new Error(`maxPrice (${maxPrice}) must be positive.`);
271
+ }
272
+ if (!Number.isFinite(incrementPercent)) {
273
+ throw new Error(`Invalid incrementPercent: ${incrementPercent}. Must be a finite number.`);
274
+ }
275
+ const minPercent = config.incrementBounds?.MIN_PERCENT;
276
+ const maxPercent = config.incrementBounds?.MAX_PERCENT;
277
+ if (incrementPercent < minPercent || incrementPercent > maxPercent) {
278
+ throw new Error(`Invalid incrementPercent: ${incrementPercent}. Must be between ` +
279
+ `${minPercent} and ${maxPercent} (inclusive).`);
280
+ }
281
+ const stepUp = 1 + (incrementPercent / 100);
282
+ const stepDown = 1 - (incrementPercent / 100);
283
+ // ================================================================================
284
+ // STEP 1: GENERATE PRICE LEVELS (Geometric progression)
285
+ // ================================================================================
286
+ // Create a geometric series of prices from minPrice to maxPrice.
287
+ // Each level is incrementPercent% away from its neighbors.
288
+ //
289
+ // We start from startPrice and expand outward in both directions to ensure
290
+ // the grid is centered around the market price.
291
+ const priceLevels = [];
292
+ // Generate levels upwards from startPrice (higher prices for SELL orders)
293
+ // Start from sqrt(stepUp) × startPrice to center the grid
294
+ let upPrice = startPrice * Math.sqrt(stepUp);
295
+ while (upPrice <= maxPrice) {
296
+ priceLevels.push(upPrice);
297
+ upPrice *= stepUp;
298
+ }
299
+ // Generate levels downwards from startPrice (lower prices for BUY orders)
300
+ // Start from sqrt(stepDown) × startPrice to center the grid
301
+ let downPrice = startPrice * Math.sqrt(stepDown);
302
+ while (downPrice >= minPrice) {
303
+ priceLevels.push(downPrice);
304
+ downPrice *= stepDown;
305
+ }
306
+ // Sort all levels from lowest to highest (Master Rail order)
307
+ priceLevels.sort((a, b) => a - b);
308
+ if (priceLevels.length === 0) {
309
+ throw new Error(`Grid generation produced no price levels for startPrice=${startPrice}, ` +
310
+ `bounds=[${minPrice}, ${maxPrice}], incrementPercent=${incrementPercent}. ` +
311
+ `Widen bounds or reduce incrementPercent.`);
312
+ }
313
+ // ================================================================================
314
+ // STEP 2: CALCULATE SPREAD GAP SIZE
315
+ // ================================================================================
316
+ // Determine how many slots should be in the spread zone.
317
+ // See formula documentation in JSDoc above.
318
+ const gapSlots = Grid.calculateGapSlots(incrementPercent, config.targetSpreadPercent, config.gridLimits);
319
+ // ================================================================================
320
+ // STEP 3: FIND SPLIT INDEX & ROLE ASSIGNMENT
321
+ // ================================================================================
322
+ // Determine the boundary and assign roles (BUY/SPREAD/SELL) to each slot.
323
+ //
324
+ // STRATEGY: Center the spread gap around startPrice
325
+ const boundaryIdx = calculateIdealBoundary(priceLevels.map(p => ({ price: p })), startPrice, gapSlots);
326
+ // ================================================================================
327
+ // STEP 4: CREATE ORDER OBJECTS
328
+ // ================================================================================
329
+ // Convert price levels to order objects with assigned roles.
330
+ const orders = priceLevels.map((price, i) => ({
331
+ id: `slot-${i}`,
332
+ price,
333
+ type: null, // assigned below
334
+ state: ORDER_STATES.VIRTUAL,
335
+ size: 0
336
+ }));
337
+ const updatedOrders = assignGridRoles(orders, boundaryIdx, gapSlots, ORDER_TYPES, ORDER_STATES);
338
+ const buyCount = updatedOrders.filter(o => o.type === ORDER_TYPES.BUY).length;
339
+ const sellCount = updatedOrders.filter(o => o.type === ORDER_TYPES.SELL).length;
340
+ if (buyCount === 0 || sellCount === 0) {
341
+ throw new Error(`Grid generation produced an imbalanced rail (buy=${buyCount}, sell=${sellCount}) for ` +
342
+ `startPrice=${startPrice}, bounds=[${minPrice}, ${maxPrice}], incrementPercent=${incrementPercent}, ` +
343
+ `targetSpreadPercent=${config.targetSpreadPercent}. Widen bounds or reduce target spread.`);
344
+ }
345
+ const initialSpreadCount = {
346
+ buy: Math.floor(gapSlots / 2),
347
+ sell: gapSlots - Math.floor(gapSlots / 2)
348
+ };
349
+ return { orders: updatedOrders, boundaryIdx, initialSpreadCount };
350
+ }
351
+ /**
352
+ * Internal utility to clear all order-related manager caches.
353
+ * Prevents stale references during grid reinitialization.
354
+ * RC-2: Synchronized to prevent concurrent modifications during clear
355
+ *
356
+ * Note: Uses explicit assignment instead of .clear() to enforce COW semantics:
357
+ * - Replace the master grid atomically with a fresh Map instance
358
+ * - Avoid mutating any previously referenced Map object
359
+ * @param {import('./types').OrderManager} manager - OrderManager instance
360
+ * @private
361
+ */
362
+ static _clearOrderCachesLogic(manager) {
363
+ // Replace frozen master grid with fresh empty frozen Map (COW pattern)
364
+ manager.orders = Object.freeze(new Map());
365
+ // Clear index Sets with fresh empty Sets (mutable for _applyOrderUpdate)
366
+ if (manager._ordersByState) {
367
+ for (const key of Object.keys(manager._ordersByState)) {
368
+ manager._ordersByState[key] = new Set();
369
+ }
370
+ }
371
+ if (manager._ordersByType) {
372
+ for (const key of Object.keys(manager._ordersByType)) {
373
+ manager._ordersByType[key] = new Set();
374
+ }
375
+ }
376
+ }
377
+ /**
378
+ * Restore a persisted grid snapshot onto a manager instance.
379
+ * @param {import('./types').OrderManager} manager - The manager instance.
380
+ * @param {Array<import('./types').GridOrderSlot>} grid - The persisted grid array.
381
+ * @param {number|null} [boundaryIdx=null] - The master boundary index.
382
+ * @returns {Promise<void>}
383
+ */
384
+ static async loadGrid(manager, grid, boundaryIdx = null) {
385
+ if (!Array.isArray(grid))
386
+ return;
387
+ return await manager._gridLock.acquire(async () => {
388
+ try {
389
+ await manager._initializeAssets();
390
+ }
391
+ catch (e) {
392
+ manager.logger?.log?.(`Asset initialization failed during grid load: ${e.message}`, 'warn');
393
+ }
394
+ // RC-2: Use logic helper
395
+ Grid._clearOrderCachesLogic(manager);
396
+ const savedBtsFeesOwed = manager.funds.btsFeesOwed;
397
+ manager.resetFunds();
398
+ manager.funds.btsFeesOwed = savedBtsFeesOwed;
399
+ // Restore boundary index for StrategyEngine
400
+ if (typeof boundaryIdx === 'number') {
401
+ manager.boundaryIdx = boundaryIdx;
402
+ // FIX: Use consistent optional chaining pattern for logger calls
403
+ manager.logger?.log?.(`Restored boundary index: ${boundaryIdx}`, 'info');
404
+ }
405
+ manager.pauseRecalcLogging();
406
+ manager.pauseFundRecalc();
407
+ try {
408
+ // RC-2: Use applyOrderUpdate (PRIVATE/UNLOCKED)
409
+ for (const order of grid) {
410
+ let currentOrder = order;
411
+ if (isPhantomOrder(order)) {
412
+ manager.logger?.log?.(`Sanitizing corrupted order ${order.id}: ACTIVE/PARTIAL without orderId -> VIRTUAL`, 'warn');
413
+ currentOrder = { ...order, state: ORDER_STATES.VIRTUAL };
414
+ }
415
+ await manager._applyOrderUpdate(currentOrder, 'grid-load', { skipAccounting: true });
416
+ }
417
+ const spreadCount = grid.filter(o => o.type === ORDER_TYPES.SPREAD).length;
418
+ manager.targetSpreadCount = spreadCount;
419
+ manager.currentSpreadCount = spreadCount;
420
+ }
421
+ finally {
422
+ await manager.resumeFundRecalc();
423
+ manager.resumeRecalcLogging();
424
+ }
425
+ manager.logger?.log?.(`Loaded ${manager.orders.size} orders from persisted grid.`, 'info');
426
+ });
427
+ }
428
+ /**
429
+ * Initialize the order grid with blockchain-aware sizing.
430
+ * @param {import('./types').OrderManager} manager - The manager instance.
431
+ * @returns {Promise<void>}
432
+ * @throws {Error} If initialization fails or account totals are missing.
433
+ */
434
+ static async initializeGrid(manager) {
435
+ if (!manager)
436
+ throw new Error('initializeGrid requires a manager instance');
437
+ await manager._initializeAssets();
438
+ // FIX: Add explicit state validation to prevent cryptic errors later
439
+ if (!manager.assets || !manager.assets.assetA || !manager.assets.assetB) {
440
+ throw new Error('Asset initialization did not complete properly - assetA or assetB undefined');
441
+ }
442
+ if (!manager.config) {
443
+ throw new Error('Manager config not initialized before grid initialization');
444
+ }
445
+ const mpRaw = manager.config.startPrice;
446
+ manager.logger?.log?.(`[DIAGNOSTIC] initializeGrid: mpRaw type=${typeof mpRaw}, value=${mpRaw}`, 'debug');
447
+ // Auto-derive price if not a fixed numeric value (e.g. "pool", "book", or undefined)
448
+ if (typeof mpRaw !== 'number' || isNaN(mpRaw)) {
449
+ try {
450
+ const { BitShares } = require('../bitshares_client');
451
+ const derived = await derivePrice(BitShares, manager.config.assetA, manager.config.assetB, manager.config.priceMode || 'auto');
452
+ if (derived) {
453
+ manager.logger?.log?.(`[DIAGNOSTIC] initializeGrid: Derived new startPrice=${derived.toFixed(8)} (mode=${manager.config.priceMode || 'auto'})`, 'info');
454
+ manager.config.startPrice = Number(derived);
455
+ }
456
+ else {
457
+ throw new Error(`Price derivation returned no result for ${manager.config.assetA}/${manager.config.assetB}`);
458
+ }
459
+ }
460
+ catch (err) {
461
+ manager.logger?.log?.(`Failed to derive market price: ${err.message}`, 'warn');
462
+ throw err; // Re-throw to prevent "pool" string reaching numeric math
463
+ }
464
+ }
465
+ const configuredStartPrice = manager.config.startPrice;
466
+ const configuredMinPrice = manager.config.minPrice;
467
+ const configuredMaxPrice = manager.config.maxPrice;
468
+ const mp = Number(manager.config.startPrice);
469
+ // Derive gridPrice — separate reference for x-factor bounds (may differ from startPrice).
470
+ // Supported modes:
471
+ // - numeric: fixed value
472
+ // - "pool" / "book": live blockchain price for the pair
473
+ // - "ama"/"ama1".."ama4": center from profiles/orders/<botKey>.dynamicgrid.json
474
+ // - null/anything else: fallback to startPrice (backward-compatible)
475
+ let gp = mp;
476
+ let gpSource = 'startPrice';
477
+ let amaSnapshot = null;
478
+ const whitelistFlags = getWhitelistFlags(manager.config.botKey);
479
+ const isGridRangeScalingWhitelisted = whitelistFlags.asymmetricBounds === true;
480
+ let gridPriceOffsetPct = 0;
481
+ const gpRaw = manager.config.gridPrice;
482
+ const gpMode = (typeof gpRaw === 'string') ? gpRaw.trim().toLowerCase() : null;
483
+ if (typeof gpRaw === 'number' && Number.isFinite(gpRaw) && gpRaw > 0) {
484
+ gp = gpRaw;
485
+ gpSource = 'numeric';
486
+ manager.logger?.log?.(`[DIAGNOSTIC] initializeGrid: gridPrice=numeric ${gp.toFixed(8)}`, 'info');
487
+ }
488
+ else if (gpMode === 'pool' || gpMode === 'book') {
489
+ try {
490
+ const { BitShares } = require('../bitshares_client');
491
+ const derived = await derivePrice(BitShares, manager.config.assetA, manager.config.assetB, gpMode);
492
+ if (derived) {
493
+ gp = Number(derived);
494
+ gpSource = gpMode;
495
+ manager.logger?.log?.(`[DIAGNOSTIC] initializeGrid: gridPrice=${gpMode} ${gp.toFixed(8)}`, 'info');
496
+ }
497
+ else {
498
+ manager.logger?.log?.(`initializeGrid: ${gpMode} gridPrice unavailable, falling back to startPrice`, 'warn');
499
+ }
500
+ }
501
+ catch (err) {
502
+ manager.logger?.log?.(`initializeGrid: ${gpMode} gridPrice derivation failed: ${err.message}`, 'warn');
503
+ }
504
+ }
505
+ else if (/^ama(?:[1-4])?$/.test(gpMode || '')) {
506
+ amaSnapshot = loadAmaCenterSnapshot(manager.config.botKey);
507
+ const amaCenter = amaSnapshot?.gridCenterPrice ?? loadAmaCenterPrice(manager.config.botKey);
508
+ if (Number.isFinite(amaCenter) && amaCenter > 0) {
509
+ gp = amaCenter;
510
+ gpSource = 'ama';
511
+ const snapshotGridPriceOffsetPct = Number(amaSnapshot?.gridPriceOffsetPct);
512
+ const hasGridPriceOffset = isGridRangeScalingWhitelisted
513
+ && Number.isFinite(snapshotGridPriceOffsetPct)
514
+ && snapshotGridPriceOffsetPct !== 0;
515
+ gridPriceOffsetPct = hasGridPriceOffset ? snapshotGridPriceOffsetPct : 0;
516
+ manager.logger?.log?.(`[DIAGNOSTIC] initializeGrid: gridPrice=AMA center ${gp.toFixed(8)}`, 'info');
517
+ }
518
+ else {
519
+ manager.logger?.log?.(`initializeGrid: AMA center unavailable for gridPrice, falling back to startPrice`, 'warn');
520
+ }
521
+ }
522
+ const minP = resolveConfiguredPriceBound(manager.config.minPrice, DEFAULT_CONFIG.minPrice, gp, 'min');
523
+ const maxP = resolveConfiguredPriceBound(manager.config.maxPrice, DEFAULT_CONFIG.maxPrice, gp, 'max');
524
+ // Asymmetric bound adjustment: widen the bound in the AMA trend direction
525
+ // and tighten the opposite side, giving the grid more room when the center
526
+ // trails price. Uses slope data from the dynamicgrid.json snapshot.
527
+ let resolvedMinP = minP;
528
+ let resolvedMaxP = maxP;
529
+ let rangeScalingFactor = null;
530
+ if (gpSource === 'ama' && Number.isFinite(minP) && Number.isFinite(maxP)
531
+ && isGridRangeScalingWhitelisted) {
532
+ const dw = amaSnapshot?.dynamicWeights;
533
+ // Fallback to root-level asymmetricBounds when dynamicWeights is
534
+ // absent (asymmetricBounds: true without dynamicWeight: true).
535
+ const rootBounds = !dw && amaSnapshot?.asymmetricBounds
536
+ && typeof amaSnapshot.asymmetricBounds === 'object'
537
+ ? amaSnapshot.asymmetricBounds
538
+ : null;
539
+ if (dw) {
540
+ const maxAsymmetryFactor = resolveMaxAsymmetryFactor(manager.config.asymmetricBounds?.maxAsymmetryFactor, dw?.maxAsymmetryFactor, MARKET_ADAPTER.ASYMMETRIC_BOUNDS_MAX_ASYMMETRY_FACTOR);
541
+ const adjustment = applyAsymmetricBounds({
542
+ centerPrice: gp,
543
+ minPrice: minP,
544
+ maxPrice: maxP,
545
+ trend: dw?.trend,
546
+ slopeOffset: dw?.slopeOffset,
547
+ maxSlopeOffset: dw?.maxSlopeOffset,
548
+ maxAsymmetryFactor,
549
+ });
550
+ if (Number.isFinite(adjustment.appliedAsymmetryFactor)) {
551
+ resolvedMinP = adjustment.resolvedMinPrice;
552
+ resolvedMaxP = adjustment.resolvedMaxPrice;
553
+ rangeScalingFactor = Number(adjustment.appliedAsymmetryFactor);
554
+ manager.logger?.log?.(`[BOUND-ASYMMETRY] trend=${dw.trend} slopeOffset=${dw.slopeOffset.toFixed(4)} `
555
+ + `raw=${(adjustment.rawAsymmetryFactor * 100).toFixed(1)}% `
556
+ + `cap=${(maxAsymmetryFactor * 100).toFixed(0)}% `
557
+ + `asymmetry=${(adjustment.appliedAsymmetryFactor * 100).toFixed(1)}% `
558
+ + `min ${minP.toFixed(8)}→${resolvedMinP.toFixed(8)} `
559
+ + `max ${maxP.toFixed(8)}→${resolvedMaxP.toFixed(8)}`, 'info');
560
+ }
561
+ }
562
+ else if (rootBounds && Number.isFinite(rootBounds.appliedAsymmetryFactor)
563
+ && (rootBounds.trend === 'UP' || rootBounds.trend === 'DOWN')) {
564
+ const asymmetry = Number(rootBounds.appliedAsymmetryFactor);
565
+ const rootTrend = rootBounds.trend;
566
+ if (rootTrend === 'DOWN') {
567
+ resolvedMinP = gp / ((gp / minP) * (1 + asymmetry));
568
+ resolvedMaxP = gp * ((maxP / gp) * (1 - asymmetry));
569
+ }
570
+ else {
571
+ resolvedMinP = gp / ((gp / minP) * (1 - asymmetry));
572
+ resolvedMaxP = gp * ((maxP / gp) * (1 + asymmetry));
573
+ }
574
+ rangeScalingFactor = asymmetry;
575
+ manager.logger?.log?.(`[BOUND-ASYMMETRY] trend=${rootTrend} `
576
+ + `asymmetry=${(asymmetry * 100).toFixed(1)}% `
577
+ + `(root-level) min ${minP.toFixed(8)}→${resolvedMinP.toFixed(8)} `
578
+ + `max ${maxP.toFixed(8)}→${resolvedMaxP.toFixed(8)}`, 'info');
579
+ }
580
+ }
581
+ let gridStartPrice = mp;
582
+ let offsetAdjustedStartPrice = gridStartPrice;
583
+ if (gpSource === 'ama' && gridPriceOffsetPct !== 0 && Number.isFinite(gridStartPrice) && gridStartPrice > 0) {
584
+ const adjustedMarketPrice = gridStartPrice * (1 + (gridPriceOffsetPct / 100));
585
+ manager.logger?.log?.(`[DIAGNOSTIC] initializeGrid: applying AMA market-price offset ${gridPriceOffsetPct.toFixed(3)}% `
586
+ + `to startPrice ${gridStartPrice.toFixed(8)} -> ${adjustedMarketPrice.toFixed(8)}`, 'info');
587
+ gridStartPrice = adjustedMarketPrice;
588
+ offsetAdjustedStartPrice = adjustedMarketPrice;
589
+ }
590
+ if (!(gridStartPrice >= resolvedMinP && gridStartPrice <= resolvedMaxP)) {
591
+ if (Number.isFinite(gp) && gp > 0 && gp >= resolvedMinP && gp <= resolvedMaxP) {
592
+ gridStartPrice = gp;
593
+ manager.logger?.log?.(`initializeGrid: startPrice (${mp}) outside bounds [${resolvedMinP}, ${resolvedMaxP}]; using gridPrice center ${gp}`, 'warn');
594
+ }
595
+ else {
596
+ const clamped = Math.min(resolvedMaxP, Math.max(resolvedMinP, gridStartPrice));
597
+ manager.logger?.log?.(`initializeGrid: startPrice (${mp}) outside bounds [${resolvedMinP}, ${resolvedMaxP}]; clamping to ${clamped}`, 'warn');
598
+ gridStartPrice = clamped;
599
+ }
600
+ }
601
+ manager.config.minPrice = resolvedMinP;
602
+ manager.config.maxPrice = resolvedMaxP;
603
+ manager._lastGridPricingContext = {
604
+ gridPrice: gp,
605
+ gridPriceOffsetPct,
606
+ offsetAdjustedStartPrice,
607
+ startPrice: gridStartPrice,
608
+ configuredMinPrice,
609
+ configuredMaxPrice,
610
+ rangeScalingFactor
611
+ };
612
+ // Ensure percentage-based funds are resolved before sizing
613
+ try {
614
+ if (manager.accountId && !manager.accountTotals) {
615
+ await manager.waitForAccountTotals(TIMING.ACCOUNT_TOTALS_TIMEOUT_MS);
616
+ }
617
+ }
618
+ catch (e) {
619
+ manager.logger?.log?.(`Failed to load account totals: ${e.message}`, 'warn');
620
+ // FIX: Add error handling - cannot proceed with grid initialization without account totals
621
+ // Continuing would create grid with 0 fund allocation, rendering it non-functional
622
+ throw new Error(`Cannot initialize grid without account totals: ${e.message}`);
623
+ }
624
+ const { orders, boundaryIdx, initialSpreadCount } = Grid.createOrderGrid({
625
+ ...manager.config,
626
+ startPrice: gridStartPrice,
627
+ minPrice: resolvedMinP,
628
+ maxPrice: resolvedMaxP,
629
+ });
630
+ // RC-8: Update boundary with notification to dependent systems
631
+ // Persist master boundary for StrategyEngine
632
+ if (manager.boundaryIdx !== boundaryIdx) {
633
+ manager.boundaryIdx = boundaryIdx;
634
+ // RC-8: Notify StrategyEngine of boundary change (if method exists)
635
+ if (typeof manager.notifyBoundaryUpdate === 'function') {
636
+ try {
637
+ manager.notifyBoundaryUpdate(boundaryIdx);
638
+ }
639
+ catch (err) {
640
+ manager.logger?.log?.(`Error notifying boundary update: ${err.message}`, 'warn');
641
+ }
642
+ }
643
+ }
644
+ const minSellSize = getMinAbsoluteOrderSize(ORDER_TYPES.SELL, manager.assets);
645
+ const minBuySize = getMinAbsoluteOrderSize(ORDER_TYPES.BUY, manager.assets);
646
+ const { A: precA, B: precB } = getPrecisionsForManager(manager.assets);
647
+ // Use centralized sizing context for both sides.
648
+ // Resolve funds once upfront so both contexts share the same snapshot,
649
+ // avoiding a redundant recalculateFunds inside the second _getSizingContext call.
650
+ await manager.recalculateFunds();
651
+ const sellCtx = await Grid._getSizingContext(manager, 'sell', { skipRecalc: true });
652
+ const buyCtx = await Grid._getSizingContext(manager, 'buy', { skipRecalc: true });
653
+ if (!sellCtx || !buyCtx)
654
+ throw new Error('Failed to retrieve sizing context for grid initialization');
655
+ let sizedOrders = calculateOrderSizes(orders, manager.config, sellCtx.budget, buyCtx.budget, minSellSize, minBuySize, precA, precB);
656
+ // Verification of sizes
657
+ const sells = filterOrdersByType(sizedOrders, ORDER_TYPES.SELL).map(o => Number(o.size || 0));
658
+ const buys = filterOrdersByType(sizedOrders, ORDER_TYPES.BUY).map(o => Number(o.size || 0));
659
+ if (checkSizesBeforeMinimum(sells, minSellSize, precA) || checkSizesBeforeMinimum(buys, minBuySize, precB)) {
660
+ throw new Error('Calculated orders fall below minimum allowable size.');
661
+ }
662
+ // Check for warning if orders are near minimal size (regression fix)
663
+ const warningSellSize = minSellSize > 0 ? getMinAbsoluteOrderSize(ORDER_TYPES.SELL, manager.assets, 100) : 0;
664
+ const warningBuySize = minBuySize > 0 ? getMinAbsoluteOrderSize(ORDER_TYPES.BUY, manager.assets, 100) : 0;
665
+ if (checkSizeThreshold(sells, warningSellSize, precA, false) || checkSizeThreshold(buys, warningBuySize, precB, false)) {
666
+ manager.logger?.log?.("WARNING: Order grid contains orders near minimum size. To ensure the bot runs properly, consider increasing the funds of your bot.", "warn");
667
+ }
668
+ // RC-2: Wrap atomic changes in grid lock
669
+ await manager._gridLock.acquire(async () => {
670
+ Grid._clearOrderCachesLogic(manager);
671
+ manager.resetFunds();
672
+ manager.pauseRecalcLogging();
673
+ manager.pauseFundRecalc();
674
+ try {
675
+ // RC-2: Use _applyOrderUpdate (PRIVATE/UNLOCKED)
676
+ for (const order of sizedOrders) {
677
+ await manager._applyOrderUpdate(order, 'grid-init', { skipAccounting: true });
678
+ }
679
+ }
680
+ finally {
681
+ await manager.resumeFundRecalc();
682
+ manager.resumeRecalcLogging();
683
+ }
684
+ // RC-6: Spread count updates protected by grid lock
685
+ manager.targetSpreadCount = initialSpreadCount.buy + initialSpreadCount.sell;
686
+ manager.currentSpreadCount = manager.targetSpreadCount;
687
+ });
688
+ // FIX: Use consistent optional chaining pattern for all logger calls
689
+ manager.logger?.log?.(`Initialized grid with ${orders.length} orders.`, 'info');
690
+ manager.logger?.logFundsStatus?.(manager);
691
+ manager.logger?.logOrderGrid?.(Array.from(manager.orders.values()), gridStartPrice);
692
+ }
693
+ /**
694
+ * Full grid resynchronization from blockchain state.
695
+ * @param {import('./types').OrderManager} manager - The manager instance.
696
+ * @param {Object} opts - Options for resynchronization.
697
+ * @param {Function} opts.readOpenOrdersFn - Function to read open orders.
698
+ * @param {Object} opts.chainOrders - Chain orders module.
699
+ * @param {string} opts.account - Account name.
700
+ * @param {string} opts.privateKey - Private key.
701
+ * @returns {Promise<void>}
702
+ */
703
+ static async recalculateGrid(manager, opts) {
704
+ const { readOpenOrdersFn, chainOrders, account, privateKey } = opts;
705
+ // Suppress invariant warnings during full resync
706
+ manager.startBootstrap();
707
+ try {
708
+ // FIX: Use consistent optional chaining pattern for logger calls
709
+ manager.logger?.log?.('Starting full resync...', 'info');
710
+ await manager._initializeAssets();
711
+ await manager.fetchAccountTotals();
712
+ const chainOpenOrders = await readOpenOrdersFn();
713
+ if (!Array.isArray(chainOpenOrders))
714
+ return;
715
+ // CRITICAL: Filter out PARTIAL orders before synchronizing - they're from old grid
716
+ // and shouldn't be part of the fresh regenerated grid structure
717
+ const activeOrders = chainOpenOrders.filter(o => o.state !== ORDER_STATES.PARTIAL);
718
+ await manager.syncFromOpenOrders(activeOrders, { skipAccounting: true, fillLockAlreadyHeld: true });
719
+ manager.resetFunds();
720
+ await manager.persistGrid();
721
+ await Grid.initializeGrid(manager);
722
+ const { reconcileGridOrders } = require('./grid_reconcile');
723
+ // FIX: Add error context for debugging grid recalculation issues
724
+ try {
725
+ await reconcileGridOrders({ manager, config: manager.config, account, privateKey, chainOrders, chainOpenOrders, fillLockAlreadyHeld: true });
726
+ }
727
+ catch (err) {
728
+ manager.logger?.log?.(`Error during startup order reconciliation: ${err.message}`, 'error');
729
+ throw new Error(`Grid recalculation failed during order reconciliation: ${err.message}`);
730
+ }
731
+ // FIX: Use consistent optional chaining pattern for logger calls
732
+ manager.logger?.log?.('Full resync complete.', 'info');
733
+ }
734
+ finally {
735
+ manager.finishBootstrap();
736
+ }
737
+ }
738
+ /**
739
+ * Check for grid divergence and trigger update if threshold is met.
740
+ *
741
+ * @param {import('./types').OrderManager} manager - Manager instance with order state
742
+ * @returns {import('./types').SideUpdateFlags}
743
+ */
744
+ static checkAndUpdateGridIfNeeded(manager) {
745
+ const threshold = manager.config?.gridLimits?.GRID_REGENERATION_PERCENTAGE;
746
+ const chainSnap = manager.getChainFundsSnapshot();
747
+ const gridBuy = Number(manager.funds?.total?.grid?.buy || 0);
748
+ const gridSell = Number(manager.funds?.total?.grid?.sell || 0);
749
+ const result = { buyUpdated: false, sellUpdated: false };
750
+ const sides = [
751
+ { name: 'buy', grid: gridBuy, orderType: ORDER_TYPES.BUY },
752
+ { name: 'sell', grid: gridSell, orderType: ORDER_TYPES.SELL }
753
+ ];
754
+ for (const s of sides) {
755
+ if (s.grid <= 0)
756
+ continue;
757
+ const availableFunds = calculateAvailableFundsValue(s.name, manager.accountTotals, manager.funds, manager.config.assetA, manager.config.assetB, manager.config.activeOrders, manager.config.min_BTS_value, manager.config.feeParams ?? null);
758
+ // Denominator: side's allocated capital (or chain total fallback).
759
+ const allocated = s.name === 'buy' ? chainSnap.allocatedBuy : chainSnap.allocatedSell;
760
+ const denominator = (allocated > 0) ? allocated : (s.grid + availableFunds);
761
+ const ratio = (denominator > 0) ? (availableFunds / denominator) * 100 : 0;
762
+ manager.logger?.log?.(`[DIVERGENCE] ${s.name.toUpperCase()} ratio check: availableFunds=${availableFunds.toFixed(5)}, allocated=${allocated.toFixed(5)}, ratio=${ratio.toFixed(4)}% (threshold=${threshold}%) → ${ratio >= threshold ? 'TRIGGER' : 'no trigger'}`, 'debug');
763
+ if (ratio >= threshold) {
764
+ // RC-3: Use Set for automatic duplicate prevention
765
+ if (!(manager._gridSidesUpdated instanceof Set))
766
+ manager._gridSidesUpdated = new Set();
767
+ manager._gridSidesUpdated.add(s.orderType);
768
+ if (s.name === 'buy')
769
+ result.buyUpdated = true;
770
+ else
771
+ result.sellUpdated = true;
772
+ }
773
+ }
774
+ return result;
775
+ }
776
+ /**
777
+ * Standardize grid sizes using blockchain total context.
778
+ *
779
+ * FUND CAPPING STRATEGY:
780
+ * =====================
781
+ * During grid regeneration (e.g., after fills increase available funds),
782
+ * this method recalculates all order sizes using geometric weighting.
783
+ * However, ACTIVE/PARTIAL orders must not grow larger than currently available funds.
784
+ *
785
+ * Rationale for capping:
786
+ * 1. POST-FILL EXPANSION PREVENTION: After a large fill, funds become available.
787
+ * A naive size recalculation might expand orders, consuming all new capital.
788
+ * Capping prevents this "resize explosion" by limiting growth to available free balance.
789
+ * 2. VIRTUAL ORDER PROTECTION: Virtual orders (not yet placed) are uncapped,
790
+ * allowing natural expansion when their slot comes up for placement.
791
+ * 3. BLOCKCHAIN-BACKED CONSTRAINT: sideFreeAvailable tracks exactly what we can spend,
792
+ * decreasing as commitments grow (proportional to realized delta).
793
+ *
794
+ * Fund Capping Algorithm:
795
+ * ========================
796
+ * For each ACTIVE/PARTIAL order slot:
797
+ * 1. Calculate new size from geometric series
798
+ * 2. If delta > 0 (growth):
799
+ * - affordableDelta = min(delta, sideFreeAvailable)
800
+ * - Cap growth to what we actually have: newSize = currentSize + affordableDelta
801
+ * - Deduct from sideFreeAvailable (this spending is now committed)
802
+ * 3. If delta < 0 (shrinkage):
803
+ * - Release the freed capital back to sideFreeAvailable
804
+ * - Allows later slots to grow into this freed capacity
805
+ * 4. For VIRTUAL orders (not on-chain):
806
+ * - Apply new size directly (no capping)
807
+ * - They will be constrained when actually placed
808
+ *
809
+ * Example (2 slots, buy side, budget=1000, simplify to linear):
810
+ * ========================================================
811
+ * Initial: slot[0]=400 (ACTIVE), slot[1]=0 (VIRTUAL), sideFree=600
812
+ * Recalc: newSizes=[500, 500]
813
+ *
814
+ * Process slot[0]:
815
+ * - Type: ACTIVE, current=400, new=500, delta=+100
816
+ * - affordableDelta = min(100, 600) = 100
817
+ * - Apply: size=500 (full growth), sideFree=500
818
+ *
819
+ * Process slot[1]:
820
+ * - Type: VIRTUAL (not capped), current=0, new=500, delta=+500
821
+ * - Apply: size=500 (no cap check)
822
+ * - Result: slot[1] ready for placement, will consume from sideFree when placed
823
+ *
824
+ * @param {import('./types').OrderManager} manager - OrderManager instance
825
+ * @param {string} orderType - ORDER_TYPES.BUY or ORDER_TYPES.SELL
826
+ * @param {Object} [options] - Options object
827
+ * @param {import('./working_grid')} [options.workingGrid] - Working grid for COW pattern
828
+ * @returns {Promise<{actions: Array, changed: boolean}|undefined>} - COW result or undefined
829
+ * @private
830
+ */
831
+ static async _recalculateGridOrderSizesFromBlockchain(manager, orderType, options = {}) {
832
+ if (!manager.assets)
833
+ return options?.workingGrid ? { actions: [], changed: false } : undefined;
834
+ const workingGrid = options?.workingGrid || null;
835
+ const collectActions = !!workingGrid;
836
+ const isBuy = orderType === ORDER_TYPES.BUY;
837
+ const sideName = isBuy ? 'buy' : 'sell';
838
+ // Use centralized sizing context (respects botFunds % allocation)
839
+ const ctx = await Grid._getSizingContext(manager, sideName);
840
+ if (!ctx)
841
+ return collectActions ? { actions: [], changed: false } : undefined;
842
+ // Get ALL slots for this side, sorted for calculateRotationOrderSizes
843
+ // SELL: sorted ASC (Market to Edge)
844
+ // BUY: sorted ASC (Edge to Market)
845
+ const allSideSlots = Array.from(manager.orders.values())
846
+ .filter(o => o.type === orderType)
847
+ .sort((a, b) => a.price - b.price);
848
+ if (allSideSlots.length === 0)
849
+ return collectActions ? { actions: [], changed: false } : undefined;
850
+ // Calculate geometric sizes for the ENTIRE rail
851
+ const newSizes = calculateRotationOrderSizes(ctx.budget, 0, allSideSlots.length, orderType, manager.config, 0, ctx.precision);
852
+ const actions = [];
853
+ let changed = false;
854
+ const freeKey = isBuy ? 'buyFree' : 'sellFree';
855
+ let sideFreeAvailable = Number(manager.accountTotals?.[freeKey] || 0);
856
+ if (!collectActions)
857
+ manager.pauseRecalcLogging();
858
+ try {
859
+ // Apply new sizes to all slots on the side
860
+ for (let i = 0; i < allSideSlots.length; i++) {
861
+ const slot = allSideSlots[i];
862
+ let newSize = newSizes[i] || 0;
863
+ // FUND CAPPING FOR COMMITTED (ON-CHAIN) ORDERS:
864
+ // Only ACTIVE/PARTIAL orders are constrained by available funds.
865
+ // Virtual orders (not yet placed) will be constrained when they are actually placed.
866
+ //
867
+ // NOTE: BTS update fees are paid from BTS balance (separate from asset balance),
868
+ // so they don't affect this asset-side size cap. Fee budgets are tracked in
869
+ // funds.btsFeesOwed and reserved separately via btsFeesReservation.
870
+ const isCommitted = isOrderOnChain(slot);
871
+ if (isCommitted) {
872
+ const currentSize = Number(slot.size || 0);
873
+ const delta = newSize - currentSize;
874
+ if (delta > 0) {
875
+ // GROWTH: Cap to available free balance
876
+ // This prevents aggressive expansion after fills
877
+ const affordableDelta = Math.min(delta, Math.max(0, sideFreeAvailable));
878
+ if (affordableDelta < delta) {
879
+ // Cannot afford full growth; cap to what's available
880
+ newSize = currentSize + affordableDelta;
881
+ }
882
+ sideFreeAvailable = Math.max(0, sideFreeAvailable - affordableDelta);
883
+ }
884
+ else if (delta < 0) {
885
+ // SHRINKAGE: Release freed capital back for other slots
886
+ sideFreeAvailable += Math.abs(delta);
887
+ }
888
+ }
889
+ // Use integer comparison to avoid redundant updates from float noise
890
+ const currentSizeInt = floatToBlockchainInt(slot.size || 0, ctx.precision);
891
+ const newSizeInt = floatToBlockchainInt(newSize, ctx.precision);
892
+ if (slot.size === undefined || currentSizeInt !== newSizeInt) {
893
+ changed = true;
894
+ if (collectActions) {
895
+ workingGrid.set(slot.id, {
896
+ ...slot,
897
+ size: newSize
898
+ });
899
+ if (isCommitted && hasOnChainId(slot)) {
900
+ actions.push({
901
+ type: COW_ACTIONS.UPDATE,
902
+ id: slot.id,
903
+ orderId: slot.orderId,
904
+ newGridId: slot.id,
905
+ newSize,
906
+ newPrice: slot.price,
907
+ order: {
908
+ id: slot.id,
909
+ type: slot.type,
910
+ price: slot.price,
911
+ size: newSize
912
+ }
913
+ });
914
+ }
915
+ }
916
+ else {
917
+ // CRITICAL: Set skipAccounting=false to ensure delta is consumed/released from ChainFree
918
+ const resizeOk = await manager._updateOrder({ ...slot, size: newSize }, 'grid-resize', { skipAccounting: false, fee: 0 });
919
+ if (resizeOk === false) {
920
+ manager.logger?.log?.(`Failed to resize order ${slot.id}`, 'warn');
921
+ }
922
+ }
923
+ }
924
+ }
925
+ if (!collectActions) {
926
+ await manager.recalculateFunds();
927
+ }
928
+ }
929
+ finally {
930
+ if (!collectActions)
931
+ manager.resumeRecalcLogging();
932
+ }
933
+ if (collectActions) {
934
+ return { actions, changed };
935
+ }
936
+ return undefined;
937
+ }
938
+ /**
939
+ * High-level entry for resizing grid from snapshot using COW pattern.
940
+ * Creates working grid, calculates new sizes, generates UPDATE actions.
941
+ * Master grid is only updated after successful blockchain confirmation.
942
+ *
943
+ * @param {import('./types').OrderManager} manager - Manager instance
944
+ * @param {string} orderType - 'buy', 'sell', or 'both' - which sides to update
945
+ * @param {boolean} [fromBlockchainTimer=false] - If true, skip refetch of account totals (already current)
946
+ * @param {number|null} [overrideBoundaryIdx=null] - Optional override for boundary index
947
+ * @returns {Promise<{actions: Array, workingGrid: import('./working_grid'), workingIndexes: Object, workingBoundary: number, hasWorkingChanges: boolean, aborted: boolean}|null>}
948
+ */
949
+ static async updateGridFromBlockchainSnapshot(manager, orderType = 'both', fromBlockchainTimer = false, overrideBoundaryIdx = null) {
950
+ if (!fromBlockchainTimer && manager.config?.accountId) {
951
+ await manager.fetchAccountTotals(manager.config.accountId);
952
+ }
953
+ const { WorkingGrid } = require('./working_grid');
954
+ const workingGrid = new WorkingGrid(manager.orders, { baseVersion: manager._gridVersion });
955
+ const allActions = [];
956
+ let hasWorkingChanges = false;
957
+ // Calculate size updates for each side (via existing sizing function in COW mode)
958
+ if (orderType === ORDER_TYPES.BUY || orderType === 'both') {
959
+ const buyResult = await Grid._recalculateGridOrderSizesFromBlockchain(manager, ORDER_TYPES.BUY, { workingGrid });
960
+ allActions.push(...buyResult.actions);
961
+ hasWorkingChanges = hasWorkingChanges || buyResult.changed;
962
+ }
963
+ if (orderType === ORDER_TYPES.SELL || orderType === 'both') {
964
+ const sellResult = await Grid._recalculateGridOrderSizesFromBlockchain(manager, ORDER_TYPES.SELL, { workingGrid });
965
+ allActions.push(...sellResult.actions);
966
+ hasWorkingChanges = hasWorkingChanges || sellResult.changed;
967
+ }
968
+ // If the boundary is shifting, reassign slot types in the WorkingGrid now.
969
+ // This ensures the COW commit delivers consistent types + boundaryIdx in one
970
+ // atomic operation — manager.boundaryIdx must not be touched before the commit.
971
+ const newBoundary = (overrideBoundaryIdx !== null) ? overrideBoundaryIdx : manager.boundaryIdx;
972
+ if (overrideBoundaryIdx !== null && overrideBoundaryIdx !== manager.boundaryIdx) {
973
+ const gapSlots = Grid.calculateGapSlots(manager.config.incrementPercent, manager.config.targetSpreadPercent, manager.config.gridLimits);
974
+ const allSlots = Array.from(workingGrid.values())
975
+ .filter(s => s.price != null)
976
+ .sort((a, b) => a.price - b.price);
977
+ const updatedSlots = assignGridRoles(allSlots, newBoundary, gapSlots, ORDER_TYPES, ORDER_STATES);
978
+ for (const slot of updatedSlots) {
979
+ workingGrid.set(slot.id, slot);
980
+ }
981
+ hasWorkingChanges = true;
982
+ }
983
+ // Return COW result only if there are changes
984
+ if (allActions.length === 0 && !hasWorkingChanges) {
985
+ return null;
986
+ }
987
+ return {
988
+ actions: allActions,
989
+ workingGrid,
990
+ workingIndexes: workingGrid.getIndexes(),
991
+ workingBoundary: newBoundary,
992
+ hasWorkingChanges,
993
+ aborted: false
994
+ };
995
+ }
996
+ /**
997
+ * Compare ideal grid vs persisted grid to detect divergence.
998
+ * INDEPENDENT SIDE CHECKING: Buy and sell sides are evaluated independently.
999
+ * Each side's RMS divergence is compared against its own threshold.
1000
+ * Only sides exceeding the threshold are marked for update.
1001
+ *
1002
+ * PURPOSE: Detect if the calculated in-memory grid has diverged significantly from the
1003
+ * persisted grid state. High divergence indicates that order fills/rotations have caused
1004
+ * size distributions to deviate, potentially requiring grid size recalculation.
1005
+ *
1006
+ * METRIC: RMS (Root Mean Square) percentage of relative size differences
1007
+ * Formula: RMS% = sqrt(mean((calculated - persisted) / persisted)²) × 100
1008
+ * This measures the typical relative error across all orders on each side.
1009
+ *
1010
+ * SIDE INDEPENDENCE:
1011
+ * - Buy side RMS is checked against GRID_COMPARISON.RMS_PERCENTAGE independently
1012
+ * - Sell side RMS is checked against GRID_COMPARISON.RMS_PERCENTAGE independently
1013
+ * - One side can diverge while the other remains stable (no update for stable side)
1014
+ *
1015
+ * RC-4: Atomic snapshot taking prevents stale data from concurrent fill operations
1016
+ * - Grids are snapshotted atomically before comparison
1017
+ * - Prevents mixing old and new grid state
1018
+ * - Ensures consistent RMS metrics across both sides
1019
+ *
1020
+ * @param {Array<import('./types').GridOrderSlot>} calculatedGrid - Ideal calculated grid
1021
+ * @param {Array<import('./types').GridOrderSlot>} persistedGrid - Persisted grid state
1022
+ * @param {import('./types').OrderManager|null} [manager=null] - Manager instance (for grid lock access)
1023
+ * @returns {Promise<import('./types').GridComparisonResult>}
1024
+ */
1025
+ static async compareGrids(calculatedGrid, persistedGrid, manager = null) {
1026
+ if (!Array.isArray(calculatedGrid) || !Array.isArray(persistedGrid)) {
1027
+ return { buy: { metric: 0, updated: false }, sell: { metric: 0, updated: false } };
1028
+ }
1029
+ // RC-4: Take snapshots atomically to prevent concurrent modification races
1030
+ // If manager has grid lock, use it to get consistent snapshots
1031
+ let calculatedSnap = calculatedGrid;
1032
+ let persistedSnap = persistedGrid;
1033
+ if (manager?._gridLock?.acquire) {
1034
+ const snapshotResult = await manager._gridLock.acquire(() => {
1035
+ return {
1036
+ calculated: Array.from(calculatedGrid),
1037
+ persisted: Array.from(persistedGrid)
1038
+ };
1039
+ });
1040
+ calculatedSnap = snapshotResult.calculated;
1041
+ persistedSnap = snapshotResult.persisted;
1042
+ }
1043
+ // Filter to ACTIVE orders only (excludes PARTIAL/VIRTUAL/SPREAD)
1044
+ // Partial orders are excluded from divergence calculation as they are expected to deviate;
1045
+ // they are instead handled by the available-funds ratio check or follow-up correction.
1046
+ // Must be sorted ASC for calculateRotationOrderSizes to match geometric weight distribution
1047
+ const filterForRms = (orders, type) => {
1048
+ const result = Array.isArray(orders) ? orders.filter(o => o && o.type === type && o.state === ORDER_STATES.ACTIVE) : [];
1049
+ return result
1050
+ .sort((a, b) => (a.price ?? 0) - (b.price ?? 0));
1051
+ };
1052
+ const calculatedBuys = filterForRms(calculatedSnap, ORDER_TYPES.BUY);
1053
+ const calculatedSells = filterForRms(calculatedSnap, ORDER_TYPES.SELL);
1054
+ const persistedBuys = filterForRms(persistedSnap, ORDER_TYPES.BUY);
1055
+ const persistedSells = filterForRms(persistedSnap, ORDER_TYPES.SELL);
1056
+ // Calculate ideal sizes for each order based on current available budget.
1057
+ // The sizing context (which includes recalculateFunds) is resolved once per side up front
1058
+ // so both buy and sell metrics share a single fund snapshot. This avoids the previous
1059
+ // double-recalculateFunds between the two sides and keeps the metric consistent even if
1060
+ // a fill event arrives between per-side calculations.
1061
+ const computeSideIdeals = (activeOrders, type, ctx) => {
1062
+ if (!manager || !ctx || ctx.budget <= 0 || activeOrders.length === 0)
1063
+ return activeOrders;
1064
+ // Identify ALL slots currently assigned to this side.
1065
+ // Ideal sizing must use the full slot count to determine geometric share per slot.
1066
+ const sideSlots = Array.from(manager.orders.values())
1067
+ .filter(o => o.type === type)
1068
+ .sort((a, b) => (a.price ?? 0) - (b.price ?? 0));
1069
+ if (sideSlots.length === 0)
1070
+ return activeOrders;
1071
+ // Calculate geometric ideals for the ENTIRE side (all slots)
1072
+ try {
1073
+ const allIdealSizes = calculateRotationOrderSizes(ctx.budget, 0, sideSlots.length, type, manager.config, 0, ctx.precision);
1074
+ // Map Ideal sizes to IDs for quick lookup
1075
+ const idealMap = new Map();
1076
+ sideSlots.forEach((slot, i) => idealMap.set(slot.id, allIdealSizes[i]));
1077
+ // Return the activeOrders subset with their true geometric ideal sizes
1078
+ return activeOrders.map(o => ({ ...o, size: idealMap.get(o.id) ?? 0 }));
1079
+ }
1080
+ catch (e) {
1081
+ return activeOrders;
1082
+ }
1083
+ };
1084
+ const needsBuy = calculatedBuys.length > 0 && manager?.assets;
1085
+ const needsSell = calculatedSells.length > 0 && manager?.assets;
1086
+ if (needsBuy || needsSell) {
1087
+ await manager.recalculateFunds();
1088
+ }
1089
+ const buyCtx = needsBuy
1090
+ ? await Grid._getSizingContext(manager, 'buy', { skipRecalc: true })
1091
+ : null;
1092
+ const sellCtx = needsSell
1093
+ ? await Grid._getSizingContext(manager, 'sell', { skipRecalc: true })
1094
+ : null;
1095
+ const buyIdeals = computeSideIdeals(calculatedBuys, ORDER_TYPES.BUY, buyCtx);
1096
+ const sellIdeals = computeSideIdeals(calculatedSells, ORDER_TYPES.SELL, sellCtx);
1097
+ // Calculate RMS divergence metric for each side
1098
+ const buyMetric = calculateGridSideDivergenceMetric(buyIdeals, persistedBuys, 'buy');
1099
+ const sellMetric = calculateGridSideDivergenceMetric(sellIdeals, persistedSells, 'sell');
1100
+ // Check if metrics exceed threshold and flag sides for regeneration
1101
+ // Set RMS_PERCENTAGE to 0 to disable RMS divergence checks
1102
+ let buyUpdated = false, sellUpdated = false;
1103
+ if (manager && (manager.config?.gridLimits?.GRID_COMPARISON?.RMS_PERCENTAGE ?? GRID_COMPARISON.RMS_PERCENTAGE) > 0) {
1104
+ const limit = (manager.config?.gridLimits?.GRID_COMPARISON?.RMS_PERCENTAGE ?? GRID_COMPARISON.RMS_PERCENTAGE) / GRID_CONSTANTS.RMS_PERCENTAGE_SCALE;
1105
+ if (buyMetric > limit) {
1106
+ // RC-3: Use Set for automatic duplicate prevention
1107
+ if (!(manager._gridSidesUpdated instanceof Set))
1108
+ manager._gridSidesUpdated = new Set();
1109
+ manager._gridSidesUpdated.add(ORDER_TYPES.BUY);
1110
+ buyUpdated = true;
1111
+ }
1112
+ if (sellMetric > limit) {
1113
+ // RC-3: Use Set for automatic duplicate prevention
1114
+ if (!(manager._gridSidesUpdated instanceof Set))
1115
+ manager._gridSidesUpdated = new Set();
1116
+ manager._gridSidesUpdated.add(ORDER_TYPES.SELL);
1117
+ sellUpdated = true;
1118
+ }
1119
+ }
1120
+ return {
1121
+ buy: { metric: buyMetric, updated: buyUpdated },
1122
+ sell: { metric: sellMetric, updated: sellUpdated },
1123
+ totalMetric: (buyMetric + sellMetric) / 2
1124
+ };
1125
+ }
1126
+ /**
1127
+ * Unified divergence monitoring.
1128
+ * Performs both Ratio-based and RMS-based divergence checks.
1129
+ *
1130
+ * @param {import('./types').OrderManager} manager - Manager instance
1131
+ * @param {Array<import('./types').GridOrderSlot>} calculatedGrid - Ideal/calculated grid
1132
+ * @param {Array<import('./types').GridOrderSlot>} persistedGrid - Current/persisted grid
1133
+ * @returns {Promise<import('./types').DivergenceResult>}
1134
+ */
1135
+ static async monitorDivergence(manager, calculatedGrid, persistedGrid) {
1136
+ // 1. Check ratio-based divergence (available funds vs allocated)
1137
+ const ratioResult = Grid.checkAndUpdateGridIfNeeded(manager);
1138
+ if (ratioResult.buyUpdated || ratioResult.sellUpdated) {
1139
+ const { getOrderTypeFromUpdatedFlags } = require('./utils/order');
1140
+ return {
1141
+ needsUpdate: true,
1142
+ buy: { updated: ratioResult.buyUpdated, ratio: ratioResult.buyUpdated, rms: false, metric: 0 },
1143
+ sell: { updated: ratioResult.sellUpdated, ratio: ratioResult.sellUpdated, rms: false, metric: 0 },
1144
+ orderType: getOrderTypeFromUpdatedFlags(ratioResult.buyUpdated, ratioResult.sellUpdated)
1145
+ };
1146
+ }
1147
+ // 2. Check RMS-based divergence (structural deviation)
1148
+ const rmsResult = await Grid.compareGrids(calculatedGrid, persistedGrid, manager);
1149
+ const buyUpdated = ratioResult.buyUpdated || rmsResult.buy.updated;
1150
+ const sellUpdated = ratioResult.sellUpdated || rmsResult.sell.updated;
1151
+ const { getOrderTypeFromUpdatedFlags } = require('./utils/order');
1152
+ return {
1153
+ needsUpdate: buyUpdated || sellUpdated,
1154
+ buy: { updated: buyUpdated, ratio: ratioResult.buyUpdated, rms: rmsResult.buy.updated, metric: rmsResult.buy.metric },
1155
+ sell: { updated: sellUpdated, ratio: ratioResult.sellUpdated, rms: rmsResult.sell.updated, metric: rmsResult.sell.metric },
1156
+ orderType: getOrderTypeFromUpdatedFlags(buyUpdated, sellUpdated)
1157
+ };
1158
+ }
1159
+ /**
1160
+ * Collect on-chain buy and sell orders from the manager.
1161
+ * Filters to orders with valid orderId and positive size.
1162
+ * @param {import('./types').OrderManager} manager - The manager instance.
1163
+ * @returns {{onChainBuys: Array<import('./types').Order>, onChainSells: Array<import('./types').Order>}}
1164
+ */
1165
+ static _getOnChainOrders(manager) {
1166
+ const onChainBuys = [
1167
+ ...manager.getOrdersByTypeAndState(ORDER_TYPES.BUY, ORDER_STATES.ACTIVE),
1168
+ ...manager.getOrdersByTypeAndState(ORDER_TYPES.BUY, ORDER_STATES.PARTIAL)
1169
+ ].filter(o => o?.orderId && Number(o?.size || 0) > 0);
1170
+ const onChainSells = [
1171
+ ...manager.getOrdersByTypeAndState(ORDER_TYPES.SELL, ORDER_STATES.ACTIVE),
1172
+ ...manager.getOrdersByTypeAndState(ORDER_TYPES.SELL, ORDER_STATES.PARTIAL)
1173
+ ].filter(o => o?.orderId && Number(o?.size || 0) > 0);
1174
+ return { onChainBuys, onChainSells };
1175
+ }
1176
+ /**
1177
+ * Calculate current market spread using on-chain orders.
1178
+ * @param {import('./types').OrderManager} manager - The manager instance.
1179
+ * @returns {number} The calculated spread percentage.
1180
+ */
1181
+ static calculateCurrentSpread(manager) {
1182
+ const { onChainBuys, onChainSells } = Grid._getOnChainOrders(manager);
1183
+ return calculateSpreadFromOrders(onChainBuys, onChainSells);
1184
+ }
1185
+ /**
1186
+ * Proactive spread correction check.
1187
+ *
1188
+ * CRITICAL: Uses AsyncLock to prevent race conditions with fill processing.
1189
+ * Without the lock, a TOCTOU (Time-Of-Check-To-Use) vulnerability exists where:
1190
+ * - Fund snapshot is taken (check phase)
1191
+ * - Fill processor modifies funds in another thread
1192
+ * - Order is placed based on stale funds (use phase)
1193
+ * Result: Orders placed beyond available liquidity, fund accounting errors
1194
+ *
1195
+ * DESIGN DECISION: Lock is released before blockchain operations for performance
1196
+ * - Lock held: Fund verification and correction decision (synchronized)
1197
+ * - Lock released: Blockchain submission (async, potentially slow)
1198
+ * - RACE CONDITION WINDOW: Between lock release and blockchain submission
1199
+ * - MITIGATION: Pre-flight fund verification before submission; comprehensive error handling
1200
+ *
1201
+ * See RACE_CONDITION_ANALYSIS.md for detailed vulnerability documentation.
1202
+ *
1203
+ * @param {import('./types').OrderManager} manager - Manager instance
1204
+ * @param {Object} BitShares - BitShares API client
1205
+ * @param {Function|null} [updateOrdersOnChainBatch=null] - Optional batch update function
1206
+ * @returns {Promise<import('./types').SpreadCheckResult>}
1207
+ */
1208
+ static async checkSpreadCondition(manager, BitShares, updateOrdersOnChainBatch = null) {
1209
+ // CRITICAL: Acquire corrections lock to serialize spread correction operations
1210
+ // This prevents concurrent fill processing from modifying funds while we're making decisions
1211
+ let correction = null;
1212
+ let shouldApplyCorrection = false;
1213
+ // Derive current market price from the bot's own grid (no blockchain call needed).
1214
+ // Grid prices are in B/A format (e.g. BTS/XRP) so no inversion is required.
1215
+ // Mid between best bid and best ask is the most current price the bot has.
1216
+ // Falls back to config.startPrice when either side is empty (e.g. at startup).
1217
+ const { onChainBuys, onChainSells } = Grid._getOnChainOrders(manager);
1218
+ const { bestBuy, bestSell } = getGridBestPrices(onChainBuys, onChainSells);
1219
+ const lastPrice = (bestBuy !== null && bestSell !== null)
1220
+ ? (bestBuy + bestSell) / 2
1221
+ : Number(manager.config.startPrice) || 0;
1222
+ // FIX: Use optional chaining for lock - if no lock exists, execute synchronously
1223
+ let fundSnapshot = null;
1224
+ const executeSpreadCheck = async () => {
1225
+ const currentSpread = Grid.calculateCurrentSpread(manager);
1226
+ // Nominal spread is the configured target spread percentage.
1227
+ // Keep this fixed: doubled-side flags are fill/replacement mechanics only.
1228
+ const nominalSpread = manager.config.targetSpreadPercent ?? DEFAULT_CONFIG.targetSpreadPercent;
1229
+ // Fixed tolerance: 0.5 steps = half increment (tighter spread check).
1230
+ const toleranceSteps = 0.5;
1231
+ const buyCount = manager.getOrdersByTypeAndState(ORDER_TYPES.BUY, ORDER_STATES.ACTIVE)
1232
+ .concat(manager.getOrdersByTypeAndState(ORDER_TYPES.BUY, ORDER_STATES.PARTIAL))
1233
+ .filter(o => o?.orderId && Number(o?.size || 0) > 0)
1234
+ .length;
1235
+ const sellCount = manager.getOrdersByTypeAndState(ORDER_TYPES.SELL, ORDER_STATES.ACTIVE)
1236
+ .concat(manager.getOrdersByTypeAndState(ORDER_TYPES.SELL, ORDER_STATES.PARTIAL))
1237
+ .filter(o => o?.orderId && Number(o?.size || 0) > 0)
1238
+ .length;
1239
+ manager.outOfSpread = shouldFlagOutOfSpread(currentSpread, nominalSpread, toleranceSteps, buyCount, sellCount, manager.config.incrementPercent);
1240
+ if (manager.outOfSpread === 0)
1241
+ return false;
1242
+ // Limit spread = nominal + half increment tolerance (0.5 steps).
1243
+ const limitSpread = nominalSpread + (manager.config.incrementPercent * toleranceSteps);
1244
+ manager.logger?.log?.(`Spread too wide (${Format.formatPercent(currentSpread)} > ${Format.formatPercent(limitSpread)}), correcting with ${manager.outOfSpread} extra slot(s)...`, 'warn');
1245
+ const decision = Grid.determineOrderSideByFunds(manager, lastPrice);
1246
+ if (!decision.side)
1247
+ return false;
1248
+ // Perform spread correction by placing orders on the chosen side.
1249
+ correction = await Grid.prepareSpreadCorrectionOrders(manager, decision.side);
1250
+ if (!correction)
1251
+ return false;
1252
+ const placeCount = correction.ordersToPlace?.length || 0;
1253
+ const updateCount = correction.ordersToUpdate?.length || 0;
1254
+ // Capture fund snapshot under lock for pre-flight verification before broadcast
1255
+ fundSnapshot = _snapshotFundState(manager);
1256
+ return (placeCount + updateCount) > 0;
1257
+ };
1258
+ try {
1259
+ shouldApplyCorrection = await manager._gridLock.acquire(executeSpreadCheck);
1260
+ }
1261
+ catch (err) {
1262
+ manager.logger?.log?.(`Error checking spread condition: ${err.message}`, 'warn');
1263
+ return { ordersPlaced: 0, partialsMoved: 0 };
1264
+ }
1265
+ // FIX: Apply blockchain operations OUTSIDE the lock to reduce lock contention
1266
+ // The lock is only needed for fund verification; order placement doesn't need it
1267
+ // Pre-flight fund verification to mitigate TOCTOU between lock release and broadcast
1268
+ if (shouldApplyCorrection && updateOrdersOnChainBatch && correction && fundSnapshot) {
1269
+ const currentFunds = _snapshotFundState(manager);
1270
+ const fundChanged = fundSnapshot.buyFree !== currentFunds.buyFree
1271
+ || fundSnapshot.sellFree !== currentFunds.sellFree
1272
+ || fundSnapshot.buyLocked !== currentFunds.buyLocked
1273
+ || fundSnapshot.sellLocked !== currentFunds.sellLocked;
1274
+ if (fundChanged) {
1275
+ manager.logger?.log?.(`Spread correction aborted: fund state changed between lock release and broadcast (pre-flight check)`, 'warn');
1276
+ return { ordersPlaced: 0, partialsMoved: 0 };
1277
+ }
1278
+ try {
1279
+ const batchResult = await updateOrdersOnChainBatch(correction);
1280
+ if (!batchResult || batchResult.executed !== true) {
1281
+ manager.logger?.log?.(`Spread correction batch was prepared but not executed. Keeping local state unchanged.`, 'warn');
1282
+ return { ordersPlaced: 0, partialsMoved: 0 };
1283
+ }
1284
+ await manager.recalculateFunds();
1285
+ const placed = correction.ordersToPlace?.length || 0;
1286
+ const updated = correction.ordersToUpdate?.length || 0;
1287
+ return { ordersPlaced: placed + updated, partialsMoved: updated };
1288
+ }
1289
+ catch (err) {
1290
+ manager.logger?.log?.(`Error applying spread correction on-chain: ${err.message}`, 'warn');
1291
+ return { ordersPlaced: 0, partialsMoved: 0 };
1292
+ }
1293
+ }
1294
+ return { ordersPlaced: 0, partialsMoved: 0 };
1295
+ }
1296
+ /**
1297
+ * Grid health check for structural violations.
1298
+ * Monitors for "Dust Partials" that are too small to be traded on-chain,
1299
+ * scoped to the active buy/sell window.
1300
+ *
1301
+ * NOTE: Internal gaps (virtual slots between active ones) are no longer
1302
+ * flagged as violations. The "Edge-First" placement strategy intentionally
1303
+ * creates these gaps to maximize grid coverage during fund expansion.
1304
+ *
1305
+ * @param {import('./types').OrderManager} manager - The manager instance.
1306
+ * @param {Function|null} [updateOrdersOnChainBatch=null] - Optional batch update function.
1307
+ * @returns {Promise<import('./types').DustCheckResult>}
1308
+ */
1309
+ static async checkGridHealth(manager, updateOrdersOnChainBatch = null) {
1310
+ if (!manager)
1311
+ return { buyDust: false, sellDust: false, buyDustOrders: [], sellDustOrders: [] };
1312
+ // Skip health checks during bootstrap to prevent spamming warnings
1313
+ if (manager._state.isBootstrapping())
1314
+ return { buyDust: false, sellDust: false, buyDustOrders: [], sellDustOrders: [] };
1315
+ // Health checks are scoped to the active on-chain window only.
1316
+ // This keeps detection aligned with maintenance actions that operate on
1317
+ // active window partials.
1318
+ const { buyDust, sellDust, buyDustOrders, sellDustOrders } = await Grid.checkWindowDust(manager);
1319
+ // Partial split/merge maintenance is intentionally disabled.
1320
+ // Health checks remain detection-only.
1321
+ return { buyDust, sellDust, buyDustOrders, sellDustOrders };
1322
+ }
1323
+ /**
1324
+ * Dust check covering all partial orders, with interior-only guard.
1325
+ *
1326
+ * The top-of-window partial (closest to market) is always eligible for dust
1327
+ * detection since cancelling it is just the grid edge moving inward.
1328
+ *
1329
+ * Interior partials (further from market) are only eligible if they have a
1330
+ * duplicate price level — another active order at essentially the same price.
1331
+ * Cancelling such an interior partial won't leave a gap in the grid because
1332
+ * the sibling active order already covers that price level.
1333
+ *
1334
+ * Returns boolean flags plus the actual dust order objects so callers can act
1335
+ * on individual orders (e.g. DUST_CANCEL_DELAY_SEC auto-cancel).
1336
+ *
1337
+ * @param {import('./types').OrderManager} manager
1338
+ * @returns {Promise<import('./types').DustCheckResult>}
1339
+ */
1340
+ static async checkWindowDust(manager) {
1341
+ if (!manager)
1342
+ return { buyDust: false, sellDust: false, buyDustOrders: [], sellDustOrders: [] };
1343
+ const allOrders = Array.from(manager.orders.values());
1344
+ const isLiveOrder = order => order &&
1345
+ order.orderId &&
1346
+ order.price != null &&
1347
+ (order.state === ORDER_STATES.ACTIVE || order.state === ORDER_STATES.PARTIAL);
1348
+ // Identify top-of-window orders (closest to market per side).
1349
+ const topBuyOrder = allOrders
1350
+ .filter(o => o.type === ORDER_TYPES.BUY && isLiveOrder(o))
1351
+ .sort((a, b) => b.price - a.price)[0];
1352
+ const topSellOrder = allOrders
1353
+ .filter(o => o.type === ORDER_TYPES.SELL && isLiveOrder(o))
1354
+ .sort((a, b) => a.price - b.price)[0];
1355
+ // Check if an order has a duplicate price level — an active sibling at the
1356
+ // same price within tolerance. If so, cancelling won't create a grid gap.
1357
+ // Only checks ACTIVE siblings. If two PARTIALs share a price with no active
1358
+ // sibling, neither qualifies and the gap is left to the rebalancer.
1359
+ // Uses the LARGER size of the two orders for tolerance calculation to prevent
1360
+ // a tiny dust order from inflating the tolerance window.
1361
+ const hasDuplicatePriceLevel = (order, assets) => allOrders.some(o => {
1362
+ if (o.id === order.id || o.type !== order.type)
1363
+ return false;
1364
+ if (o.state !== ORDER_STATES.ACTIVE || !o.orderId || o.price == null)
1365
+ return false;
1366
+ const toleranceSize = Math.max(order.size, o.size);
1367
+ const tolerance = calculatePriceTolerance(Math.min(order.price, o.price), toleranceSize, order.type, assets);
1368
+ return tolerance != null && Math.abs(o.price - order.price) <= tolerance;
1369
+ });
1370
+ const assets = manager.assets;
1371
+ const allPartials = allOrders.filter((o) => isLiveOrder(o) && o.state === ORDER_STATES.PARTIAL);
1372
+ const isTopBuy = (o) => topBuyOrder && o.id === topBuyOrder.id;
1373
+ const isTopSell = (o) => topSellOrder && o.id === topSellOrder.id;
1374
+ // Safety filter: top-of-window partials always qualify; interior partials
1375
+ // only qualify if they have a duplicate price level (no gap risk).
1376
+ const eligibleBuyPartials = allPartials.filter((o) => o.type === ORDER_TYPES.BUY && (isTopBuy(o) || hasDuplicatePriceLevel(o, assets)));
1377
+ const eligibleSellPartials = allPartials.filter((o) => o.type === ORDER_TYPES.SELL && (isTopSell(o) || hasDuplicatePriceLevel(o, assets)));
1378
+ const buyDustOrders = await Grid._getDustOrders(manager, eligibleBuyPartials, ORDER_TYPES.BUY);
1379
+ const sellDustOrders = await Grid._getDustOrders(manager, eligibleSellPartials, ORDER_TYPES.SELL);
1380
+ return {
1381
+ buyDust: buyDustOrders.length > 0,
1382
+ sellDust: sellDustOrders.length > 0,
1383
+ buyDustOrders,
1384
+ sellDustOrders,
1385
+ };
1386
+ }
1387
+ /**
1388
+ * Return the subset of partial orders that qualify as dust on a given side.
1389
+ * Shares the same sizing context as _hasAnyDust but returns the actual order
1390
+ * objects so callers can act on them (e.g. auto-cancel).
1391
+ * @private
1392
+ * @param {import('./types').OrderManager} manager
1393
+ * @param {Array<import('./types').GridOrderSlot>} partials - Candidate partial orders to test.
1394
+ * @param {string} type - ORDER_TYPES.BUY or ORDER_TYPES.SELL
1395
+ * @returns {Promise<Array<import('./types').GridOrderSlot>>} Orders whose size is below the dust threshold.
1396
+ */
1397
+ static async _getDustOrders(manager, partials, type) {
1398
+ if (!partials || partials.length === 0)
1399
+ return [];
1400
+ const side = type === ORDER_TYPES.BUY ? 'buy' : 'sell';
1401
+ const ctx = await Grid._getSizingContext(manager, side);
1402
+ if (!ctx || ctx.budget <= 0)
1403
+ return [];
1404
+ const sideSlots = Array.from(manager.orders.values())
1405
+ .filter(o => o.type === type)
1406
+ .sort((a, b) => a.price - b.price);
1407
+ if (sideSlots.length === 0)
1408
+ return [];
1409
+ const idealSizes = allocateFundsByWeights(ctx.budget, sideSlots.length, manager.config.weightDistribution[side], manager.config.incrementPercent / 100, type === ORDER_TYPES.BUY, 0, ctx.precision);
1410
+ return partials.filter(p => {
1411
+ const idx = sideSlots.findIndex(s => s.id === p.id);
1412
+ if (idx === -1)
1413
+ return false;
1414
+ const threshold = getSingleDustThreshold(idealSizes[idx], manager.config?.gridLimits?.PARTIAL_DUST_THRESHOLD_PERCENTAGE);
1415
+ return p.size < threshold;
1416
+ });
1417
+ }
1418
+ /**
1419
+ * Check if any partial orders on a side represent "dust" that should be cleaned.
1420
+ * @param {import('./types').OrderManager} manager - Manager instance
1421
+ * @param {Array<import('./types').GridOrderSlot>} partials - Partial orders to check
1422
+ * @param {string} type - ORDER_TYPES.BUY or ORDER_TYPES.SELL
1423
+ * @returns {Promise<boolean>} true if dust partials exist
1424
+ * @private
1425
+ */
1426
+ static async _hasAnyDust(manager, partials, type) {
1427
+ return (await Grid._getDustOrders(manager, partials, type)).length > 0;
1428
+ }
1429
+ /**
1430
+ * Public dust helper shared by StrategyEngine and Grid health checks.
1431
+ * @param {import('./types').OrderManager} manager
1432
+ * @param {Array<import('./types').GridOrderSlot>} partials
1433
+ * @param {'buy'|'sell'} side
1434
+ * @returns {Promise<boolean>}
1435
+ */
1436
+ static async hasAnyDust(manager, partials, side) {
1437
+ const type = side === 'buy' ? ORDER_TYPES.BUY : side === 'sell' ? ORDER_TYPES.SELL : null;
1438
+ if (!type)
1439
+ return false;
1440
+ return await Grid._hasAnyDust(manager, partials, type);
1441
+ }
1442
+ /**
1443
+ * Public dust helper that returns the subset of candidate partials currently below
1444
+ * the configured dust threshold for the requested side.
1445
+ * @param {import('./types').OrderManager} manager
1446
+ * @param {Array<import('./types').GridOrderSlot>} partials
1447
+ * @param {'buy'|'sell'} side
1448
+ * @returns {Promise<Array<import('./types').GridOrderSlot>>}
1449
+ */
1450
+ static async getDustOrders(manager, partials, side) {
1451
+ const type = side === 'buy' ? ORDER_TYPES.BUY : side === 'sell' ? ORDER_TYPES.SELL : null;
1452
+ if (!type)
1453
+ return [];
1454
+ return await Grid._getDustOrders(manager, partials, type);
1455
+ }
1456
+ /**
1457
+ * Determine which side has more available funds for spread correction.
1458
+ * @param {import('./types').OrderManager} manager - The manager instance.
1459
+ * @param {number} currentMarketPrice - Last traded price in B/A format (e.g. BTS/XRP), used to
1460
+ * normalize sell-side funds into buy-side units for a fair cross-asset comparison.
1461
+ * @returns {{ side: import('./types').OrderType|null, reason: string }} The side to correct on, or null if insufficient funds.
1462
+ */
1463
+ static determineOrderSideByFunds(manager, currentMarketPrice) {
1464
+ const buyAvailable = Math.min(Number(manager.funds?.available?.buy || 0), Number(manager.accountTotals?.buyFree || 0));
1465
+ const sellAvailable = Math.min(Number(manager.funds?.available?.sell || 0), Number(manager.accountTotals?.sellFree || 0));
1466
+ // Need at least some funds on a side to justify correction
1467
+ const buyPrecision = manager.assets?.assetB?.precision;
1468
+ const sellPrecision = manager.assets?.assetA?.precision;
1469
+ if (buyPrecision === undefined || sellPrecision === undefined) {
1470
+ throw new Error(`CRITICAL: Asset precision unavailable for grid correction check`);
1471
+ }
1472
+ const buyMinUnit = 1 / Math.pow(10, buyPrecision);
1473
+ const sellMinUnit = 1 / Math.pow(10, sellPrecision);
1474
+ const buyViable = buyAvailable > buyMinUnit;
1475
+ const sellViable = sellAvailable > sellMinUnit;
1476
+ let side = null;
1477
+ if (buyViable && sellViable) {
1478
+ // Normalize sell (assetA) to assetB units using market price so both sides
1479
+ // are comparable. Without this, a raw number comparison (e.g. 2192 BTS vs
1480
+ // 0.12 XRP) always picks BUY even when the sell side is larger in value.
1481
+ const marketPrice = Number(currentMarketPrice);
1482
+ const sellInBuyUnits = (Number.isFinite(marketPrice) && marketPrice > 0)
1483
+ ? sellAvailable * marketPrice
1484
+ : sellAvailable;
1485
+ side = buyAvailable >= sellInBuyUnits ? ORDER_TYPES.BUY : ORDER_TYPES.SELL;
1486
+ }
1487
+ else if (buyViable) {
1488
+ side = ORDER_TYPES.BUY;
1489
+ }
1490
+ else if (sellViable) {
1491
+ side = ORDER_TYPES.SELL;
1492
+ }
1493
+ if (!side) {
1494
+ const committedBuy = Math.max(0, Number(manager.funds?.committed?.chain?.buy || 0));
1495
+ const committedSell = Math.max(0, Number(manager.funds?.committed?.chain?.sell || 0));
1496
+ const marketPrice = Number(currentMarketPrice);
1497
+ const hasValidPrice = Number.isFinite(marketPrice) && marketPrice > 0;
1498
+ if (committedBuy > buyMinUnit || committedSell > sellMinUnit) {
1499
+ if (hasValidPrice) {
1500
+ const buyComparable = committedBuy;
1501
+ const sellComparable = committedSell * marketPrice;
1502
+ side = buyComparable >= sellComparable ? ORDER_TYPES.BUY : ORDER_TYPES.SELL;
1503
+ }
1504
+ else if (committedBuy > buyMinUnit && committedSell <= sellMinUnit) {
1505
+ side = ORDER_TYPES.BUY;
1506
+ }
1507
+ else if (committedSell > sellMinUnit && committedBuy <= buyMinUnit) {
1508
+ side = ORDER_TYPES.SELL;
1509
+ }
1510
+ else {
1511
+ // Deterministic fallback when both sides hold inventory but market valuation is unavailable.
1512
+ side = ORDER_TYPES.BUY;
1513
+ }
1514
+ manager.logger?.log?.(`Spread correction using redistribution fallback on ${side} ` +
1515
+ `(free buy=${Format.formatAmount8(buyAvailable)}, free sell=${Format.formatAmount8(sellAvailable)}, ` +
1516
+ `price=${hasValidPrice ? Format.formatAmount8(marketPrice) : 'unavailable'})`, 'info');
1517
+ }
1518
+ }
1519
+ if (!side) {
1520
+ manager.logger?.log?.(`Spread correction skipped: insufficient free funds and no committed inventory to redistribute ` +
1521
+ `(buy=${Format.formatAmount8(buyAvailable)}, sell=${Format.formatAmount8(sellAvailable)})`, 'warn');
1522
+ }
1523
+ return { side, reason: side ? `Choosing ${side}` : 'Insufficient funds or committed inventory' };
1524
+ }
1525
+ /**
1526
+ * Calculate the geometric ideal size for a new order being placed during spread correction.
1527
+ * @param {import('./types').OrderManager} manager - The manager instance.
1528
+ * @param {import('./types').OrderType} targetType - The type of order being placed (ORDER_TYPES.BUY or ORDER_TYPES.SELL).
1529
+ * @returns {Promise<number|null>} The calculated geometric size.
1530
+ */
1531
+ static async calculateGeometricSizeForSpreadCorrection(manager, targetType) {
1532
+ const side = targetType === ORDER_TYPES.BUY ? 'buy' : 'sell';
1533
+ const slotsCount = Array.from(manager.orders.values()).filter(o => o.type === targetType).length + 1;
1534
+ // Use centralized sizing context (respects botFunds % allocation)
1535
+ const ctx = await Grid._getSizingContext(manager, side);
1536
+ if (!ctx || ctx.budget <= 0 || slotsCount < 1)
1537
+ return null;
1538
+ // ALLOW slotsCount === 1 to enable spread correction even if a side is completely missing
1539
+ const dummy = Array.from({ length: slotsCount }, () => ({ type: targetType }));
1540
+ try {
1541
+ const sized = calculateOrderSizes(dummy, manager.config, side === 'sell' ? ctx.budget : 0, side === 'buy' ? ctx.budget : 0, 0, 0, ctx.precision, ctx.precision);
1542
+ if (!Array.isArray(sized) || sized.length === 0) {
1543
+ manager.logger?.log?.(`calculateOrderSizes returned invalid result for spread correction`, 'warn');
1544
+ return null;
1545
+ }
1546
+ return side === 'sell' ? sized[0].size : sized[sized.length - 1].size;
1547
+ }
1548
+ catch (e) {
1549
+ manager.logger?.log?.(`Error calculating geometric size for spread correction: ${e.message}`, 'warn');
1550
+ return null;
1551
+ }
1552
+ }
1553
+ /**
1554
+ * Prepares one or more orders to correct a wide spread.
1555
+ * @param {import('./types').OrderManager} manager - The OrderManager instance.
1556
+ * @param {string} preferredSide - The side to place the correction on (ORDER_TYPES.BUY/SELL).
1557
+ * @returns {Promise<import('./types').SpreadCorrectionResult>}
1558
+ * @throws {Error} If preferredSide is invalid.
1559
+ */
1560
+ static async prepareSpreadCorrectionOrders(manager, preferredSide) {
1561
+ // FIX: Validate preferredSide parameter to prevent silent logic errors
1562
+ if (preferredSide !== ORDER_TYPES.BUY && preferredSide !== ORDER_TYPES.SELL) {
1563
+ throw new Error(`Invalid preferredSide: ${preferredSide}. Must be '${ORDER_TYPES.BUY}' or '${ORDER_TYPES.SELL}'.`);
1564
+ }
1565
+ const ordersToPlace = [];
1566
+ const ordersToUpdate = [];
1567
+ const railType = preferredSide;
1568
+ const sideName = railType === ORDER_TYPES.BUY ? 'buy' : 'sell';
1569
+ const configuredMissingSlots = Number(manager.outOfSpread || 0);
1570
+ const missingSlots = configuredMissingSlots > 0
1571
+ ? Math.floor(configuredMissingSlots)
1572
+ : 1;
1573
+ // STRATEGY: Edge-Based Correction (Safe Bridging)
1574
+ // Instead of calculating a "mid-price" (which can be dangerous in wide gaps),
1575
+ // we strictly target the orders closest to the spread gap.
1576
+ // 1. Priority: Update existing PARTIAL orders at the edge (Highest Buy / Lowest Sell).
1577
+ // 2. Fallback: Activate SPREAD slots at the edge (Lowest Spread for Buy / Highest Spread for Sell).
1578
+ const allOrders = Array.from(manager.orders.values());
1579
+ let edgePartial = null;
1580
+ const partials = allOrders
1581
+ .filter(o => o.type === railType && o.state === ORDER_STATES.PARTIAL)
1582
+ .sort((a, b) => railType === ORDER_TYPES.BUY ? b.price - a.price : a.price - b.price);
1583
+ if (partials.length > 0) {
1584
+ edgePartial = partials[0];
1585
+ manager.logger?.log?.(`[SPREAD-CORRECTION] Identified partial order at ${edgePartial.price} for update`, 'debug');
1586
+ }
1587
+ // Primary candidates: SPREAD-type slots adjacent to the gap.
1588
+ const typedSpreadCandidates = allOrders
1589
+ .filter(o => o.type === ORDER_TYPES.SPREAD && isSlotAvailable(o))
1590
+ .sort((a, b) => railType === ORDER_TYPES.BUY ? a.price - b.price : b.price - a.price)
1591
+ .slice(0, missingSlots);
1592
+ // Secondary candidates: orphaned virtual slots of the correct side-type that have
1593
+ // lost their order (e.g. stale-cleaned after a race condition during a crash).
1594
+ // These sit inside the active window and are invisible to the SPREAD-type filter above.
1595
+ const orphanedVirtualCandidates = allOrders
1596
+ .filter(o => o.type === railType && o.state === ORDER_STATES.VIRTUAL && !o.orderId && Number(o.size || 0) === 0)
1597
+ .sort((a, b) => railType === ORDER_TYPES.BUY ? b.price - a.price : a.price - b.price)
1598
+ .slice(0, missingSlots);
1599
+ // Merge: prefer orphaned virtuals (they already occupy correct grid positions) then
1600
+ // fall back to SPREAD slots for any remaining quota.
1601
+ const remainingQuota = Math.max(0, missingSlots - orphanedVirtualCandidates.length);
1602
+ const spreadCandidates = [
1603
+ ...orphanedVirtualCandidates,
1604
+ ...typedSpreadCandidates.slice(0, remainingQuota)
1605
+ ];
1606
+ if (spreadCandidates.length > 0) {
1607
+ manager.logger?.log?.(`[SPREAD-CORRECTION] Identified ${spreadCandidates.length}/${missingSlots} slot(s) for activation on ${sideName} (orphaned=${orphanedVirtualCandidates.length}, spread=${spreadCandidates.length - orphanedVirtualCandidates.length})`, 'debug');
1608
+ }
1609
+ if (!edgePartial && spreadCandidates.length === 0) {
1610
+ manager.logger?.log?.(`[SPREAD-CORRECTION] No suitable partials, orphaned virtual slots, or spread slots found. Skipping.`, 'warn');
1611
+ return { ordersToPlace: [], ordersToUpdate: [] };
1612
+ }
1613
+ const orphanedIds = new Set(orphanedVirtualCandidates.map(o => o.id));
1614
+ const sideSlots = allOrders
1615
+ .filter(o => o.type === railType && !orphanedIds.has(o.id))
1616
+ .sort((a, b) => a.price - b.price);
1617
+ const syntheticSideSlots = [
1618
+ ...sideSlots,
1619
+ ...spreadCandidates.map(slot => ({ ...slot, type: railType }))
1620
+ ].sort((a, b) => a.price - b.price);
1621
+ const ctx = await Grid._getSizingContext(manager, sideName);
1622
+ if (!ctx || ctx.budget <= 0 || syntheticSideSlots.length === 0) {
1623
+ return { ordersToPlace: [], ordersToUpdate: [] };
1624
+ }
1625
+ const precisionEpsilon = getPrecisionSlack(ctx.precision, 1);
1626
+ const idealSizes = allocateFundsByWeights(ctx.budget, syntheticSideSlots.length, manager.config.weightDistribution[sideName], manager.config.incrementPercent / 100, railType === ORDER_TYPES.BUY, 0, ctx.precision);
1627
+ const idealById = new Map();
1628
+ syntheticSideSlots.forEach((slot, idx) => {
1629
+ idealById.set(slot.id, Number(idealSizes[idx] || 0));
1630
+ });
1631
+ const availableFund = Math.max(0, Math.min(Number(manager.funds?.available?.[sideName] || 0), Number(sideName === 'buy' ? manager.accountTotals?.buyFree : manager.accountTotals?.sellFree) || 0));
1632
+ const minAbsoluteSize = getMinAbsoluteOrderSize(railType, manager.assets);
1633
+ const prioritizedTargets = [];
1634
+ if (edgePartial && edgePartial.id) {
1635
+ const ideal = Number(idealById.get(edgePartial.id) || 0);
1636
+ const current = Number(edgePartial.size || 0);
1637
+ if (ideal > current + precisionEpsilon) {
1638
+ prioritizedTargets.push({
1639
+ kind: 'partial-topup',
1640
+ order: edgePartial,
1641
+ current,
1642
+ ideal,
1643
+ needed: Math.max(0, ideal - current)
1644
+ });
1645
+ }
1646
+ }
1647
+ for (const slot of spreadCandidates) {
1648
+ const ideal = Number(idealById.get(slot.id) || 0);
1649
+ if (ideal > precisionEpsilon) {
1650
+ prioritizedTargets.push({
1651
+ kind: 'create',
1652
+ order: slot,
1653
+ current: 0,
1654
+ ideal,
1655
+ needed: ideal
1656
+ });
1657
+ }
1658
+ }
1659
+ if (prioritizedTargets.length === 0) {
1660
+ return { ordersToPlace: [], ordersToUpdate: [] };
1661
+ }
1662
+ const totalNeeded = prioritizedTargets.reduce((sum, t) => sum + Math.max(0, Number(t.needed || 0)), 0);
1663
+ let recoveredBudget = 0;
1664
+ const redistributionUpdates = [];
1665
+ if (totalNeeded > availableFund + precisionEpsilon) {
1666
+ let shortfall = totalNeeded - availableFund;
1667
+ const donors = sideSlots
1668
+ .filter(o => hasOnChainId(o) && (o.state === ORDER_STATES.ACTIVE || o.state === ORDER_STATES.PARTIAL))
1669
+ .filter(o => !edgePartial || o.id !== edgePartial.id)
1670
+ .sort((a, b) => railType === ORDER_TYPES.BUY ? a.price - b.price : b.price - a.price);
1671
+ for (const donor of donors) {
1672
+ if (shortfall <= precisionEpsilon)
1673
+ break;
1674
+ const donorCurrent = Number(donor.size || 0);
1675
+ const donorIdeal = Number(idealById.get(donor.id) || 0);
1676
+ const donorFloor = Math.max(minAbsoluteSize, donorIdeal);
1677
+ const donorReducible = Math.max(0, donorCurrent - donorFloor);
1678
+ if (donorReducible <= precisionEpsilon)
1679
+ continue;
1680
+ const reduction = Math.min(donorReducible, shortfall);
1681
+ const donorNext = donorCurrent - reduction;
1682
+ if (donorNext <= precisionEpsilon)
1683
+ continue;
1684
+ if (!isOrderHealthy(donorNext, railType, manager.assets, donorIdeal || donorNext))
1685
+ continue;
1686
+ redistributionUpdates.push({ partialOrder: { ...donor }, newSize: donorNext });
1687
+ recoveredBudget += reduction;
1688
+ shortfall -= reduction;
1689
+ }
1690
+ if (recoveredBudget > precisionEpsilon) {
1691
+ manager.logger?.log?.(`[SPREAD-CORRECTION] Recovered ${Format.formatSizeByOrderType(recoveredBudget, railType, manager.assets)} on ${sideName} via redistribution`, 'info');
1692
+ }
1693
+ }
1694
+ let remainingBudget = availableFund + recoveredBudget;
1695
+ for (const target of prioritizedTargets) {
1696
+ if (remainingBudget <= precisionEpsilon)
1697
+ break;
1698
+ if (target.kind === 'partial-topup') {
1699
+ const topUp = Math.min(target.needed, remainingBudget);
1700
+ const newSize = target.current + topUp;
1701
+ if (newSize > target.current + precisionEpsilon && isOrderHealthy(newSize, railType, manager.assets, target.ideal)) {
1702
+ ordersToUpdate.push({ partialOrder: { ...target.order }, newSize });
1703
+ remainingBudget -= topUp;
1704
+ }
1705
+ continue;
1706
+ }
1707
+ const createSize = Math.min(target.ideal, remainingBudget);
1708
+ if (createSize <= precisionEpsilon)
1709
+ continue;
1710
+ if (!isOrderHealthy(createSize, railType, manager.assets, target.ideal))
1711
+ continue;
1712
+ ordersToPlace.push({
1713
+ ...target.order,
1714
+ type: railType,
1715
+ size: createSize,
1716
+ state: ORDER_STATES.VIRTUAL
1717
+ });
1718
+ remainingBudget -= createSize;
1719
+ }
1720
+ const combinedUpdates = [...redistributionUpdates];
1721
+ for (const plannedUpdate of ordersToUpdate) {
1722
+ const id = plannedUpdate?.partialOrder?.id || plannedUpdate?.id;
1723
+ if (!id)
1724
+ continue;
1725
+ const existingIdx = combinedUpdates.findIndex(u => (u?.partialOrder?.id || u?.id) === id);
1726
+ if (existingIdx >= 0) {
1727
+ combinedUpdates[existingIdx] = plannedUpdate;
1728
+ }
1729
+ else {
1730
+ combinedUpdates.push(plannedUpdate);
1731
+ }
1732
+ }
1733
+ if (spreadCandidates.length < missingSlots) {
1734
+ manager.logger?.log?.(`[SPREAD-CORRECTION] Requested ${missingSlots} extra slot(s), found ${spreadCandidates.length} available slot(s) on ${sideName}`, 'warn');
1735
+ }
1736
+ if (ordersToPlace.length < spreadCandidates.length) {
1737
+ manager.logger?.log?.(`[SPREAD-CORRECTION] Fund-constrained placement on ${sideName}: planned ${spreadCandidates.length}, placing ${ordersToPlace.length}`, 'info');
1738
+ }
1739
+ if (combinedUpdates.length > 0 || ordersToPlace.length > 0) {
1740
+ manager.logger?.log?.(`[SPREAD-CORRECTION] Prepared updates=${combinedUpdates.length}, creates=${ordersToPlace.length}, remainingBudget=${Format.formatSizeByOrderType(Math.max(0, remainingBudget), railType, manager.assets)}`, 'debug');
1741
+ }
1742
+ return { ordersToPlace, ordersToUpdate: combinedUpdates };
1743
+ }
1744
+ }
1745
+ module.exports = Grid;
1746
+ //# sourceMappingURL=grid.js.map