@reefclaw/connect 0.1.12 → 0.1.13

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 (390) hide show
  1. package/assets/plugin/audit/mode-transition-audit.d.ts +11 -0
  2. package/assets/plugin/audit/mode-transition-audit.js +29 -0
  3. package/assets/plugin/balance-utils.d.ts +36 -0
  4. package/assets/plugin/balance-utils.js +98 -0
  5. package/assets/plugin/ccxt/binance-ban-gate.d.ts +47 -0
  6. package/assets/plugin/ccxt/binance-ban-gate.js +420 -0
  7. package/assets/plugin/ccxt/binance-private.d.ts +346 -0
  8. package/assets/plugin/ccxt/binance-private.js +1525 -0
  9. package/assets/plugin/ccxt/binance-public.d.ts +34 -0
  10. package/assets/plugin/ccxt/binance-public.js +180 -0
  11. package/assets/plugin/ccxt/intel-public.d.ts +25 -0
  12. package/assets/plugin/ccxt/intel-public.js +80 -0
  13. package/assets/plugin/ccxt/public-market-data-api.d.ts +26 -0
  14. package/assets/plugin/ccxt/public-market-data-api.js +23 -0
  15. package/assets/plugin/config/agent-config-client.d.ts +77 -0
  16. package/assets/plugin/config/agent-config-client.js +188 -0
  17. package/assets/plugin/config/agent-config-poller.d.ts +32 -0
  18. package/assets/plugin/config/agent-config-poller.js +101 -0
  19. package/assets/plugin/config/brackets-config.d.ts +22 -0
  20. package/assets/plugin/config/brackets-config.js +58 -0
  21. package/assets/plugin/config/entitlement-gate.d.ts +51 -0
  22. package/assets/plugin/config/entitlement-gate.js +137 -0
  23. package/assets/plugin/config/gate-store.d.ts +18 -0
  24. package/assets/plugin/config/gate-store.js +61 -0
  25. package/assets/plugin/config/plugin-config-io.d.ts +222 -0
  26. package/assets/plugin/config/plugin-config-io.js +126 -0
  27. package/assets/plugin/config/position-review-config.d.ts +35 -0
  28. package/assets/plugin/config/position-review-config.js +105 -0
  29. package/assets/plugin/config/tool-gate.d.ts +53 -0
  30. package/assets/plugin/config/tool-gate.js +128 -0
  31. package/assets/plugin/config/user-data-stream-config.d.ts +89 -0
  32. package/assets/plugin/config/user-data-stream-config.js +239 -0
  33. package/assets/plugin/connector-supervisor.d.ts +36 -0
  34. package/assets/plugin/connector-supervisor.js +149 -0
  35. package/assets/plugin/exchange-adapter.d.ts +65 -0
  36. package/assets/plugin/exchange-adapter.js +4 -0
  37. package/assets/plugin/index.d.ts +30 -0
  38. package/assets/plugin/index.js +2763 -0
  39. package/assets/plugin/ingest/pending-entry-metadata.d.ts +74 -0
  40. package/assets/plugin/ingest/pending-entry-metadata.js +236 -0
  41. package/assets/plugin/ingest/position-auto-capture.d.ts +171 -0
  42. package/assets/plugin/ingest/position-auto-capture.js +709 -0
  43. package/assets/plugin/ingest/position-decisions-client.d.ts +327 -0
  44. package/assets/plugin/ingest/position-decisions-client.js +306 -0
  45. package/assets/plugin/ingest/readiness-reporter.d.ts +42 -0
  46. package/assets/plugin/ingest/readiness-reporter.js +163 -0
  47. package/assets/plugin/ingest/reconcile-db-vs-exchange.d.ts +51 -0
  48. package/assets/plugin/ingest/reconcile-db-vs-exchange.js +170 -0
  49. package/assets/plugin/ingest/reconciler-cleanup.d.ts +37 -0
  50. package/assets/plugin/ingest/reconciler-cleanup.js +147 -0
  51. package/assets/plugin/ingest/rest-gap-filler.d.ts +191 -0
  52. package/assets/plugin/ingest/rest-gap-filler.js +565 -0
  53. package/assets/plugin/ingest/touched-symbols-store.d.ts +25 -0
  54. package/assets/plugin/ingest/touched-symbols-store.js +96 -0
  55. package/assets/plugin/ingest/trade-store-client.d.ts +40 -0
  56. package/assets/plugin/ingest/trade-store-client.js +125 -0
  57. package/assets/plugin/ingest/ws-ingest.d.ts +43 -0
  58. package/assets/plugin/ingest/ws-ingest.js +126 -0
  59. package/assets/plugin/learning/setup-family.d.ts +21 -0
  60. package/assets/plugin/learning/setup-family.js +103 -0
  61. package/assets/plugin/lifecycle/install-signal-handlers.d.ts +33 -0
  62. package/assets/plugin/lifecycle/install-signal-handlers.js +112 -0
  63. package/assets/plugin/lifecycle/shutdown-coordinator.d.ts +43 -0
  64. package/assets/plugin/lifecycle/shutdown-coordinator.js +131 -0
  65. package/assets/plugin/lifecycle/trading-operation-lock.d.ts +17 -0
  66. package/assets/plugin/lifecycle/trading-operation-lock.js +14 -0
  67. package/assets/plugin/live/bracket-id.d.ts +26 -0
  68. package/assets/plugin/live/bracket-id.js +112 -0
  69. package/assets/plugin/live/bracket-ledger.d.ts +54 -0
  70. package/assets/plugin/live/bracket-ledger.js +267 -0
  71. package/assets/plugin/live/bracket-manager.d.ts +82 -0
  72. package/assets/plugin/live/bracket-manager.js +478 -0
  73. package/assets/plugin/live/bracket-params.d.ts +22 -0
  74. package/assets/plugin/live/bracket-params.js +124 -0
  75. package/assets/plugin/live/bracket-reconciler.d.ts +95 -0
  76. package/assets/plugin/live/bracket-reconciler.js +573 -0
  77. package/assets/plugin/live/bracket-types.d.ts +102 -0
  78. package/assets/plugin/live/bracket-types.js +8 -0
  79. package/assets/plugin/live/deposit-tracker.d.ts +62 -0
  80. package/assets/plugin/live/deposit-tracker.js +97 -0
  81. package/assets/plugin/live/emergency-controls.d.ts +32 -0
  82. package/assets/plugin/live/emergency-controls.js +226 -0
  83. package/assets/plugin/live/exchange-errors.d.ts +12 -0
  84. package/assets/plugin/live/exchange-errors.js +130 -0
  85. package/assets/plugin/live/exchange-info-cache.d.ts +37 -0
  86. package/assets/plugin/live/exchange-info-cache.js +134 -0
  87. package/assets/plugin/live/fact-subscriber.d.ts +78 -0
  88. package/assets/plugin/live/fact-subscriber.js +182 -0
  89. package/assets/plugin/live/fill-price.d.ts +13 -0
  90. package/assets/plugin/live/fill-price.js +37 -0
  91. package/assets/plugin/live/intent-journal.d.ts +42 -0
  92. package/assets/plugin/live/intent-journal.js +122 -0
  93. package/assets/plugin/live/listen-key-manager.d.ts +70 -0
  94. package/assets/plugin/live/listen-key-manager.js +169 -0
  95. package/assets/plugin/live/live-adapter.d.ts +319 -0
  96. package/assets/plugin/live/live-adapter.js +1906 -0
  97. package/assets/plugin/live/live-balance-enricher.d.ts +32 -0
  98. package/assets/plugin/live/live-balance-enricher.js +104 -0
  99. package/assets/plugin/live/live-bracket-api.d.ts +13 -0
  100. package/assets/plugin/live/live-bracket-api.js +20 -0
  101. package/assets/plugin/live/live-state-store.d.ts +194 -0
  102. package/assets/plugin/live/live-state-store.js +450 -0
  103. package/assets/plugin/live/local-signal-service.d.ts +57 -0
  104. package/assets/plugin/live/local-signal-service.js +151 -0
  105. package/assets/plugin/live/local-strategy-evaluator.d.ts +62 -0
  106. package/assets/plugin/live/local-strategy-evaluator.js +131 -0
  107. package/assets/plugin/live/microstructure-assembler.d.ts +54 -0
  108. package/assets/plugin/live/microstructure-assembler.js +148 -0
  109. package/assets/plugin/live/order-poller.d.ts +29 -0
  110. package/assets/plugin/live/order-poller.js +125 -0
  111. package/assets/plugin/live/position-state-store.d.ts +87 -0
  112. package/assets/plugin/live/position-state-store.js +237 -0
  113. package/assets/plugin/live/proposal-decision-listener.d.ts +70 -0
  114. package/assets/plugin/live/proposal-decision-listener.js +292 -0
  115. package/assets/plugin/live/proposal-manager.d.ts +76 -0
  116. package/assets/plugin/live/proposal-manager.js +140 -0
  117. package/assets/plugin/live/rate-limiter.d.ts +47 -0
  118. package/assets/plugin/live/rate-limiter.js +159 -0
  119. package/assets/plugin/live/reconciler.d.ts +39 -0
  120. package/assets/plugin/live/reconciler.js +175 -0
  121. package/assets/plugin/live/setup-buckets.d.ts +7 -0
  122. package/assets/plugin/live/setup-buckets.js +33 -0
  123. package/assets/plugin/live/slippage-tracker.d.ts +45 -0
  124. package/assets/plugin/live/slippage-tracker.js +78 -0
  125. package/assets/plugin/live/stop-watcher.d.ts +67 -0
  126. package/assets/plugin/live/stop-watcher.js +218 -0
  127. package/assets/plugin/live/user-data-active-probe.d.ts +54 -0
  128. package/assets/plugin/live/user-data-active-probe.js +180 -0
  129. package/assets/plugin/live/user-data-stream-controller.d.ts +200 -0
  130. package/assets/plugin/live/user-data-stream-controller.js +579 -0
  131. package/assets/plugin/live/user-data-stream-ws.d.ts +22 -0
  132. package/assets/plugin/live/user-data-stream-ws.js +63 -0
  133. package/assets/plugin/live/user-data-stream.d.ts +243 -0
  134. package/assets/plugin/live/user-data-stream.js +704 -0
  135. package/assets/plugin/logger.d.ts +2 -0
  136. package/assets/plugin/logger.js +2 -0
  137. package/assets/plugin/mfe.d.ts +21 -0
  138. package/assets/plugin/mfe.js +68 -0
  139. package/assets/plugin/onboarding/mode-ladder.d.ts +1 -0
  140. package/assets/plugin/onboarding/mode-ladder.js +3 -0
  141. package/assets/plugin/onboarding/runtime.d.ts +102 -0
  142. package/assets/plugin/onboarding/runtime.js +215 -0
  143. package/assets/plugin/openclaw.plugin.json +92 -0
  144. package/assets/plugin/paper-adapter.d.ts +24 -0
  145. package/assets/plugin/paper-adapter.js +91 -0
  146. package/assets/plugin/persistence/state-manager.d.ts +66 -0
  147. package/assets/plugin/persistence/state-manager.js +222 -0
  148. package/assets/plugin/pinned-plan.d.ts +9 -0
  149. package/assets/plugin/pinned-plan.js +23 -0
  150. package/assets/plugin/portfolio/reentry-tracker.d.ts +36 -0
  151. package/assets/plugin/portfolio/reentry-tracker.js +127 -0
  152. package/assets/plugin/portfolio/wave9-admission.d.ts +67 -0
  153. package/assets/plugin/portfolio/wave9-admission.js +262 -0
  154. package/assets/plugin/portfolio/wave9-policy.d.ts +36 -0
  155. package/assets/plugin/portfolio/wave9-policy.js +183 -0
  156. package/assets/plugin/risk/pre-trade-check.d.ts +38 -0
  157. package/assets/plugin/risk/pre-trade-check.js +345 -0
  158. package/assets/plugin/risk/pre-trade-types.d.ts +60 -0
  159. package/assets/plugin/risk/pre-trade-types.js +3 -0
  160. package/assets/plugin/shadow/shadow-tracker.d.ts +36 -0
  161. package/assets/plugin/shadow/shadow-tracker.js +151 -0
  162. package/assets/plugin/shadow/types.d.ts +42 -0
  163. package/assets/plugin/shadow/types.js +20 -0
  164. package/assets/plugin/shared/indicators-extended.d.ts +52 -0
  165. package/assets/plugin/shared/indicators-extended.js +291 -0
  166. package/assets/plugin/shared/indicators.d.ts +15 -0
  167. package/assets/plugin/shared/indicators.js +114 -0
  168. package/assets/plugin/signals/conditions/registry.d.ts +16 -0
  169. package/assets/plugin/signals/conditions/registry.js +1333 -0
  170. package/assets/plugin/signals/conditions/types.d.ts +1 -0
  171. package/assets/plugin/signals/conditions/types.js +4 -0
  172. package/assets/plugin/signals/direction-rules.d.ts +3 -0
  173. package/assets/plugin/signals/direction-rules.js +24 -0
  174. package/assets/plugin/signals/entry-rules.d.ts +6 -0
  175. package/assets/plugin/signals/entry-rules.js +33 -0
  176. package/assets/plugin/signals/serialize-context.d.ts +4 -0
  177. package/assets/plugin/signals/serialize-context.js +39 -0
  178. package/assets/plugin/signals/stop-rules.d.ts +3 -0
  179. package/assets/plugin/signals/stop-rules.js +48 -0
  180. package/assets/plugin/signals/strategy-adapter.d.ts +47 -0
  181. package/assets/plugin/signals/strategy-adapter.js +209 -0
  182. package/assets/plugin/signals/types.d.ts +1 -0
  183. package/assets/plugin/signals/types.js +8 -0
  184. package/assets/plugin/simulator/exchange-simulator.d.ts +105 -0
  185. package/assets/plugin/simulator/exchange-simulator.js +759 -0
  186. package/assets/plugin/simulator/fill-engine.d.ts +53 -0
  187. package/assets/plugin/simulator/fill-engine.js +280 -0
  188. package/assets/plugin/simulator/paper-market-feed.d.ts +26 -0
  189. package/assets/plugin/simulator/paper-market-feed.js +104 -0
  190. package/assets/plugin/simulator/realistic-fills.d.ts +59 -0
  191. package/assets/plugin/simulator/realistic-fills.js +175 -0
  192. package/assets/plugin/simulator/types.d.ts +228 -0
  193. package/assets/plugin/simulator/types.js +43 -0
  194. package/assets/plugin/strategy/builtin-strategies.d.ts +2 -0
  195. package/assets/plugin/strategy/builtin-strategies.js +113 -0
  196. package/assets/plugin/strategy/condition-registry.d.ts +3 -0
  197. package/assets/plugin/strategy/condition-registry.js +153 -0
  198. package/assets/plugin/strategy/evaluator.d.ts +70 -0
  199. package/assets/plugin/strategy/evaluator.js +98 -0
  200. package/assets/plugin/tools/assessment-validation.d.ts +141 -0
  201. package/assets/plugin/tools/assessment-validation.js +473 -0
  202. package/assets/plugin/tools/attach-brackets.d.ts +39 -0
  203. package/assets/plugin/tools/attach-brackets.js +564 -0
  204. package/assets/plugin/tools/audit-bracket-protection.d.ts +49 -0
  205. package/assets/plugin/tools/audit-bracket-protection.js +683 -0
  206. package/assets/plugin/tools/bracket-control.d.ts +12 -0
  207. package/assets/plugin/tools/bracket-control.js +35 -0
  208. package/assets/plugin/tools/cancel-all-orders.d.ts +9 -0
  209. package/assets/plugin/tools/cancel-all-orders.js +8 -0
  210. package/assets/plugin/tools/cancel-order.d.ts +14 -0
  211. package/assets/plugin/tools/cancel-order.js +60 -0
  212. package/assets/plugin/tools/check-position-health.d.ts +46 -0
  213. package/assets/plugin/tools/check-position-health.js +194 -0
  214. package/assets/plugin/tools/clear-exchange-credentials.d.ts +24 -0
  215. package/assets/plugin/tools/clear-exchange-credentials.js +70 -0
  216. package/assets/plugin/tools/close-position.d.ts +45 -0
  217. package/assets/plugin/tools/close-position.js +722 -0
  218. package/assets/plugin/tools/create-order.d.ts +89 -0
  219. package/assets/plugin/tools/create-order.js +1555 -0
  220. package/assets/plugin/tools/exit-gate.d.ts +58 -0
  221. package/assets/plugin/tools/exit-gate.js +162 -0
  222. package/assets/plugin/tools/fetch-balance.d.ts +5 -0
  223. package/assets/plugin/tools/fetch-balance.js +4 -0
  224. package/assets/plugin/tools/fetch-ohlcv.d.ts +11 -0
  225. package/assets/plugin/tools/fetch-ohlcv.js +8 -0
  226. package/assets/plugin/tools/fetch-open-orders.d.ts +7 -0
  227. package/assets/plugin/tools/fetch-open-orders.js +4 -0
  228. package/assets/plugin/tools/fetch-positions.d.ts +7 -0
  229. package/assets/plugin/tools/fetch-positions.js +4 -0
  230. package/assets/plugin/tools/fetch-ticker.d.ts +11 -0
  231. package/assets/plugin/tools/fetch-ticker.js +5 -0
  232. package/assets/plugin/tools/get-agent-profile.d.ts +4 -0
  233. package/assets/plugin/tools/get-agent-profile.js +6 -0
  234. package/assets/plugin/tools/get-analytics.d.ts +6 -0
  235. package/assets/plugin/tools/get-analytics.js +7 -0
  236. package/assets/plugin/tools/get-backtest.d.ts +12 -0
  237. package/assets/plugin/tools/get-backtest.js +91 -0
  238. package/assets/plugin/tools/get-basis.d.ts +7 -0
  239. package/assets/plugin/tools/get-basis.js +7 -0
  240. package/assets/plugin/tools/get-bracket-config.d.ts +11 -0
  241. package/assets/plugin/tools/get-bracket-config.js +24 -0
  242. package/assets/plugin/tools/get-cascade-risk.d.ts +7 -0
  243. package/assets/plugin/tools/get-cascade-risk.js +8 -0
  244. package/assets/plugin/tools/get-crypto-metrics.d.ts +18 -0
  245. package/assets/plugin/tools/get-crypto-metrics.js +62 -0
  246. package/assets/plugin/tools/get-cvd.d.ts +6 -0
  247. package/assets/plugin/tools/get-cvd.js +6 -0
  248. package/assets/plugin/tools/get-divergences.d.ts +6 -0
  249. package/assets/plugin/tools/get-divergences.js +6 -0
  250. package/assets/plugin/tools/get-funding-context.d.ts +6 -0
  251. package/assets/plugin/tools/get-funding-context.js +16 -0
  252. package/assets/plugin/tools/get-liquidation-levels.d.ts +7 -0
  253. package/assets/plugin/tools/get-liquidation-levels.js +7 -0
  254. package/assets/plugin/tools/get-liquidation-pulse.d.ts +9 -0
  255. package/assets/plugin/tools/get-liquidation-pulse.js +22 -0
  256. package/assets/plugin/tools/get-market-breadth.d.ts +6 -0
  257. package/assets/plugin/tools/get-market-breadth.js +8 -0
  258. package/assets/plugin/tools/get-market-intel.d.ts +19 -0
  259. package/assets/plugin/tools/get-market-intel.js +116 -0
  260. package/assets/plugin/tools/get-market-structure.d.ts +47 -0
  261. package/assets/plugin/tools/get-market-structure.js +198 -0
  262. package/assets/plugin/tools/get-my-mined-patterns.d.ts +20 -0
  263. package/assets/plugin/tools/get-my-mined-patterns.js +61 -0
  264. package/assets/plugin/tools/get-my-proposed-learnings.d.ts +20 -0
  265. package/assets/plugin/tools/get-my-proposed-learnings.js +55 -0
  266. package/assets/plugin/tools/get-my-recent-reviews.d.ts +22 -0
  267. package/assets/plugin/tools/get-my-recent-reviews.js +66 -0
  268. package/assets/plugin/tools/get-orderbook.d.ts +21 -0
  269. package/assets/plugin/tools/get-orderbook.js +32 -0
  270. package/assets/plugin/tools/get-pattern-scan.d.ts +7 -0
  271. package/assets/plugin/tools/get-pattern-scan.js +8 -0
  272. package/assets/plugin/tools/get-regime.d.ts +6 -0
  273. package/assets/plugin/tools/get-regime.js +7 -0
  274. package/assets/plugin/tools/get-relevant-learnings.d.ts +21 -0
  275. package/assets/plugin/tools/get-relevant-learnings.js +65 -0
  276. package/assets/plugin/tools/get-resting-liquidity.d.ts +6 -0
  277. package/assets/plugin/tools/get-resting-liquidity.js +11 -0
  278. package/assets/plugin/tools/get-risk-scenario.d.ts +29 -0
  279. package/assets/plugin/tools/get-risk-scenario.js +47 -0
  280. package/assets/plugin/tools/get-risk-summary.d.ts +51 -0
  281. package/assets/plugin/tools/get-risk-summary.js +118 -0
  282. package/assets/plugin/tools/get-sentiment.d.ts +4 -0
  283. package/assets/plugin/tools/get-sentiment.js +6 -0
  284. package/assets/plugin/tools/get-session-review.d.ts +7 -0
  285. package/assets/plugin/tools/get-session-review.js +8 -0
  286. package/assets/plugin/tools/get-setup-detail.d.ts +7 -0
  287. package/assets/plugin/tools/get-setup-detail.js +322 -0
  288. package/assets/plugin/tools/get-signals.d.ts +15 -0
  289. package/assets/plugin/tools/get-signals.js +54 -0
  290. package/assets/plugin/tools/get-sizing.d.ts +6 -0
  291. package/assets/plugin/tools/get-sizing.js +6 -0
  292. package/assets/plugin/tools/get-trade-feedback.d.ts +7 -0
  293. package/assets/plugin/tools/get-trade-feedback.js +8 -0
  294. package/assets/plugin/tools/get-trade-flow.d.ts +7 -0
  295. package/assets/plugin/tools/get-trade-flow.js +7 -0
  296. package/assets/plugin/tools/get-volume-analysis.d.ts +21 -0
  297. package/assets/plugin/tools/get-volume-analysis.js +74 -0
  298. package/assets/plugin/tools/get-volume-profile.d.ts +7 -0
  299. package/assets/plugin/tools/get-volume-profile.js +7 -0
  300. package/assets/plugin/tools/get-wave9-status.d.ts +127 -0
  301. package/assets/plugin/tools/get-wave9-status.js +796 -0
  302. package/assets/plugin/tools/helpers.d.ts +26 -0
  303. package/assets/plugin/tools/helpers.js +39 -0
  304. package/assets/plugin/tools/intel-api.d.ts +34 -0
  305. package/assets/plugin/tools/intel-api.js +119 -0
  306. package/assets/plugin/tools/intel-cache.d.ts +25 -0
  307. package/assets/plugin/tools/intel-cache.js +148 -0
  308. package/assets/plugin/tools/list-strategies.d.ts +17 -0
  309. package/assets/plugin/tools/list-strategies.js +23 -0
  310. package/assets/plugin/tools/modify-stop.d.ts +21 -0
  311. package/assets/plugin/tools/modify-stop.js +120 -0
  312. package/assets/plugin/tools/modify-target.d.ts +21 -0
  313. package/assets/plugin/tools/modify-target.js +110 -0
  314. package/assets/plugin/tools/propose-learning.d.ts +22 -0
  315. package/assets/plugin/tools/propose-learning.js +65 -0
  316. package/assets/plugin/tools/query-review-outcomes.d.ts +30 -0
  317. package/assets/plugin/tools/query-review-outcomes.js +64 -0
  318. package/assets/plugin/tools/query-trades.d.ts +21 -0
  319. package/assets/plugin/tools/query-trades.js +37 -0
  320. package/assets/plugin/tools/record-position-reviews.d.ts +38 -0
  321. package/assets/plugin/tools/record-position-reviews.js +147 -0
  322. package/assets/plugin/tools/save-strategy.d.ts +16 -0
  323. package/assets/plugin/tools/save-strategy.js +46 -0
  324. package/assets/plugin/tools/scan-pairs.d.ts +22 -0
  325. package/assets/plugin/tools/scan-pairs.js +234 -0
  326. package/assets/plugin/tools/score-setup.d.ts +31 -0
  327. package/assets/plugin/tools/score-setup.js +268 -0
  328. package/assets/plugin/tools/set-bracket-requirement.d.ts +18 -0
  329. package/assets/plugin/tools/set-bracket-requirement.js +81 -0
  330. package/assets/plugin/tools/set-exchange-credentials.d.ts +25 -0
  331. package/assets/plugin/tools/set-exchange-credentials.js +80 -0
  332. package/assets/plugin/tools/set-trading-mode.d.ts +26 -0
  333. package/assets/plugin/tools/set-trading-mode.js +152 -0
  334. package/assets/plugin/tools/test-exchange-credentials.d.ts +16 -0
  335. package/assets/plugin/tools/test-exchange-credentials.js +100 -0
  336. package/assets/plugin/tools/toggle-strategy.d.ts +8 -0
  337. package/assets/plugin/tools/toggle-strategy.js +15 -0
  338. package/assets/plugin/trading-params-cache.d.ts +26 -0
  339. package/assets/plugin/trading-params-cache.js +52 -0
  340. package/assets/plugin/types.d.ts +134 -0
  341. package/assets/plugin/types.js +7 -0
  342. package/assets/plugin/util/plugin-paths.d.ts +3 -0
  343. package/assets/plugin/util/plugin-paths.js +15 -0
  344. package/assets/plugin/venues/hyperliquid/hl-balance.d.ts +116 -0
  345. package/assets/plugin/venues/hyperliquid/hl-balance.js +145 -0
  346. package/assets/plugin/venues/hyperliquid/hl-bracket-coordinator.d.ts +123 -0
  347. package/assets/plugin/venues/hyperliquid/hl-bracket-coordinator.js +533 -0
  348. package/assets/plugin/venues/hyperliquid/hl-brackets.d.ts +102 -0
  349. package/assets/plugin/venues/hyperliquid/hl-brackets.js +172 -0
  350. package/assets/plugin/venues/hyperliquid/hl-cloid.d.ts +22 -0
  351. package/assets/plugin/venues/hyperliquid/hl-cloid.js +82 -0
  352. package/assets/plugin/venues/hyperliquid/hl-info-cache.d.ts +46 -0
  353. package/assets/plugin/venues/hyperliquid/hl-info-cache.js +125 -0
  354. package/assets/plugin/venues/hyperliquid/hl-live-adapter.d.ts +146 -0
  355. package/assets/plugin/venues/hyperliquid/hl-live-adapter.js +728 -0
  356. package/assets/plugin/venues/hyperliquid/hl-precision.d.ts +61 -0
  357. package/assets/plugin/venues/hyperliquid/hl-precision.js +176 -0
  358. package/assets/plugin/venues/hyperliquid/hl-private.d.ts +88 -0
  359. package/assets/plugin/venues/hyperliquid/hl-private.js +357 -0
  360. package/assets/plugin/venues/hyperliquid/hl-public.d.ts +79 -0
  361. package/assets/plugin/venues/hyperliquid/hl-public.js +436 -0
  362. package/assets/plugin/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
  363. package/assets/plugin/venues/hyperliquid/hl-rate-gate.js +220 -0
  364. package/assets/plugin/venues/hyperliquid/hl-user-stream.d.ts +90 -0
  365. package/assets/plugin/venues/hyperliquid/hl-user-stream.js +220 -0
  366. package/assets/plugin/venues/registry.d.ts +38 -0
  367. package/assets/plugin/venues/registry.js +46 -0
  368. package/assets/plugin/venues/symbols.d.ts +43 -0
  369. package/assets/plugin/venues/symbols.js +107 -0
  370. package/assets/plugin/wave9/live-account-capture.d.ts +67 -0
  371. package/assets/plugin/wave9/live-account-capture.js +435 -0
  372. package/assets/plugin/wave9/live-autonomous-protection.d.ts +39 -0
  373. package/assets/plugin/wave9/live-autonomous-protection.js +112 -0
  374. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
  375. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.js +115 -0
  376. package/assets/plugin/wave9/live-execution-ledger.d.ts +107 -0
  377. package/assets/plugin/wave9/live-execution-ledger.js +498 -0
  378. package/assets/plugin/wave9/live-position-confirmation.d.ts +18 -0
  379. package/assets/plugin/wave9/live-position-confirmation.js +111 -0
  380. package/assets/plugin/wave9/live-residual-protection.d.ts +18 -0
  381. package/assets/plugin/wave9/live-residual-protection.js +250 -0
  382. package/assets/plugin/wave9/live-startup-reconciliation.d.ts +38 -0
  383. package/assets/plugin/wave9/live-startup-reconciliation.js +454 -0
  384. package/assets/plugin/wave9/live-symbol-ownership.d.ts +20 -0
  385. package/assets/plugin/wave9/live-symbol-ownership.js +132 -0
  386. package/assets/plugin/wave9/paper-admission-guard.d.ts +199 -0
  387. package/assets/plugin/wave9/paper-admission-guard.js +650 -0
  388. package/assets/plugin/wave9/usdm-evidence-provider.d.ts +42 -0
  389. package/assets/plugin/wave9/usdm-evidence-provider.js +133 -0
  390. package/package.json +1 -1
@@ -0,0 +1,709 @@
1
+ // Auto-capture helper for the Position Decision Journal.
2
+ //
3
+ // Wires create_order / close_position / fill events into the webapp ingest:
4
+ //
5
+ // ENTRY — onCreateOrderFilled() upserts the parent positions row, gets back
6
+ // the UUID, persists it locally for restart-survival, then posts the
7
+ // entry decision row.
8
+ // CLOSE — onClosePositionFilled() looks up the cached position UUID and posts
9
+ // the close decision row. Local position-state is dropped after the
10
+ // dispatch so the symbol can re-enter cleanly.
11
+ //
12
+ // Auto-capture is mode-agnostic — it runs whenever the decisions client is
13
+ // configured (env vars present), regardless of trading mode (PAPER vs LIVE) or
14
+ // positionReview.mode. Fail-open: a network blip on a webapp POST does not
15
+ // block the trading hot path.
16
+ //
17
+ // PR 1 scope: market orders (immediate fill detection from CcxtOrder.filled).
18
+ // Limit-order fill capture and scale-in detection are deferred to a follow-up
19
+ // that hooks into the WS-ingest pipeline (see POSITION_DECISION_JOURNAL_PLAN
20
+ // §5.1 for the longer-term design).
21
+ import { logger } from '../logger.js';
22
+ import { isBracketCid } from '../live/bracket-id.js';
23
+ import { normalizeBracketSymbol } from '../live/bracket-ledger.js';
24
+ import { fillPriceFromOrder } from '../live/fill-price.js';
25
+ const TAG = 'position-auto-capture';
26
+ /** Flatness tolerance for remaining-contracts tracking. Reduce-only fills sum
27
+ * exactly to the position size on Binance, so any residual below this is noise
28
+ * / float rounding and means the position is flat. */
29
+ const FLAT_EPSILON = 1e-6;
30
+ /** Extract the simulator's engine-exact trade economics from a close order,
31
+ * with strict numeric guards (absent on live orders → undefined). */
32
+ export function extractPaperTrade(order) {
33
+ const raw = order?.info?.['paperTrade'];
34
+ if (!raw || typeof raw !== 'object')
35
+ return undefined;
36
+ const num = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
37
+ const gross = num(raw['grossRealizedPnl']);
38
+ const net = num(raw['netRealizedPnl']);
39
+ if (gross === undefined || net === undefined)
40
+ return undefined;
41
+ return {
42
+ grossRealizedPnl: gross,
43
+ netRealizedPnl: net,
44
+ openFee: num(raw['openFee']) ?? 0,
45
+ closeFee: num(raw['closeFee']) ?? 0,
46
+ setupType: typeof raw['setupType'] === 'string' ? raw['setupType'] : undefined,
47
+ };
48
+ }
49
+ /** Stale-state probe: does the exchange position for `symbol` consist entirely
50
+ * of the just-filled quantity? 'stale' → the tracked prior exposure no longer
51
+ * exists (closed outside the journal); 'active' → genuine scale-in;
52
+ * 'unknown' → cannot tell (null fetch etc.) — callers keep legacy behaviour. */
53
+ async function probeStaleStateEntry(ctx, symbol, filledQty) {
54
+ const adapter = ctx.resolveAdapter?.();
55
+ if (!adapter)
56
+ return 'unknown';
57
+ let positions;
58
+ try {
59
+ positions = await adapter.getPositionsOrNull(symbol);
60
+ }
61
+ catch {
62
+ return 'unknown';
63
+ }
64
+ if (positions === null)
65
+ return 'unknown';
66
+ const key = normalizeBracketSymbol(symbol);
67
+ const pos = positions.find((p) => normalizeBracketSymbol(p.symbol) === key);
68
+ const contracts = Math.abs(Number(pos?.contracts));
69
+ if (!pos || !Number.isFinite(contracts) || contracts <= 0)
70
+ return 'unknown';
71
+ const tolerance = Math.max(1e-9, contracts * 0.001);
72
+ return Math.abs(contracts - filledQty) <= tolerance ? 'stale' : 'active';
73
+ }
74
+ /** Called after a successful adapter.createOrder().
75
+ *
76
+ * For PR 1, we treat any non-zero `filled` quantity as an entry event and
77
+ * upsert the parent position + post the entry decision row. Limit orders that
78
+ * haven't filled yet are skipped (caller can call us again from the WS-ingest
79
+ * pipeline once they fill — that wiring is the follow-up).
80
+ *
81
+ * Always-fail-open: webapp errors are logged but never thrown.
82
+ */
83
+ export async function onCreateOrderFilled(ctx, inputs, order) {
84
+ if (!ctx.decisionsClient || !ctx.userId || !ctx.stateStore)
85
+ return;
86
+ const filledQty = typeof order.filled === 'number' && Number.isFinite(order.filled)
87
+ ? order.filled
88
+ : 0;
89
+ if (filledQty <= 0) {
90
+ // Limit order not yet filled — nothing to capture yet.
91
+ return;
92
+ }
93
+ // Resolve the ACTUAL average fill — never order.price (the limit). A
94
+ // marketable limit can fill percent-scale away from its limit, and the
95
+ // synchronous RESULT response sometimes omits avgPrice on a filled limit
96
+ // (issue #196: TAO journaled 202 vs a real 196.23 fill). The adapter
97
+ // re-queries to enrich order.average before we get here; if it's still
98
+ // unresolvable we SKIP rather than journal the wrong (limit) price — the
99
+ // WS-driven onWsFillObserved path re-captures the entry with the real fill.
100
+ const fillPrice = fillPriceFromOrder(order);
101
+ if (fillPrice === null) {
102
+ logger.warn(TAG, `onCreateOrderFilled ${inputs.symbol}: fill price unresolvable (avg=${order.average ?? 'null'} cost=${order.cost} filled=${order.filled}); skipping capture (WS fill path will re-capture with the real price)`);
103
+ return;
104
+ }
105
+ // Need v2.10.0 metadata to build a meaningful entry row. In paper mode the
106
+ // agent may omit them; we still capture an entry but mark missing fields.
107
+ const md = inputs.metadata;
108
+ if (!md) {
109
+ logger.info(TAG, `onCreateOrderFilled ${inputs.symbol}: no metadata; skipping (paper without rationale)`);
110
+ return;
111
+ }
112
+ const positionSide = inputs.side === 'buy' ? 'long' : 'short';
113
+ const openedAtMs = Date.now();
114
+ // State-store already knows this symbol. Three cases (issue #199):
115
+ // STALE — the exchange position is exactly this fill, so the tracked
116
+ // prior exposure was closed outside the journal (stop-watcher /
117
+ // manual). Drop the stale mapping and journal a NEW position;
118
+ // stitching onto it fabricates P&L against the old entry.
119
+ // SCALE-IN (paper) — genuine add. Paper has no WS fill stream, so the old
120
+ // "deferred to v2" early-return silently dropped every paper
121
+ // scale-in; journal an is_scale_in entry row here instead.
122
+ // SCALE-IN (live) — keep deferring to onWsFillObserved (it captures
123
+ // scale-ins with WS-exact data; capturing here would double-post).
124
+ const existing = ctx.stateStore.get(inputs.symbol);
125
+ if (existing) {
126
+ const probe = await probeStaleStateEntry(ctx, inputs.symbol, filledQty);
127
+ if (probe === 'stale') {
128
+ logger.warn(TAG, `onCreateOrderFilled ${inputs.symbol}: state-store entry (openedAt=${existing.openedAt}) is STALE — ` +
129
+ `exchange position equals this fill (${filledQty}); prior exposure closed outside the journal. ` +
130
+ `Dropping stale mapping and journaling a NEW position (issue #199).`);
131
+ ctx.stateStore.remove(inputs.symbol);
132
+ // fall through to the new-position path below
133
+ }
134
+ else if (ctx.resolveMode?.() !== 'paper') {
135
+ logger.info(TAG, `onCreateOrderFilled ${inputs.symbol}: existing position in state-store (openedAt=${existing.openedAt}); scale-in captured by the WS fill path`);
136
+ return;
137
+ }
138
+ else if (existing.side !== positionSide) {
139
+ // Opposite-side paper fill on a tracked position = partial/soft flatten
140
+ // that bypassed close_position. Don't guess — the close paths + periodic
141
+ // sweep own this.
142
+ logger.warn(TAG, `onCreateOrderFilled ${inputs.symbol}: opposite-side fill on tracked ${existing.side} position — skipping (use close_position)`);
143
+ return;
144
+ }
145
+ else {
146
+ if (!existing.webappPositionId) {
147
+ logger.warn(TAG, `onCreateOrderFilled ${inputs.symbol}: paper scale-in but no webappPositionId yet — skipping entry row`);
148
+ return;
149
+ }
150
+ const entry = buildEntryPayload({
151
+ positionId: existing.webappPositionId,
152
+ isScaleIn: true,
153
+ ts: openedAtMs,
154
+ fillPrice,
155
+ fillSize: filledQty,
156
+ exchangeTradeId: typeof order.id === 'string' ? order.id : '',
157
+ metadata: md,
158
+ });
159
+ ctx.decisionsClient.postEntry(ctx.userId, entry);
160
+ ctx.stateStore.addOpenContracts(inputs.symbol, filledQty);
161
+ logger.info(TAG, `scale-in captured (paper) ${inputs.symbol} ${positionSide} ${filledQty} @ ${fillPrice} ` +
162
+ `(positionId=${existing.webappPositionId.slice(0, 8)}…)`);
163
+ return;
164
+ }
165
+ }
166
+ // Local state-store first — ensures restart-survival even if the webapp POST
167
+ // fails on the first attempt.
168
+ ctx.stateStore.upsert({
169
+ symbol: inputs.symbol,
170
+ openedAt: openedAtMs,
171
+ side: positionSide,
172
+ openedFromExchangeTradeId: typeof order.id === 'string' ? order.id : undefined,
173
+ setupType: md.setupType,
174
+ });
175
+ const upsert = {
176
+ symbol: inputs.symbol,
177
+ positionOpenAt: openedAtMs,
178
+ side: positionSide,
179
+ currentSize: filledQty,
180
+ avgEntryPrice: fillPrice,
181
+ mode: ctx.resolveMode?.(),
182
+ exchange: ctx.venue,
183
+ };
184
+ // postPosition is awaited — we need the UUID before posting the entry row.
185
+ const positionId = await ctx.decisionsClient.postPosition(ctx.userId, upsert);
186
+ if (!positionId) {
187
+ logger.warn(TAG, `onCreateOrderFilled ${inputs.symbol}: webapp position upsert returned no id; entry row will not be posted (will retry on next observation)`);
188
+ return;
189
+ }
190
+ ctx.stateStore.setWebappId(inputs.symbol, positionId);
191
+ // Track size so a later reduce-only/bracket exit fill can detect flat + close.
192
+ ctx.stateStore.addOpenContracts(inputs.symbol, filledQty);
193
+ // Build entry decision payload from the v2.10.0 metadata.
194
+ // regime_confidence stored 0-100 in PositionMetadata (per validator), but the
195
+ // webapp schema expects 0-1. Normalize.
196
+ const regimeConfNormalized = typeof md.regimeConfidence === 'number'
197
+ ? Math.max(0, Math.min(1, md.regimeConfidence / 100))
198
+ : 0.5;
199
+ const entry = {
200
+ positionId,
201
+ entryAt: openedAtMs,
202
+ isScaleIn: false,
203
+ thesis: md.thesis ?? '(no thesis recorded)',
204
+ setupType: md.setupType ?? 'unknown',
205
+ regime: md.regime ?? 'unknown',
206
+ regimeConfidence: regimeConfNormalized,
207
+ scorecardVerdict: md.scorecardVerdict ?? 'GO',
208
+ confluenceScore: typeof md.confluenceScore === 'number' ? md.confluenceScore : 0,
209
+ fillPrice,
210
+ fillSize: filledQty,
211
+ exchangeTradeId: typeof order.id === 'string' ? order.id : undefined,
212
+ metadata: buildEntryPlanMetadata(md),
213
+ };
214
+ ctx.decisionsClient.postEntry(ctx.userId, entry);
215
+ logger.info(TAG, `entry captured ${inputs.symbol} ${positionSide} ${filledQty} @ ${fillPrice} (positionId=${positionId.slice(0, 8)}…)`);
216
+ }
217
+ /** Called after a successful adapter.closePosition() with the v2.10.0 reason +
218
+ * assessment from close_position.ts. Looks up the cached position UUID, posts
219
+ * the close decision row, and drops the local state entry. */
220
+ export async function onClosePositionFilled(ctx, inputs, order) {
221
+ if (!ctx.decisionsClient || !ctx.userId || !ctx.stateStore)
222
+ return;
223
+ const stateEntry = ctx.stateStore.get(inputs.symbol);
224
+ if (!stateEntry || !stateEntry.webappPositionId) {
225
+ logger.warn(TAG, `onClosePositionFilled ${inputs.symbol}: no cached webappPositionId; close row will not be posted (entry was not captured)`);
226
+ return;
227
+ }
228
+ const filledQty = typeof order.filled === 'number' && Number.isFinite(order.filled)
229
+ ? order.filled
230
+ : 0;
231
+ // Actual average fill — never order.price (the limit); see fillPriceFromOrder (#196).
232
+ const fillPrice = fillPriceFromOrder(order);
233
+ if (filledQty <= 0 || fillPrice === null) {
234
+ logger.warn(TAG, `onClosePositionFilled ${inputs.symbol}: missing fill quantity or price; skipping capture`);
235
+ return;
236
+ }
237
+ const closeAtMs = Date.now();
238
+ const regimeConfNormalized = typeof inputs.regimeConfidence === 'number'
239
+ ? Math.max(0, Math.min(1, inputs.regimeConfidence / 100))
240
+ : 0.5;
241
+ // Engine-exact economics on paper (issue #201): the simulator attaches the
242
+ // trade's net P&L + both fee legs to the close order. Posting a non-zero
243
+ // realizedPnl means the webapp keeps it verbatim (its recompute only fires
244
+ // on the legacy all-zero path) — the journal stops being gross-of-fees.
245
+ const paperTrade = extractPaperTrade(order);
246
+ // Size-mismatch defense (issue #199): if the close fill is materially larger
247
+ // or smaller than the contracts tracked for THIS journal position, the close
248
+ // likely belongs to different exposure (state-store staleness). Flag it so
249
+ // the webapp close route refuses to fabricate price-based P&L/R from it.
250
+ const tracked = stateEntry.remainingContracts;
251
+ const sizeMismatch = typeof tracked === 'number' && Number.isFinite(tracked) && tracked > 0 &&
252
+ Math.abs(tracked - filledQty) / Math.max(tracked, filledQty) > 0.05
253
+ ? { tracked_contracts: tracked, close_fill_size: filledQty }
254
+ : undefined;
255
+ if (sizeMismatch) {
256
+ logger.warn(TAG, `onClosePositionFilled ${inputs.symbol}: close fill ${filledQty} vs tracked ${tracked} contracts — ` +
257
+ `flagging size_mismatch (webapp will not derive price-based metrics from this close)`);
258
+ }
259
+ const closeAssessment = {
260
+ ...inputs.closeAssessment,
261
+ ...(paperTrade
262
+ ? {
263
+ pnl_source: 'paper_engine_net',
264
+ pnl_gross: paperTrade.grossRealizedPnl,
265
+ fees: { open: paperTrade.openFee, close: paperTrade.closeFee },
266
+ }
267
+ : {}),
268
+ ...(sizeMismatch ? { size_mismatch: sizeMismatch } : {}),
269
+ };
270
+ const close = {
271
+ positionId: stateEntry.webappPositionId,
272
+ closeAt: closeAtMs,
273
+ closeReason: inputs.closeReason,
274
+ closeAssessment,
275
+ scorecardVerdict: inputs.scorecardVerdict ?? 'NO_GO',
276
+ confluenceScore: typeof inputs.confluenceScore === 'number' ? inputs.confluenceScore : 0,
277
+ regime: inputs.regime ?? 'unknown',
278
+ regimeConfidence: regimeConfNormalized,
279
+ fillPrice,
280
+ fillSize: filledQty,
281
+ realizedPnl: paperTrade?.netRealizedPnl ?? 0, // live: filled by ws-ingest / server
282
+ realizedR: 0,
283
+ mfeRAtClose: 0,
284
+ giveBackPctAtClose: 0,
285
+ exchangeTradeId: typeof order.id === 'string' ? order.id : undefined,
286
+ };
287
+ ctx.decisionsClient.postClose(ctx.userId, close);
288
+ // Re-entry indication (issue #204) — record the exit so scan_pairs can flag
289
+ // same-bar re-entries on this (symbol, setup).
290
+ ctx.reentryTracker?.recordExit({
291
+ symbol: inputs.symbol,
292
+ setupType: stateEntry.setupType ?? paperTrade?.setupType,
293
+ side: stateEntry.side,
294
+ wasLoss: paperTrade ? paperTrade.netRealizedPnl < 0 : undefined,
295
+ closedAtMs: closeAtMs,
296
+ });
297
+ // Drop local state — symbol can re-enter as a new position.
298
+ ctx.stateStore.remove(inputs.symbol);
299
+ logger.info(TAG, `close captured ${inputs.symbol} reason=${inputs.closeReason} (positionId=${stateEntry.webappPositionId.slice(0, 8)}…)`);
300
+ }
301
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
302
+ /** How long onAutoFlattenClose polls for the entry journal's webappPositionId
303
+ * before giving up (the entry's WS fill can land a beat after the flatten).
304
+ * 6 × 750ms ≈ 4.5s upper bound; off the trading hot path (event-handler). */
305
+ const AUTO_FLATTEN_LOOKUP_ATTEMPTS = 6;
306
+ const AUTO_FLATTEN_LOOKUP_INTERVAL_MS = 750;
307
+ /** Journal the close produced by an adapter-internal auto-flatten
308
+ * (`bracket_attach_failed`). Those flattens call `adapter.closePosition`
309
+ * directly, bypassing the close_position tool, so `onClosePositionFilled`
310
+ * never runs and the position row orphaned as status='open' (issue #196:
311
+ * LTC 2026-07-14, TAO 2026-07-15 — both showed 1 exchange position vs 2 in the
312
+ * journal). Polls briefly for the entry journal's webappPositionId (the
313
+ * entry's WS fill can arrive just after the flatten), then posts an idempotent
314
+ * close row (UNIQUE on position_id) and drops the state-store entry. Fail-open;
315
+ * the boot DB reconcile sweep is the backstop if the entry never journals. */
316
+ export async function onAutoFlattenClose(ctx, inputs, lookup = {
317
+ attempts: AUTO_FLATTEN_LOOKUP_ATTEMPTS,
318
+ intervalMs: AUTO_FLATTEN_LOOKUP_INTERVAL_MS,
319
+ }) {
320
+ if (!ctx.decisionsClient || !ctx.userId || !ctx.stateStore)
321
+ return;
322
+ if (!(inputs.fillPrice > 0) || !(inputs.fillSize > 0))
323
+ return;
324
+ // The entry may not be journaled yet: the auto-flatten fires the moment the
325
+ // bracket attach exhausts its retries, which can beat the entry's WS-fill
326
+ // capture. Poll a few times for the webappPositionId before giving up.
327
+ let webappPositionId;
328
+ for (let attempt = 0; attempt < lookup.attempts; attempt++) {
329
+ webappPositionId = ctx.stateStore.get(inputs.symbol)?.webappPositionId;
330
+ if (webappPositionId)
331
+ break;
332
+ if (attempt < lookup.attempts - 1)
333
+ await sleep(lookup.intervalMs);
334
+ }
335
+ if (!webappPositionId) {
336
+ logger.warn(TAG, `onAutoFlattenClose ${inputs.symbol}: no webappPositionId after ${lookup.attempts} attempts — ` +
337
+ `DB row may stay status='open' until the boot reconcile sweep (entry likely not journaled)`);
338
+ return;
339
+ }
340
+ const close = {
341
+ positionId: webappPositionId,
342
+ closeAt: inputs.observedAtMs ?? Date.now(),
343
+ closeReason: 'bracket_attach_failed',
344
+ closeAssessment: {
345
+ note: 'Position auto-flattened because exchange-native bracket protection could not be ' +
346
+ 'attached (bracket_attach_failed). Journaled from the flatten fill — no close_position ' +
347
+ 'call (auto-flatten bypass, issue #196).',
348
+ observedFrom: 'auto_flatten',
349
+ },
350
+ scorecardVerdict: 'NO_GO',
351
+ confluenceScore: 0,
352
+ regime: 'unknown',
353
+ regimeConfidence: 0.5,
354
+ fillPrice: inputs.fillPrice,
355
+ fillSize: Math.abs(inputs.fillSize),
356
+ // Real close (we have the flatten fill) — not synthetic. Zeros are filled
357
+ // in per-metric by the webapp close route (price-based R from the pinned
358
+ // invalidation_price); a scratch resolves to ≈0R, honestly.
359
+ realizedPnl: 0,
360
+ realizedR: 0,
361
+ mfeRAtClose: 0,
362
+ giveBackPctAtClose: 0,
363
+ ...(inputs.exchangeTradeId ? { exchangeTradeId: inputs.exchangeTradeId } : {}),
364
+ };
365
+ ctx.decisionsClient.postClose(ctx.userId, close);
366
+ const flattenState = ctx.stateStore.get(inputs.symbol);
367
+ ctx.reentryTracker?.recordExit({
368
+ symbol: inputs.symbol,
369
+ setupType: flattenState?.setupType,
370
+ side: flattenState?.side ?? 'long',
371
+ closedAtMs: inputs.observedAtMs ?? Date.now(),
372
+ });
373
+ ctx.stateStore.remove(inputs.symbol);
374
+ logger.info(TAG, `auto-flatten close captured ${inputs.symbol} @ ${inputs.fillPrice} ` +
375
+ `(positionId=${webappPositionId.slice(0, 8)}…)`);
376
+ }
377
+ /** Journal a stop-watcher auto-close (issue #199).
378
+ *
379
+ * The watcher calls `adapter.closePosition(symbol, 'stop_watcher')` directly —
380
+ * it never goes through the close_position tool, and paper has no WS fill
381
+ * stream, so before this hook every watcher close left the journal position
382
+ * (and the state-store mapping) alive: the next trade on the symbol was then
383
+ * silently dropped or stitched onto the stale position, fabricating P&L
384
+ * (measured 14/69 corrupted closes on the 2026-07 HL soak; worst single
385
+ * fabrication −$117.86 vs a real −$10.63).
386
+ *
387
+ * Contract: the engine exposure is DEFINITIVELY gone when this runs (the
388
+ * watcher's close resolved), so the state-store entry is always dropped —
389
+ * even when we can't post a close row (the periodic DB sweep then heals the
390
+ * orphaned DB row instead of a later close being mis-stitched). Fail-open. */
391
+ export async function onStopWatcherClose(ctx, inputs) {
392
+ if (!ctx.stateStore)
393
+ return;
394
+ const stateEntry = ctx.stateStore.get(inputs.symbol);
395
+ const paperTrade = extractPaperTrade(inputs.order);
396
+ const closeAtMs = Date.now();
397
+ // Record the exit for re-entry indication regardless of journal linkage —
398
+ // the engine trade happened even if the journal never knew the position.
399
+ ctx.reentryTracker?.recordExit({
400
+ symbol: inputs.symbol,
401
+ setupType: stateEntry?.setupType ?? paperTrade?.setupType,
402
+ side: stateEntry?.side ?? 'long',
403
+ wasLoss: paperTrade ? paperTrade.netRealizedPnl < 0 : undefined,
404
+ closedAtMs: closeAtMs,
405
+ });
406
+ const dropState = () => { ctx.stateStore?.remove(inputs.symbol); };
407
+ if (!ctx.decisionsClient || !ctx.userId) {
408
+ dropState();
409
+ return;
410
+ }
411
+ if (!stateEntry?.webappPositionId) {
412
+ logger.warn(TAG, `onStopWatcherClose ${inputs.symbol}: no cached webappPositionId — dropping state; ` +
413
+ `periodic DB sweep will close any orphaned journal row`);
414
+ dropState();
415
+ return;
416
+ }
417
+ const order = inputs.order;
418
+ const fillPrice = (order ? fillPriceFromOrder(order) : null)
419
+ ?? (Number.isFinite(inputs.markPrice) && inputs.markPrice > 0 ? inputs.markPrice : null);
420
+ const filledQty = typeof order?.filled === 'number' && Number.isFinite(order.filled) && order.filled > 0
421
+ ? order.filled
422
+ : (typeof stateEntry.remainingContracts === 'number' && stateEntry.remainingContracts > 0
423
+ ? stateEntry.remainingContracts
424
+ : null);
425
+ if (fillPrice === null || filledQty === null) {
426
+ logger.warn(TAG, `onStopWatcherClose ${inputs.symbol}: fill data unresolvable — dropping state; ` +
427
+ `periodic DB sweep will close the journal row`);
428
+ dropState();
429
+ return;
430
+ }
431
+ const close = {
432
+ positionId: stateEntry.webappPositionId,
433
+ closeAt: closeAtMs,
434
+ closeReason: 'stop_watcher',
435
+ closeAssessment: {
436
+ note: 'Position auto-closed by the stop-watcher: mark crossed the pinned stopPrice. ' +
437
+ 'Journaled from the watcher close fill — no close_position call (issue #199).',
438
+ observedFrom: 'stop_watcher',
439
+ stop_price: inputs.stopPrice,
440
+ mark_price: inputs.markPrice,
441
+ ...(paperTrade
442
+ ? {
443
+ pnl_source: 'paper_engine_net',
444
+ pnl_gross: paperTrade.grossRealizedPnl,
445
+ fees: { open: paperTrade.openFee, close: paperTrade.closeFee },
446
+ }
447
+ : {}),
448
+ },
449
+ scorecardVerdict: 'NO_GO',
450
+ confluenceScore: 0,
451
+ regime: 'unknown',
452
+ regimeConfidence: 0.5,
453
+ fillPrice,
454
+ fillSize: Math.abs(filledQty),
455
+ realizedPnl: paperTrade?.netRealizedPnl ?? 0,
456
+ realizedR: 0,
457
+ mfeRAtClose: 0,
458
+ giveBackPctAtClose: 0,
459
+ ...(typeof order?.id === 'string' && order.id.length > 0 ? { exchangeTradeId: order.id } : {}),
460
+ };
461
+ ctx.decisionsClient.postClose(ctx.userId, close);
462
+ dropState();
463
+ logger.info(TAG, `stop-watcher close captured ${inputs.symbol} @ ${fillPrice} ` +
464
+ `(positionId=${stateEntry.webappPositionId.slice(0, 8)}…, ` +
465
+ `pnl=${paperTrade ? paperTrade.netRealizedPnl.toFixed(4) : 'server-side'})`);
466
+ }
467
+ /** Look up the metadata create_order stashed for this fill. Primary key is
468
+ * the exchange orderId (post-REST-ack `promote()`); the clientOrderId
469
+ * fallback covers the routine market-order race where the WS fill arrives
470
+ * BEFORE the REST response, i.e. before the promote — the pre-submission
471
+ * stash is keyed by the cid, which the WS event carries (`o.c`). */
472
+ function consumePendingEntry(ctx, fill) {
473
+ if (!ctx.pendingEntries)
474
+ return undefined;
475
+ return (ctx.pendingEntries.consume(fill.exchangeOrderId) ??
476
+ (fill.clientOrderId ? ctx.pendingEntries.consume(fill.clientOrderId) : undefined));
477
+ }
478
+ export async function onWsFillObserved(ctx, fill) {
479
+ if (!ctx.decisionsClient || !ctx.userId || !ctx.stateStore)
480
+ return;
481
+ if (fill.fillPrice <= 0 || fill.fillSize <= 0)
482
+ return;
483
+ // Reduce-only fills are exit-side. close_position tool exits are journaled by
484
+ // onClosePositionFilled; bracket SL/TP fills and other reduce-only closes
485
+ // bypass that path entirely and used to leave the journal position open
486
+ // forever (close-bypass class). When closeOnReduceOnlyFill is enabled, detect
487
+ // the position going flat from the fill(s) and journal an exact close.
488
+ if (fill.reduceOnly) {
489
+ await handleReduceOnlyExit(ctx, fill);
490
+ return;
491
+ }
492
+ const stateEntry = ctx.stateStore.get(fill.symbol);
493
+ const fillSide = fill.side === 'buy' ? 'long' : 'short';
494
+ const ts = fill.exchangeTimeMs ?? Date.now();
495
+ if (stateEntry) {
496
+ // Dedup: if the synchronous create_order path already captured this
497
+ // exact order (orderId match), the WS event is the same fill — skip.
498
+ if (stateEntry.openedFromExchangeTradeId === fill.exchangeOrderId) {
499
+ return;
500
+ }
501
+ if (stateEntry.side !== fillSide) {
502
+ // Opposite-side fill on an existing position. Almost certainly a manual
503
+ // flatten that bypassed close_position. Don't journal here — the
504
+ // operator should use close_position with reason+assessment instead.
505
+ logger.warn(TAG, `onWsFillObserved ${fill.symbol}: opposite-side fill on existing ${stateEntry.side} position; skipping (use close_position for rationale capture)`);
506
+ return;
507
+ }
508
+ // SCALE-IN: same symbol, same side, fresh fill.
509
+ if (!stateEntry.webappPositionId) {
510
+ logger.warn(TAG, `onWsFillObserved ${fill.symbol}: scale-in observed but no webappPositionId yet — skipping (parent upsert may have failed; will retry next fill)`);
511
+ return;
512
+ }
513
+ const pending = consumePendingEntry(ctx, fill);
514
+ const entry = buildEntryPayload({
515
+ positionId: stateEntry.webappPositionId,
516
+ isScaleIn: true,
517
+ ts,
518
+ fillPrice: fill.fillPrice,
519
+ fillSize: fill.fillSize,
520
+ exchangeTradeId: fill.exchangeTradeId,
521
+ metadata: pending?.metadata,
522
+ });
523
+ ctx.decisionsClient.postEntry(ctx.userId, entry);
524
+ // Grow tracked size so a later reduce-only exit detects flat correctly.
525
+ ctx.stateStore.addOpenContracts(fill.symbol, fill.fillSize);
526
+ logger.info(TAG, `scale-in captured ${fill.symbol} ${fillSide} ${fill.fillSize} @ ${fill.fillPrice} ` +
527
+ `(positionId=${stateEntry.webappPositionId.slice(0, 8)}…)`);
528
+ return;
529
+ }
530
+ // No state-store entry → NEW position via WS-driven fill. Replicates the
531
+ // synchronous-market-order path but pulls metadata from the pending cache
532
+ // (which create_order populated BEFORE submitting the order).
533
+ const pending = consumePendingEntry(ctx, fill);
534
+ ctx.stateStore.upsert({
535
+ symbol: fill.symbol,
536
+ openedAt: ts,
537
+ side: fillSide,
538
+ openedFromExchangeTradeId: fill.exchangeOrderId,
539
+ setupType: pending?.metadata?.setupType,
540
+ });
541
+ const upsert = {
542
+ symbol: fill.symbol,
543
+ positionOpenAt: ts,
544
+ side: fillSide,
545
+ currentSize: fill.fillSize,
546
+ avgEntryPrice: fill.fillPrice,
547
+ mode: ctx.resolveMode?.(),
548
+ exchange: ctx.venue,
549
+ };
550
+ const positionId = await ctx.decisionsClient.postPosition(ctx.userId, upsert);
551
+ if (!positionId) {
552
+ logger.warn(TAG, `onWsFillObserved ${fill.symbol}: webapp position upsert returned no id; entry row will not be posted`);
553
+ return;
554
+ }
555
+ ctx.stateStore.setWebappId(fill.symbol, positionId);
556
+ // Track size so a later reduce-only/bracket exit fill can detect flat + close.
557
+ ctx.stateStore.addOpenContracts(fill.symbol, fill.fillSize);
558
+ const entry = buildEntryPayload({
559
+ positionId,
560
+ isScaleIn: false,
561
+ ts,
562
+ fillPrice: fill.fillPrice,
563
+ fillSize: fill.fillSize,
564
+ exchangeTradeId: fill.exchangeTradeId,
565
+ metadata: pending?.metadata,
566
+ });
567
+ ctx.decisionsClient.postEntry(ctx.userId, entry);
568
+ logger.info(TAG, `entry captured (ws) ${fill.symbol} ${fillSide} ${fill.fillSize} @ ${fill.fillPrice} ` +
569
+ `(positionId=${positionId.slice(0, 8)}…, metadata=${pending ? 'cached' : 'none'})`);
570
+ }
571
+ /** Reduce-only exit fill handler — the close-bypass fix. When enabled, sums the
572
+ * fill's realised PnL into the tracked position and, once remaining size hits
573
+ * flat, posts an exact close decision row (Binance-exact realised PnL, real
574
+ * exit price) and drops the local state entry. Idempotent at the webapp
575
+ * (position_closes UNIQUE on position_id); fail-open. */
576
+ async function handleReduceOnlyExit(ctx, fill) {
577
+ if (!ctx.decisionsClient || !ctx.userId || !ctx.stateStore)
578
+ return;
579
+ // Gated: when off, preserve the legacy behaviour (reduce-only fills ignored).
580
+ if (!ctx.closeOnReduceOnlyFill)
581
+ return;
582
+ const stateEntry = ctx.stateStore.get(fill.symbol);
583
+ // No tracked entry (already cleaned, or never journaled) → nothing to close.
584
+ if (!stateEntry || !stateEntry.webappPositionId)
585
+ return;
586
+ const updated = ctx.stateStore.applyExitContracts(fill.symbol, fill.fillSize, fill.realizedPnl ?? 0);
587
+ if (!updated) {
588
+ // Size unknown for this entry (opened before tracking existed and not yet
589
+ // seeded). Can't decide flatness from fills — leave the reconciler/ALGO
590
+ // close-bypass path as the backstop.
591
+ logger.info(TAG, `reduce-only fill ${fill.symbol}: size untracked; deferring close to reconciler/ALGO backstop`);
592
+ return;
593
+ }
594
+ const remaining = updated.remainingContracts ?? 0;
595
+ if (remaining > FLAT_EPSILON) {
596
+ logger.info(TAG, `partial exit ${fill.symbol}: ${fill.fillSize} reduce-only fill, ${remaining} contracts remaining`);
597
+ return; // not flat yet — keep accumulating
598
+ }
599
+ // FLAT. Auto-journal an exact close ONLY for exchange-native bracket fills
600
+ // (the cid is one of our bracket legs). Non-bracket reduce-only flats are
601
+ // either close_position's own order (its WS fill can race ahead of
602
+ // onClosePositionFilled) or a manual/external close — both are handled by
603
+ // their own paths (close_position's rich reason+assessment, or the reconciler
604
+ // backstop). Closing here would clobber the agent's close reasoning, so defer.
605
+ const isBracket = fill.clientOrderId ? isBracketCid(fill.clientOrderId) : false;
606
+ if (!isBracket) {
607
+ logger.info(TAG, `${fill.symbol} flat via non-bracket reduce-only fill (cid=${fill.clientOrderId ?? 'none'}) — ` +
608
+ `deferring close to close_position / reconciler backstop (no clobber)`);
609
+ return; // do NOT remove — onClosePositionFilled / backstop owns this close
610
+ }
611
+ const realizedPnl = updated.realizedPnlAccum ?? 0;
612
+ const close = {
613
+ positionId: stateEntry.webappPositionId,
614
+ closeAt: fill.exchangeTimeMs ?? Date.now(),
615
+ closeReason: 'bracket_fill',
616
+ closeAssessment: {
617
+ note: 'Position closed by an exchange-native bracket SL/TP fill (reduce-only). ' +
618
+ 'Auto-journaled from the WS fill — no close_position call (close-bypass path).',
619
+ observedFrom: 'ws_reduce_only_fill',
620
+ clientOrderId: fill.clientOrderId,
621
+ },
622
+ scorecardVerdict: 'NO_GO',
623
+ confluenceScore: 0,
624
+ regime: 'unknown',
625
+ regimeConfidence: 0.5,
626
+ fillPrice: fill.fillPrice,
627
+ fillSize: Math.abs(fill.fillSize),
628
+ // realizedPnl is Binance-exact (summed across exit fills); the zeros below
629
+ // are filled in PER-METRIC by the webapp close route (price-based R from
630
+ // the pinned invalidation_price, mfe/give-back from the last review — see
631
+ // webapp lib/api/close-metrics.ts). The route's old all-four-zero gate
632
+ // meant this non-zero PnL used to suppress that fill-in entirely, persisting
633
+ // realized_r=0 on every bracket_fill close (the 2026-07-11 zero-hole).
634
+ realizedPnl,
635
+ realizedR: 0,
636
+ mfeRAtClose: 0,
637
+ giveBackPctAtClose: 0,
638
+ exchangeTradeId: fill.exchangeTradeId,
639
+ };
640
+ ctx.decisionsClient.postClose(ctx.userId, close);
641
+ ctx.reentryTracker?.recordExit({
642
+ symbol: fill.symbol,
643
+ setupType: stateEntry.setupType,
644
+ side: stateEntry.side,
645
+ wasLoss: realizedPnl < 0,
646
+ closedAtMs: fill.exchangeTimeMs ?? Date.now(),
647
+ });
648
+ ctx.stateStore.remove(fill.symbol);
649
+ logger.info(TAG, `close captured (ws bracket fill) ${fill.symbol} pnl=${realizedPnl.toFixed(4)} ` +
650
+ `(positionId=${stateEntry.webappPositionId.slice(0, 8)}…)`);
651
+ }
652
+ /** Internal helper — builds the PositionEntryPayload from a fill, applying the
653
+ * same regimeConfidence normalisation (0-100 → 0-1) as the synchronous path. */
654
+ function buildEntryPayload(args) {
655
+ const md = args.metadata;
656
+ const regimeConfNormalized = typeof md?.regimeConfidence === 'number'
657
+ ? Math.max(0, Math.min(1, md.regimeConfidence / 100))
658
+ : 0.5;
659
+ return {
660
+ positionId: args.positionId,
661
+ entryAt: args.ts,
662
+ isScaleIn: args.isScaleIn,
663
+ thesis: md?.thesis ?? '(no thesis recorded)',
664
+ setupType: md?.setupType ?? 'unknown',
665
+ regime: md?.regime ?? 'unknown',
666
+ regimeConfidence: regimeConfNormalized,
667
+ scorecardVerdict: md?.scorecardVerdict ?? 'GO',
668
+ confluenceScore: typeof md?.confluenceScore === 'number' ? md.confluenceScore : 0,
669
+ fillPrice: args.fillPrice,
670
+ fillSize: args.fillSize,
671
+ exchangeTradeId: args.exchangeTradeId,
672
+ metadata: buildEntryPlanMetadata(md),
673
+ };
674
+ }
675
+ /** Pinned-plan subset persisted to position_entries.metadata (free-form JSONB).
676
+ * Keys are snake_case per the canonical-JSONB-key rule (CLAUDE.md learning-loop:
677
+ * a camelCase mismatch previously broke learning matching). Returns undefined
678
+ * when neither field is present so we never write an empty object. */
679
+ export function buildEntryPlanMetadata(md) {
680
+ if (!md)
681
+ return undefined;
682
+ const out = {};
683
+ if (typeof md.invalidationPrice === 'number' && Number.isFinite(md.invalidationPrice)) {
684
+ out.invalidation_price = md.invalidationPrice;
685
+ }
686
+ // Pin the planned profit target too — it's the agent's stated target (and the
687
+ // bracket TP in live), surfaced on the dashboard pinned-plan view. Distinct
688
+ // from realization_rule (which is mostly omitted); without this the target is
689
+ // blank on the journal/Trade-card pinned-plan surfaces in the common case.
690
+ if (typeof md.targetPrice === 'number' && Number.isFinite(md.targetPrice)) {
691
+ out.target_price = md.targetPrice;
692
+ }
693
+ const rr = md.realizationRule;
694
+ if (rr) {
695
+ const j = { type: rr.type };
696
+ if (rr.targetPrice != null)
697
+ j.target_price = rr.targetPrice;
698
+ if (rr.trailAfterR != null)
699
+ j.trail_after_r = rr.trailAfterR;
700
+ if (rr.trailDistanceR != null)
701
+ j.trail_distance_r = rr.trailDistanceR;
702
+ if (rr.scale)
703
+ j.scale = rr.scale.map((s) => ({ at_r: s.atR, fraction: s.fraction }));
704
+ if (rr.note)
705
+ j.note = rr.note;
706
+ out.realization_rule = j;
707
+ }
708
+ return Object.keys(out).length > 0 ? out : undefined;
709
+ }