@reefclaw/connect 0.1.10 → 0.1.12

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 (396) hide show
  1. package/assets/bridge/gateway/event-parser.d.ts +6 -1
  2. package/assets/bridge/gateway/event-parser.js +19 -2
  3. package/assets/bridge/gateway/heartbeat-cron.d.ts +27 -0
  4. package/assets/bridge/gateway/heartbeat-cron.js +85 -0
  5. package/assets/bridge/gateway/poller.d.ts +1 -0
  6. package/assets/bridge/gateway/poller.js +14 -2
  7. package/assets/bridge/providers/gateway.d.ts +24 -2
  8. package/assets/bridge/providers/gateway.js +124 -25
  9. package/assets/bridge/setup.js +6 -51
  10. package/assets/shared/signals/conditions/registry.js +11 -2
  11. package/assets/shared/signals/strategy-adapter.js +17 -7
  12. package/assets/skill/SKILL.md +2 -0
  13. package/dist/deps.js +2 -2
  14. package/dist/plugin.js +35 -7
  15. package/package.json +1 -1
  16. package/assets/plugin/audit/mode-transition-audit.d.ts +0 -11
  17. package/assets/plugin/audit/mode-transition-audit.js +0 -29
  18. package/assets/plugin/balance-utils.d.ts +0 -36
  19. package/assets/plugin/balance-utils.js +0 -98
  20. package/assets/plugin/ccxt/binance-ban-gate.d.ts +0 -47
  21. package/assets/plugin/ccxt/binance-ban-gate.js +0 -420
  22. package/assets/plugin/ccxt/binance-private.d.ts +0 -346
  23. package/assets/plugin/ccxt/binance-private.js +0 -1525
  24. package/assets/plugin/ccxt/binance-public.d.ts +0 -34
  25. package/assets/plugin/ccxt/binance-public.js +0 -180
  26. package/assets/plugin/ccxt/intel-public.d.ts +0 -25
  27. package/assets/plugin/ccxt/intel-public.js +0 -80
  28. package/assets/plugin/ccxt/public-market-data-api.d.ts +0 -12
  29. package/assets/plugin/ccxt/public-market-data-api.js +0 -9
  30. package/assets/plugin/config/agent-config-client.d.ts +0 -77
  31. package/assets/plugin/config/agent-config-client.js +0 -188
  32. package/assets/plugin/config/agent-config-poller.d.ts +0 -32
  33. package/assets/plugin/config/agent-config-poller.js +0 -101
  34. package/assets/plugin/config/brackets-config.d.ts +0 -22
  35. package/assets/plugin/config/brackets-config.js +0 -58
  36. package/assets/plugin/config/entitlement-gate.d.ts +0 -51
  37. package/assets/plugin/config/entitlement-gate.js +0 -137
  38. package/assets/plugin/config/gate-store.d.ts +0 -18
  39. package/assets/plugin/config/gate-store.js +0 -61
  40. package/assets/plugin/config/plugin-config-io.d.ts +0 -215
  41. package/assets/plugin/config/plugin-config-io.js +0 -111
  42. package/assets/plugin/config/position-review-config.d.ts +0 -35
  43. package/assets/plugin/config/position-review-config.js +0 -105
  44. package/assets/plugin/config/tool-gate.d.ts +0 -53
  45. package/assets/plugin/config/tool-gate.js +0 -128
  46. package/assets/plugin/config/user-data-stream-config.d.ts +0 -89
  47. package/assets/plugin/config/user-data-stream-config.js +0 -239
  48. package/assets/plugin/connector-supervisor.d.ts +0 -36
  49. package/assets/plugin/connector-supervisor.js +0 -149
  50. package/assets/plugin/exchange-adapter.d.ts +0 -65
  51. package/assets/plugin/exchange-adapter.js +0 -4
  52. package/assets/plugin/index.d.ts +0 -30
  53. package/assets/plugin/index.js +0 -2678
  54. package/assets/plugin/ingest/pending-entry-metadata.d.ts +0 -74
  55. package/assets/plugin/ingest/pending-entry-metadata.js +0 -236
  56. package/assets/plugin/ingest/position-auto-capture.d.ts +0 -103
  57. package/assets/plugin/ingest/position-auto-capture.js +0 -411
  58. package/assets/plugin/ingest/position-decisions-client.d.ts +0 -322
  59. package/assets/plugin/ingest/position-decisions-client.js +0 -296
  60. package/assets/plugin/ingest/readiness-reporter.d.ts +0 -42
  61. package/assets/plugin/ingest/readiness-reporter.js +0 -163
  62. package/assets/plugin/ingest/reconcile-db-vs-exchange.d.ts +0 -13
  63. package/assets/plugin/ingest/reconcile-db-vs-exchange.js +0 -114
  64. package/assets/plugin/ingest/reconciler-cleanup.d.ts +0 -37
  65. package/assets/plugin/ingest/reconciler-cleanup.js +0 -147
  66. package/assets/plugin/ingest/rest-gap-filler.d.ts +0 -191
  67. package/assets/plugin/ingest/rest-gap-filler.js +0 -565
  68. package/assets/plugin/ingest/touched-symbols-store.d.ts +0 -25
  69. package/assets/plugin/ingest/touched-symbols-store.js +0 -96
  70. package/assets/plugin/ingest/trade-store-client.d.ts +0 -40
  71. package/assets/plugin/ingest/trade-store-client.js +0 -125
  72. package/assets/plugin/ingest/ws-ingest.d.ts +0 -43
  73. package/assets/plugin/ingest/ws-ingest.js +0 -126
  74. package/assets/plugin/learning/setup-family.d.ts +0 -21
  75. package/assets/plugin/learning/setup-family.js +0 -103
  76. package/assets/plugin/lifecycle/install-signal-handlers.d.ts +0 -33
  77. package/assets/plugin/lifecycle/install-signal-handlers.js +0 -112
  78. package/assets/plugin/lifecycle/shutdown-coordinator.d.ts +0 -43
  79. package/assets/plugin/lifecycle/shutdown-coordinator.js +0 -131
  80. package/assets/plugin/lifecycle/trading-operation-lock.d.ts +0 -17
  81. package/assets/plugin/lifecycle/trading-operation-lock.js +0 -14
  82. package/assets/plugin/live/bracket-id.d.ts +0 -26
  83. package/assets/plugin/live/bracket-id.js +0 -112
  84. package/assets/plugin/live/bracket-ledger.d.ts +0 -54
  85. package/assets/plugin/live/bracket-ledger.js +0 -267
  86. package/assets/plugin/live/bracket-manager.d.ts +0 -82
  87. package/assets/plugin/live/bracket-manager.js +0 -478
  88. package/assets/plugin/live/bracket-params.d.ts +0 -22
  89. package/assets/plugin/live/bracket-params.js +0 -124
  90. package/assets/plugin/live/bracket-reconciler.d.ts +0 -95
  91. package/assets/plugin/live/bracket-reconciler.js +0 -573
  92. package/assets/plugin/live/bracket-types.d.ts +0 -102
  93. package/assets/plugin/live/bracket-types.js +0 -8
  94. package/assets/plugin/live/deposit-tracker.d.ts +0 -62
  95. package/assets/plugin/live/deposit-tracker.js +0 -97
  96. package/assets/plugin/live/emergency-controls.d.ts +0 -32
  97. package/assets/plugin/live/emergency-controls.js +0 -226
  98. package/assets/plugin/live/exchange-errors.d.ts +0 -12
  99. package/assets/plugin/live/exchange-errors.js +0 -130
  100. package/assets/plugin/live/exchange-info-cache.d.ts +0 -37
  101. package/assets/plugin/live/exchange-info-cache.js +0 -134
  102. package/assets/plugin/live/fact-subscriber.d.ts +0 -78
  103. package/assets/plugin/live/fact-subscriber.js +0 -182
  104. package/assets/plugin/live/intent-journal.d.ts +0 -42
  105. package/assets/plugin/live/intent-journal.js +0 -122
  106. package/assets/plugin/live/listen-key-manager.d.ts +0 -70
  107. package/assets/plugin/live/listen-key-manager.js +0 -169
  108. package/assets/plugin/live/live-adapter.d.ts +0 -287
  109. package/assets/plugin/live/live-adapter.js +0 -1777
  110. package/assets/plugin/live/live-balance-enricher.d.ts +0 -32
  111. package/assets/plugin/live/live-balance-enricher.js +0 -104
  112. package/assets/plugin/live/live-bracket-api.d.ts +0 -13
  113. package/assets/plugin/live/live-bracket-api.js +0 -20
  114. package/assets/plugin/live/live-state-store.d.ts +0 -194
  115. package/assets/plugin/live/live-state-store.js +0 -450
  116. package/assets/plugin/live/local-signal-service.d.ts +0 -57
  117. package/assets/plugin/live/local-signal-service.js +0 -151
  118. package/assets/plugin/live/local-strategy-evaluator.d.ts +0 -62
  119. package/assets/plugin/live/local-strategy-evaluator.js +0 -131
  120. package/assets/plugin/live/microstructure-assembler.d.ts +0 -54
  121. package/assets/plugin/live/microstructure-assembler.js +0 -148
  122. package/assets/plugin/live/order-poller.d.ts +0 -29
  123. package/assets/plugin/live/order-poller.js +0 -125
  124. package/assets/plugin/live/position-state-store.d.ts +0 -83
  125. package/assets/plugin/live/position-state-store.js +0 -237
  126. package/assets/plugin/live/proposal-decision-listener.d.ts +0 -70
  127. package/assets/plugin/live/proposal-decision-listener.js +0 -292
  128. package/assets/plugin/live/proposal-manager.d.ts +0 -76
  129. package/assets/plugin/live/proposal-manager.js +0 -140
  130. package/assets/plugin/live/rate-limiter.d.ts +0 -47
  131. package/assets/plugin/live/rate-limiter.js +0 -159
  132. package/assets/plugin/live/reconciler.d.ts +0 -39
  133. package/assets/plugin/live/reconciler.js +0 -175
  134. package/assets/plugin/live/setup-buckets.d.ts +0 -7
  135. package/assets/plugin/live/setup-buckets.js +0 -33
  136. package/assets/plugin/live/slippage-tracker.d.ts +0 -45
  137. package/assets/plugin/live/slippage-tracker.js +0 -78
  138. package/assets/plugin/live/stop-watcher.d.ts +0 -60
  139. package/assets/plugin/live/stop-watcher.js +0 -215
  140. package/assets/plugin/live/user-data-active-probe.d.ts +0 -54
  141. package/assets/plugin/live/user-data-active-probe.js +0 -180
  142. package/assets/plugin/live/user-data-stream-controller.d.ts +0 -200
  143. package/assets/plugin/live/user-data-stream-controller.js +0 -579
  144. package/assets/plugin/live/user-data-stream-ws.d.ts +0 -22
  145. package/assets/plugin/live/user-data-stream-ws.js +0 -63
  146. package/assets/plugin/live/user-data-stream.d.ts +0 -243
  147. package/assets/plugin/live/user-data-stream.js +0 -704
  148. package/assets/plugin/logger.d.ts +0 -2
  149. package/assets/plugin/logger.js +0 -2
  150. package/assets/plugin/mfe.d.ts +0 -21
  151. package/assets/plugin/mfe.js +0 -68
  152. package/assets/plugin/onboarding/mode-ladder.d.ts +0 -1
  153. package/assets/plugin/onboarding/mode-ladder.js +0 -3
  154. package/assets/plugin/onboarding/runtime.d.ts +0 -84
  155. package/assets/plugin/onboarding/runtime.js +0 -173
  156. package/assets/plugin/openclaw.plugin.json +0 -92
  157. package/assets/plugin/paper-adapter.d.ts +0 -24
  158. package/assets/plugin/paper-adapter.js +0 -91
  159. package/assets/plugin/persistence/state-manager.d.ts +0 -66
  160. package/assets/plugin/persistence/state-manager.js +0 -222
  161. package/assets/plugin/pinned-plan.d.ts +0 -9
  162. package/assets/plugin/pinned-plan.js +0 -23
  163. package/assets/plugin/portfolio/wave9-admission.d.ts +0 -67
  164. package/assets/plugin/portfolio/wave9-admission.js +0 -262
  165. package/assets/plugin/portfolio/wave9-policy.d.ts +0 -36
  166. package/assets/plugin/portfolio/wave9-policy.js +0 -183
  167. package/assets/plugin/risk/pre-trade-check.d.ts +0 -38
  168. package/assets/plugin/risk/pre-trade-check.js +0 -345
  169. package/assets/plugin/risk/pre-trade-types.d.ts +0 -60
  170. package/assets/plugin/risk/pre-trade-types.js +0 -3
  171. package/assets/plugin/shadow/shadow-tracker.d.ts +0 -36
  172. package/assets/plugin/shadow/shadow-tracker.js +0 -151
  173. package/assets/plugin/shadow/types.d.ts +0 -42
  174. package/assets/plugin/shadow/types.js +0 -20
  175. package/assets/plugin/shared/indicators-extended.d.ts +0 -52
  176. package/assets/plugin/shared/indicators-extended.js +0 -291
  177. package/assets/plugin/shared/indicators.d.ts +0 -15
  178. package/assets/plugin/shared/indicators.js +0 -114
  179. package/assets/plugin/signals/conditions/registry.d.ts +0 -16
  180. package/assets/plugin/signals/conditions/registry.js +0 -1324
  181. package/assets/plugin/signals/conditions/types.d.ts +0 -1
  182. package/assets/plugin/signals/conditions/types.js +0 -4
  183. package/assets/plugin/signals/direction-rules.d.ts +0 -3
  184. package/assets/plugin/signals/direction-rules.js +0 -24
  185. package/assets/plugin/signals/entry-rules.d.ts +0 -6
  186. package/assets/plugin/signals/entry-rules.js +0 -33
  187. package/assets/plugin/signals/serialize-context.d.ts +0 -4
  188. package/assets/plugin/signals/serialize-context.js +0 -39
  189. package/assets/plugin/signals/stop-rules.d.ts +0 -3
  190. package/assets/plugin/signals/stop-rules.js +0 -48
  191. package/assets/plugin/signals/strategy-adapter.d.ts +0 -47
  192. package/assets/plugin/signals/strategy-adapter.js +0 -199
  193. package/assets/plugin/signals/types.d.ts +0 -1
  194. package/assets/plugin/signals/types.js +0 -8
  195. package/assets/plugin/simulator/exchange-simulator.d.ts +0 -93
  196. package/assets/plugin/simulator/exchange-simulator.js +0 -689
  197. package/assets/plugin/simulator/fill-engine.d.ts +0 -53
  198. package/assets/plugin/simulator/fill-engine.js +0 -280
  199. package/assets/plugin/simulator/paper-market-feed.d.ts +0 -26
  200. package/assets/plugin/simulator/paper-market-feed.js +0 -104
  201. package/assets/plugin/simulator/realistic-fills.d.ts +0 -59
  202. package/assets/plugin/simulator/realistic-fills.js +0 -175
  203. package/assets/plugin/simulator/types.d.ts +0 -224
  204. package/assets/plugin/simulator/types.js +0 -43
  205. package/assets/plugin/strategy/builtin-strategies.d.ts +0 -2
  206. package/assets/plugin/strategy/builtin-strategies.js +0 -113
  207. package/assets/plugin/strategy/condition-registry.d.ts +0 -3
  208. package/assets/plugin/strategy/condition-registry.js +0 -153
  209. package/assets/plugin/strategy/evaluator.d.ts +0 -70
  210. package/assets/plugin/strategy/evaluator.js +0 -98
  211. package/assets/plugin/tools/assessment-validation.d.ts +0 -120
  212. package/assets/plugin/tools/assessment-validation.js +0 -415
  213. package/assets/plugin/tools/attach-brackets.d.ts +0 -39
  214. package/assets/plugin/tools/attach-brackets.js +0 -399
  215. package/assets/plugin/tools/audit-bracket-protection.d.ts +0 -49
  216. package/assets/plugin/tools/audit-bracket-protection.js +0 -527
  217. package/assets/plugin/tools/cancel-all-orders.d.ts +0 -9
  218. package/assets/plugin/tools/cancel-all-orders.js +0 -8
  219. package/assets/plugin/tools/cancel-order.d.ts +0 -14
  220. package/assets/plugin/tools/cancel-order.js +0 -60
  221. package/assets/plugin/tools/check-position-health.d.ts +0 -46
  222. package/assets/plugin/tools/check-position-health.js +0 -194
  223. package/assets/plugin/tools/clear-exchange-credentials.d.ts +0 -24
  224. package/assets/plugin/tools/clear-exchange-credentials.js +0 -70
  225. package/assets/plugin/tools/close-position.d.ts +0 -45
  226. package/assets/plugin/tools/close-position.js +0 -722
  227. package/assets/plugin/tools/create-order.d.ts +0 -82
  228. package/assets/plugin/tools/create-order.js +0 -1516
  229. package/assets/plugin/tools/exit-gate.d.ts +0 -58
  230. package/assets/plugin/tools/exit-gate.js +0 -162
  231. package/assets/plugin/tools/fetch-balance.d.ts +0 -5
  232. package/assets/plugin/tools/fetch-balance.js +0 -4
  233. package/assets/plugin/tools/fetch-ohlcv.d.ts +0 -11
  234. package/assets/plugin/tools/fetch-ohlcv.js +0 -8
  235. package/assets/plugin/tools/fetch-open-orders.d.ts +0 -7
  236. package/assets/plugin/tools/fetch-open-orders.js +0 -4
  237. package/assets/plugin/tools/fetch-positions.d.ts +0 -7
  238. package/assets/plugin/tools/fetch-positions.js +0 -4
  239. package/assets/plugin/tools/fetch-ticker.d.ts +0 -11
  240. package/assets/plugin/tools/fetch-ticker.js +0 -5
  241. package/assets/plugin/tools/get-agent-profile.d.ts +0 -4
  242. package/assets/plugin/tools/get-agent-profile.js +0 -6
  243. package/assets/plugin/tools/get-analytics.d.ts +0 -6
  244. package/assets/plugin/tools/get-analytics.js +0 -7
  245. package/assets/plugin/tools/get-backtest.d.ts +0 -12
  246. package/assets/plugin/tools/get-backtest.js +0 -91
  247. package/assets/plugin/tools/get-basis.d.ts +0 -7
  248. package/assets/plugin/tools/get-basis.js +0 -7
  249. package/assets/plugin/tools/get-bracket-config.d.ts +0 -11
  250. package/assets/plugin/tools/get-bracket-config.js +0 -24
  251. package/assets/plugin/tools/get-cascade-risk.d.ts +0 -7
  252. package/assets/plugin/tools/get-cascade-risk.js +0 -8
  253. package/assets/plugin/tools/get-crypto-metrics.d.ts +0 -18
  254. package/assets/plugin/tools/get-crypto-metrics.js +0 -62
  255. package/assets/plugin/tools/get-cvd.d.ts +0 -6
  256. package/assets/plugin/tools/get-cvd.js +0 -6
  257. package/assets/plugin/tools/get-divergences.d.ts +0 -6
  258. package/assets/plugin/tools/get-divergences.js +0 -6
  259. package/assets/plugin/tools/get-funding-context.d.ts +0 -6
  260. package/assets/plugin/tools/get-funding-context.js +0 -16
  261. package/assets/plugin/tools/get-liquidation-levels.d.ts +0 -7
  262. package/assets/plugin/tools/get-liquidation-levels.js +0 -7
  263. package/assets/plugin/tools/get-liquidation-pulse.d.ts +0 -9
  264. package/assets/plugin/tools/get-liquidation-pulse.js +0 -22
  265. package/assets/plugin/tools/get-market-breadth.d.ts +0 -6
  266. package/assets/plugin/tools/get-market-breadth.js +0 -8
  267. package/assets/plugin/tools/get-market-intel.d.ts +0 -19
  268. package/assets/plugin/tools/get-market-intel.js +0 -116
  269. package/assets/plugin/tools/get-market-structure.d.ts +0 -47
  270. package/assets/plugin/tools/get-market-structure.js +0 -198
  271. package/assets/plugin/tools/get-my-mined-patterns.d.ts +0 -20
  272. package/assets/plugin/tools/get-my-mined-patterns.js +0 -61
  273. package/assets/plugin/tools/get-my-proposed-learnings.d.ts +0 -20
  274. package/assets/plugin/tools/get-my-proposed-learnings.js +0 -55
  275. package/assets/plugin/tools/get-my-recent-reviews.d.ts +0 -22
  276. package/assets/plugin/tools/get-my-recent-reviews.js +0 -66
  277. package/assets/plugin/tools/get-orderbook.d.ts +0 -21
  278. package/assets/plugin/tools/get-orderbook.js +0 -32
  279. package/assets/plugin/tools/get-pattern-scan.d.ts +0 -7
  280. package/assets/plugin/tools/get-pattern-scan.js +0 -8
  281. package/assets/plugin/tools/get-regime.d.ts +0 -6
  282. package/assets/plugin/tools/get-regime.js +0 -7
  283. package/assets/plugin/tools/get-relevant-learnings.d.ts +0 -21
  284. package/assets/plugin/tools/get-relevant-learnings.js +0 -65
  285. package/assets/plugin/tools/get-resting-liquidity.d.ts +0 -6
  286. package/assets/plugin/tools/get-resting-liquidity.js +0 -11
  287. package/assets/plugin/tools/get-risk-scenario.d.ts +0 -29
  288. package/assets/plugin/tools/get-risk-scenario.js +0 -47
  289. package/assets/plugin/tools/get-risk-summary.d.ts +0 -51
  290. package/assets/plugin/tools/get-risk-summary.js +0 -118
  291. package/assets/plugin/tools/get-sentiment.d.ts +0 -4
  292. package/assets/plugin/tools/get-sentiment.js +0 -6
  293. package/assets/plugin/tools/get-session-review.d.ts +0 -7
  294. package/assets/plugin/tools/get-session-review.js +0 -8
  295. package/assets/plugin/tools/get-setup-detail.d.ts +0 -7
  296. package/assets/plugin/tools/get-setup-detail.js +0 -311
  297. package/assets/plugin/tools/get-signals.d.ts +0 -15
  298. package/assets/plugin/tools/get-signals.js +0 -54
  299. package/assets/plugin/tools/get-sizing.d.ts +0 -6
  300. package/assets/plugin/tools/get-sizing.js +0 -6
  301. package/assets/plugin/tools/get-trade-feedback.d.ts +0 -7
  302. package/assets/plugin/tools/get-trade-feedback.js +0 -8
  303. package/assets/plugin/tools/get-trade-flow.d.ts +0 -7
  304. package/assets/plugin/tools/get-trade-flow.js +0 -7
  305. package/assets/plugin/tools/get-volume-analysis.d.ts +0 -21
  306. package/assets/plugin/tools/get-volume-analysis.js +0 -74
  307. package/assets/plugin/tools/get-volume-profile.d.ts +0 -7
  308. package/assets/plugin/tools/get-volume-profile.js +0 -7
  309. package/assets/plugin/tools/get-wave9-status.d.ts +0 -127
  310. package/assets/plugin/tools/get-wave9-status.js +0 -796
  311. package/assets/plugin/tools/helpers.d.ts +0 -26
  312. package/assets/plugin/tools/helpers.js +0 -39
  313. package/assets/plugin/tools/intel-api.d.ts +0 -34
  314. package/assets/plugin/tools/intel-api.js +0 -119
  315. package/assets/plugin/tools/intel-cache.d.ts +0 -25
  316. package/assets/plugin/tools/intel-cache.js +0 -148
  317. package/assets/plugin/tools/list-strategies.d.ts +0 -17
  318. package/assets/plugin/tools/list-strategies.js +0 -23
  319. package/assets/plugin/tools/modify-stop.d.ts +0 -21
  320. package/assets/plugin/tools/modify-stop.js +0 -120
  321. package/assets/plugin/tools/modify-target.d.ts +0 -21
  322. package/assets/plugin/tools/modify-target.js +0 -110
  323. package/assets/plugin/tools/propose-learning.d.ts +0 -22
  324. package/assets/plugin/tools/propose-learning.js +0 -65
  325. package/assets/plugin/tools/query-review-outcomes.d.ts +0 -30
  326. package/assets/plugin/tools/query-review-outcomes.js +0 -64
  327. package/assets/plugin/tools/query-trades.d.ts +0 -21
  328. package/assets/plugin/tools/query-trades.js +0 -37
  329. package/assets/plugin/tools/record-position-reviews.d.ts +0 -38
  330. package/assets/plugin/tools/record-position-reviews.js +0 -147
  331. package/assets/plugin/tools/save-strategy.d.ts +0 -16
  332. package/assets/plugin/tools/save-strategy.js +0 -46
  333. package/assets/plugin/tools/scan-pairs.d.ts +0 -18
  334. package/assets/plugin/tools/scan-pairs.js +0 -231
  335. package/assets/plugin/tools/score-setup.d.ts +0 -31
  336. package/assets/plugin/tools/score-setup.js +0 -268
  337. package/assets/plugin/tools/set-bracket-requirement.d.ts +0 -18
  338. package/assets/plugin/tools/set-bracket-requirement.js +0 -81
  339. package/assets/plugin/tools/set-exchange-credentials.d.ts +0 -25
  340. package/assets/plugin/tools/set-exchange-credentials.js +0 -80
  341. package/assets/plugin/tools/set-trading-mode.d.ts +0 -26
  342. package/assets/plugin/tools/set-trading-mode.js +0 -135
  343. package/assets/plugin/tools/test-exchange-credentials.d.ts +0 -16
  344. package/assets/plugin/tools/test-exchange-credentials.js +0 -100
  345. package/assets/plugin/tools/toggle-strategy.d.ts +0 -8
  346. package/assets/plugin/tools/toggle-strategy.js +0 -15
  347. package/assets/plugin/trading-params-cache.d.ts +0 -26
  348. package/assets/plugin/trading-params-cache.js +0 -52
  349. package/assets/plugin/types.d.ts +0 -134
  350. package/assets/plugin/types.js +0 -7
  351. package/assets/plugin/util/plugin-paths.d.ts +0 -3
  352. package/assets/plugin/util/plugin-paths.js +0 -15
  353. package/assets/plugin/venues/hyperliquid/hl-balance.d.ts +0 -116
  354. package/assets/plugin/venues/hyperliquid/hl-balance.js +0 -145
  355. package/assets/plugin/venues/hyperliquid/hl-brackets.d.ts +0 -102
  356. package/assets/plugin/venues/hyperliquid/hl-brackets.js +0 -172
  357. package/assets/plugin/venues/hyperliquid/hl-cloid.d.ts +0 -22
  358. package/assets/plugin/venues/hyperliquid/hl-cloid.js +0 -82
  359. package/assets/plugin/venues/hyperliquid/hl-info-cache.d.ts +0 -46
  360. package/assets/plugin/venues/hyperliquid/hl-info-cache.js +0 -125
  361. package/assets/plugin/venues/hyperliquid/hl-live-adapter.d.ts +0 -88
  362. package/assets/plugin/venues/hyperliquid/hl-live-adapter.js +0 -353
  363. package/assets/plugin/venues/hyperliquid/hl-precision.d.ts +0 -61
  364. package/assets/plugin/venues/hyperliquid/hl-precision.js +0 -176
  365. package/assets/plugin/venues/hyperliquid/hl-private.d.ts +0 -88
  366. package/assets/plugin/venues/hyperliquid/hl-private.js +0 -357
  367. package/assets/plugin/venues/hyperliquid/hl-public.d.ts +0 -79
  368. package/assets/plugin/venues/hyperliquid/hl-public.js +0 -429
  369. package/assets/plugin/venues/hyperliquid/hl-rate-gate.d.ts +0 -57
  370. package/assets/plugin/venues/hyperliquid/hl-rate-gate.js +0 -220
  371. package/assets/plugin/venues/hyperliquid/hl-user-stream.d.ts +0 -90
  372. package/assets/plugin/venues/hyperliquid/hl-user-stream.js +0 -220
  373. package/assets/plugin/venues/registry.d.ts +0 -38
  374. package/assets/plugin/venues/registry.js +0 -46
  375. package/assets/plugin/venues/symbols.d.ts +0 -43
  376. package/assets/plugin/venues/symbols.js +0 -107
  377. package/assets/plugin/wave9/live-account-capture.d.ts +0 -67
  378. package/assets/plugin/wave9/live-account-capture.js +0 -435
  379. package/assets/plugin/wave9/live-autonomous-protection.d.ts +0 -39
  380. package/assets/plugin/wave9/live-autonomous-protection.js +0 -112
  381. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.d.ts +0 -33
  382. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.js +0 -115
  383. package/assets/plugin/wave9/live-execution-ledger.d.ts +0 -107
  384. package/assets/plugin/wave9/live-execution-ledger.js +0 -498
  385. package/assets/plugin/wave9/live-position-confirmation.d.ts +0 -18
  386. package/assets/plugin/wave9/live-position-confirmation.js +0 -111
  387. package/assets/plugin/wave9/live-residual-protection.d.ts +0 -18
  388. package/assets/plugin/wave9/live-residual-protection.js +0 -250
  389. package/assets/plugin/wave9/live-startup-reconciliation.d.ts +0 -38
  390. package/assets/plugin/wave9/live-startup-reconciliation.js +0 -454
  391. package/assets/plugin/wave9/live-symbol-ownership.d.ts +0 -20
  392. package/assets/plugin/wave9/live-symbol-ownership.js +0 -132
  393. package/assets/plugin/wave9/paper-admission-guard.d.ts +0 -199
  394. package/assets/plugin/wave9/paper-admission-guard.js +0 -650
  395. package/assets/plugin/wave9/usdm-evidence-provider.d.ts +0 -42
  396. package/assets/plugin/wave9/usdm-evidence-provider.js +0 -133
@@ -1,2678 +0,0 @@
1
- // ReefClaw Trading Plugin — OpenClaw plugin entry point.
2
- // Registers 40 trading + analysis + learning tools with the OpenClaw gateway.
3
- // Supports three modes: PAPER → MICRO_LIVE → LIVE.
4
- // Paper mode: simulated execution, no API keys needed.
5
- //
6
- // CRITICAL: register() MUST be synchronous — OpenClaw silently ignores async registration.
7
- // CRITICAL: Agent needs "group:plugins" in tools.sandbox.tools.allow in openclaw.json.
8
- // See memory/connection.md for full deployment guide.
9
- import { readFileSync, existsSync } from 'node:fs';
10
- import { homedir } from 'node:os';
11
- import { join, dirname } from 'node:path';
12
- import { fileURLToPath } from 'node:url';
13
- import { BinancePublicApi } from './ccxt/binance-public.js';
14
- import { IntelPublicApi } from './ccxt/intel-public.js';
15
- import { BinancePrivateApi } from './ccxt/binance-private.js';
16
- import { ExchangeSimulator } from './simulator/exchange-simulator.js';
17
- import { ShadowTracker } from './shadow/shadow-tracker.js';
18
- import { StateManager } from './persistence/state-manager.js';
19
- import { logger, setLogLevel, formatError } from './logger.js';
20
- import { DEFAULT_CONFIG } from './types.js';
21
- import { PaperAdapter } from './paper-adapter.js';
22
- import { LiveAdapter, } from './live/live-adapter.js';
23
- import { createLiveAdapter, fillExchangeId, isLiveVenueSupported, parseVenue, venueQuoteCurrency, } from './venues/registry.js';
24
- import { HyperliquidPublicApi } from './venues/hyperliquid/hl-public.js';
25
- import { bracketsEnabled, loadBracketMode } from './config/brackets-config.js';
26
- import { loadUserDataStreamMode, loadUserDataStreamTunables, loadUserDataStreamDbWrite, getUserDataStreamIngestBaseUrl, resolveIngestToken, resolveReefclawUserId, } from './config/user-data-stream-config.js';
27
- import { TradeStoreClient } from './ingest/trade-store-client.js';
28
- import { ProposalManager } from './live/proposal-manager.js';
29
- import { ProposalDecisionListener } from './live/proposal-decision-listener.js';
30
- import { PositionDecisionsClient } from './ingest/position-decisions-client.js';
31
- import { PositionStateStore } from './live/position-state-store.js';
32
- import { PendingEntryStore } from './ingest/pending-entry-metadata.js';
33
- import { onReconcilerObservedClose, reconcileStateStoreOnStartup } from './ingest/reconciler-cleanup.js';
34
- import { reconcileDbOpenVsExchange } from './ingest/reconcile-db-vs-exchange.js';
35
- import { startReadinessReporter } from './ingest/readiness-reporter.js';
36
- import { IntelMicrostructureAssembler } from './live/microstructure-assembler.js';
37
- import { recordPositionReviewsTool } from './tools/record-position-reviews.js';
38
- import { getMyRecentReviewsTool } from './tools/get-my-recent-reviews.js';
39
- import { getRelevantLearningsTool } from './tools/get-relevant-learnings.js';
40
- import { getMyMinedPatternsTool } from './tools/get-my-mined-patterns.js';
41
- import { getMyProposedLearningsTool } from './tools/get-my-proposed-learnings.js';
42
- import { proposeLearningTool } from './tools/propose-learning.js';
43
- import { queryReviewOutcomesTool } from './tools/query-review-outcomes.js';
44
- import { loadPositionReviewMode } from './config/position-review-config.js';
45
- import { installSignalHandlers } from './lifecycle/install-signal-handlers.js';
46
- import { SerialTradingOperationLock } from './lifecycle/trading-operation-lock.js';
47
- import { readPluginConfig, readOpenClawConnection } from './config/plugin-config-io.js';
48
- import { startConnectorSupervisor, hasBundledBridge } from './connector-supervisor.js';
49
- import { ToolGate } from './config/tool-gate.js';
50
- import { gateStore } from './config/gate-store.js';
51
- import { entitlementGate } from './config/entitlement-gate.js';
52
- import { startAgentConfigPoller } from './config/agent-config-poller.js';
53
- import { PositionWatcher } from './live/stop-watcher.js';
54
- import { PaperMarketFeed } from './simulator/paper-market-feed.js';
55
- import { PluginRuntime } from './onboarding/runtime.js';
56
- import { getQuoteBalance, getQuoteWalletBalance } from './balance-utils.js';
57
- import { setExchangeCredentialsTool } from './tools/set-exchange-credentials.js';
58
- import { setTradingModeTool } from './tools/set-trading-mode.js';
59
- import { getBracketConfigTool } from './tools/get-bracket-config.js';
60
- import { setBracketRequirementTool } from './tools/set-bracket-requirement.js';
61
- import { testExchangeCredentialsTool } from './tools/test-exchange-credentials.js';
62
- import { clearExchangeCredentialsTool } from './tools/clear-exchange-credentials.js';
63
- // Tool implementations
64
- import { fetchTickerTool } from './tools/fetch-ticker.js';
65
- import { fetchOhlcvTool } from './tools/fetch-ohlcv.js';
66
- import { fetchBalanceTool } from './tools/fetch-balance.js';
67
- import { fetchPositionsTool } from './tools/fetch-positions.js';
68
- import { fetchOpenOrdersTool } from './tools/fetch-open-orders.js';
69
- import { createOrderTool } from './tools/create-order.js';
70
- import { cancelOrderTool } from './tools/cancel-order.js';
71
- import { cancelAllOrdersTool } from './tools/cancel-all-orders.js';
72
- import { closePositionTool } from './tools/close-position.js';
73
- import { modifyStopTool } from './tools/modify-stop.js';
74
- import { modifyTargetTool } from './tools/modify-target.js';
75
- import { attachBracketsTool } from './tools/attach-brackets.js';
76
- import { auditBracketProtectionTool } from './tools/audit-bracket-protection.js';
77
- import { getMarketStructureTool } from './tools/get-market-structure.js';
78
- import { computeVolFactor } from './risk/pre-trade-check.js';
79
- import { getCryptoMetricsTool } from './tools/get-crypto-metrics.js';
80
- import { getVolumeAnalysisTool } from './tools/get-volume-analysis.js';
81
- import { getRiskSummaryTool } from './tools/get-risk-summary.js';
82
- import { getMarketIntelTool } from './tools/get-market-intel.js';
83
- import { getRegimeTool } from './tools/get-regime.js';
84
- import { getSignalsTool } from './tools/get-signals.js';
85
- import { LocalSignalService } from './live/local-signal-service.js';
86
- import { getAnalyticsTool } from './tools/get-analytics.js';
87
- import { getOrderbookTool } from './tools/get-orderbook.js';
88
- import { getVolumeProfileTool } from './tools/get-volume-profile.js';
89
- import { getLiquidationLevelsTool } from './tools/get-liquidation-levels.js';
90
- import { getTradeFeedbackTool } from './tools/get-trade-feedback.js';
91
- import { getTradeFlowTool } from './tools/get-trade-flow.js';
92
- import { scoreSetupTool } from './tools/score-setup.js';
93
- import { checkPositionHealthTool } from './tools/check-position-health.js';
94
- import { getSizingTool } from './tools/get-sizing.js';
95
- import { getRiskScenarioTool } from './tools/get-risk-scenario.js';
96
- import { getSessionReviewTool } from './tools/get-session-review.js';
97
- import { getBacktestTool } from './tools/get-backtest.js';
98
- import { saveStrategyTool } from './tools/save-strategy.js';
99
- import { listStrategiesTool, prepareListStrategiesArgs } from './tools/list-strategies.js';
100
- import { toggleStrategyTool } from './tools/toggle-strategy.js';
101
- import { getPatternScanTool } from './tools/get-pattern-scan.js';
102
- import { scanPairsTool } from './tools/scan-pairs.js';
103
- import { getSetupDetailTool } from './tools/get-setup-detail.js';
104
- import { queryTradesTool } from './tools/query-trades.js';
105
- import { getAgentProfileTool } from './tools/get-agent-profile.js';
106
- import { getCvdTool } from './tools/get-cvd.js';
107
- import { getMarketBreadthTool } from './tools/get-market-breadth.js';
108
- import { getBasisTool } from './tools/get-basis.js';
109
- import { getCascadeRiskTool } from './tools/get-cascade-risk.js';
110
- import { getSentimentTool } from './tools/get-sentiment.js';
111
- import { getDivergencesTool } from './tools/get-divergences.js';
112
- import { getLiquidationPulseTool } from './tools/get-liquidation-pulse.js';
113
- import { getRestingLiquidityTool } from './tools/get-resting-liquidity.js';
114
- import { getFundingContextTool } from './tools/get-funding-context.js';
115
- import { captureWave9PaperAccountSnapshot, captureWave9PaperPositionFingerprint, Wave9PaperAdmissionGuard, } from './wave9/paper-admission-guard.js';
116
- import { captureWave9LiveAccount, captureWave9LivePosition, confirmWave9LiveNativeStop, } from './wave9/live-account-capture.js';
117
- import { Wave9LiveExecutionLedger } from './wave9/live-execution-ledger.js';
118
- import { recoverWave9LiveExposure } from './wave9/live-autonomous-protection.js';
119
- import { reconcileWave9LiveDurableRows, } from './wave9/live-startup-reconciliation.js';
120
- import { Wave9LiveDurableReconciliationScheduler } from './wave9/live-durable-reconciliation-scheduler.js';
121
- import { inspectWave9LiveSymbolOwnership, } from './wave9/live-symbol-ownership.js';
122
- import { getWave9StatusTool, IntelWave9ActivationClient, } from './tools/get-wave9-status.js';
123
- import { refreshTradingParams } from './trading-params-cache.js';
124
- const TAG = 'paper-trading';
125
- const PLUGIN_ID = 'reefclaw-paper-trading';
126
- /* eslint-enable @typescript-eslint/no-explicit-any */
127
- // ---- Tool parameter schemas (JSON Schema / TypeBox compatible at runtime) ----
128
- const TOOL_PARAMS = {
129
- fetch_ticker: {
130
- type: 'object',
131
- properties: {
132
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
133
- },
134
- required: ['symbol'],
135
- },
136
- fetch_ohlcv: {
137
- type: 'object',
138
- properties: {
139
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
140
- timeframe: { type: 'string', description: 'Candle timeframe: 1m, 5m, 15m, 1h, 4h, 1d', default: '1h' },
141
- limit: { type: 'number', description: 'Number of candles to fetch (max 1000)', default: 100 },
142
- },
143
- required: ['symbol'],
144
- },
145
- fetch_balance: {
146
- type: 'object',
147
- properties: {},
148
- },
149
- fetch_positions: {
150
- type: 'object',
151
- properties: {
152
- symbol: { type: 'string', description: 'Filter by symbol (optional)' },
153
- },
154
- },
155
- fetch_open_orders: {
156
- type: 'object',
157
- properties: {
158
- symbol: { type: 'string', description: 'Filter by symbol (optional)' },
159
- },
160
- },
161
- create_order: {
162
- type: 'object',
163
- properties: {
164
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
165
- side: { type: 'string', enum: ['buy', 'sell'], description: 'Order side' },
166
- type: { type: 'string', enum: ['market', 'limit'], description: 'Order type' },
167
- amount: { type: 'number', description: 'Quantity in base currency (e.g. BTC amount)' },
168
- price: { type: 'number', description: 'Limit price (required for limit orders)' },
169
- stopPrice: { type: 'number', description: 'Protective stop-loss trigger price. REQUIRED in live mode when brackets.requireStopLoss is on (default). Must be below entry for longs, above entry for shorts.' },
170
- mission_id: { type: 'string', description: 'Canonical mission ID for attribution. Wave 9 requires the exact mission ID returned by get_wave9_status.' },
171
- candidate_id: { type: 'string', description: 'Wave 9 only: exact candidate ID returned by get_wave9_status. Invalid on generic orders.' },
172
- wave9_admission_token: { type: 'string', description: 'Wave 9 only: short-lived single-use PAPER/LIVE admission token returned by get_wave9_status. Invalid on generic orders.' },
173
- setup_type: { type: 'string', description: 'Setup classification (e.g. pullback_to_ema, breakout, mean_reversion). REQUIRED in live mode.' },
174
- thesis: { type: 'string', description: 'Entry reason in 1-2 sentences (≥20 chars). REQUIRED in live mode — the heartbeat reassessment compares against this.' },
175
- target_price: { type: 'number', description: 'Planned profit target price. REQUIRED in live mode when brackets.requireTakeProfit is on (default).' },
176
- regime: { type: 'string', description: 'Market regime at entry (e.g. TREND_UP, RANGE_TIGHT). REQUIRED in live mode — heartbeat uses this to detect regime flips.' },
177
- regime_confidence: { type: 'number', description: 'Regime confidence 0-100. REQUIRED in live mode.' },
178
- scorecard_verdict: { type: 'string', enum: ['STRONG_GO', 'GO', 'MARGINAL', 'NO_GO'], description: 'Scorecard verdict. REQUIRED in live mode.' },
179
- confluence_score: { type: 'number', description: 'Composite scorecard score 0-10. REQUIRED in live mode — heartbeat reassessment compares delta against this.' },
180
- invalidation_price: { type: 'number', description: 'Optional (recommended in live). The price at which this entry thesis is WRONG — your pinned "I am wrong here" level, distinct from the protective stopPrice. Surfaced back to you each heartbeat so you hold to your own plan instead of re-deciding. Long: below entry. Short: above entry.' },
181
- realization_rule: { type: 'object', description: 'Optional (recommended in live). Your PLAN for taking profit, pinned at entry — indication, NOT auto-enforced. type=fixed_target|trail|scale|manual.', properties: { type: { type: 'string', enum: ['fixed_target', 'trail', 'scale', 'manual'] }, target_price: { type: 'number', description: 'For fixed_target.' }, trail_after_r: { type: 'number', description: 'For trail: begin trailing after +N R.' }, trail_distance_r: { type: 'number', description: 'For trail: distance behind peak, in R.' }, scale: { type: 'array', description: 'For scale: ladder rungs.', items: { type: 'object', properties: { at_r: { type: 'number' }, fraction: { type: 'number' } } } }, note: { type: 'string', description: 'Free-text plan summary.' } } },
182
- supersedes_id: { type: 'string', description: 'Optional. When the operator is discussing a pending proposal in chat and wants a modification, pass the proposal_id from the chat-context header here. The webapp atomically marks the old proposal `superseded` and links the new one. No effect when approval mode is off. See SKILL.md Approval Mode section.' },
183
- },
184
- required: ['symbol', 'side', 'type', 'amount'],
185
- },
186
- cancel_order: {
187
- type: 'object',
188
- properties: {
189
- id: { type: 'string', description: 'Order ID to cancel' },
190
- symbol: { type: 'string', description: 'Optional symbol (e.g. BTC/USDT). Binance Futures requires a symbol for the underlying cancel call; if omitted, the plugin auto-resolves it from the open-orders snapshot. Provide it when you already know it to skip the lookup.' },
191
- },
192
- required: ['id'],
193
- },
194
- cancel_all_orders: {
195
- type: 'object',
196
- properties: {
197
- symbol: { type: 'string', description: 'Cancel orders for this symbol only (optional)' },
198
- },
199
- },
200
- close_position: {
201
- type: 'object',
202
- description: 'Close an open position at market. REQUIRES a documented reason and (except for operator_command) a structured assessment — see SKILL.md v2.10.0 Position Management Discipline. Brackets are the default exit; use this only for the five legitimate triggers.',
203
- properties: {
204
- symbol: { type: 'string', description: 'Symbol of the position to close' },
205
- reason: {
206
- type: 'string',
207
- enum: ['regime_flip', 'risk_limit', 'bracket_integrity', 'operator_command', 'wave9_signal_reversal'],
208
- description: 'Why you are closing manually instead of letting brackets handle it. wave9_signal_reversal is reserved for an exact get_wave9_status exit authorization.',
209
- },
210
- candidate_id: { type: 'string', description: 'Wave 9 only: exact held candidate ID returned by get_wave9_status.' },
211
- wave9_exit_token: { type: 'string', description: 'Wave 9 only: short-lived single-use PAPER/LIVE signal-reversal exit token returned by get_wave9_status.' },
212
- assessment: {
213
- type: 'object',
214
- description: 'Structured reasoning — populated from a fresh scorecard run this heartbeat. Required for all reasons except operator_command. Reason-specific consistency is enforced server-side.',
215
- properties: {
216
- entry_score: { type: 'number', description: 'Scorecard confluence score at entry (0-10).' },
217
- current_score: { type: 'number', description: 'Telemetry only — fresh scorecard confluence this heartbeat (0-10). NOT an exit trigger (scorecard_reversal is deprecated).' },
218
- entry_regime: { type: 'string', description: 'Regime label at entry.' },
219
- current_regime: { type: 'string', description: 'Fresh regime label this heartbeat. For regime_flip, must differ from entry_regime.' },
220
- contradicting_metric: { type: 'string', description: 'Specific metric that flipped or crossed threshold. Required for regime_flip, risk_limit, bracket_integrity.' },
221
- position_age_seconds: { type: 'number', description: 'Telemetry only — how long the position has been open.' },
222
- r_multiple_at_close: { type: 'number', description: 'Telemetry only — R-multiple (profit/risk ratio) at close time.' },
223
- },
224
- },
225
- },
226
- required: ['symbol', 'reason'],
227
- },
228
- modify_stop: {
229
- type: 'object',
230
- properties: {
231
- symbol: { type: 'string', description: 'Symbol of the open live position' },
232
- new_stop_price: { type: 'number', description: 'New stop-loss price (must be on the protective side of entry)' },
233
- },
234
- required: ['symbol', 'new_stop_price'],
235
- },
236
- modify_target: {
237
- type: 'object',
238
- properties: {
239
- symbol: { type: 'string', description: 'Symbol of the open live position' },
240
- new_target_price: { type: 'number', description: 'New take-profit price (must be on the winning side of entry)' },
241
- },
242
- required: ['symbol', 'new_target_price'],
243
- },
244
- attach_brackets: {
245
- type: 'object',
246
- properties: {
247
- symbol: { type: 'string', description: 'Symbol of the open live position that needs protection' },
248
- stop_price: { type: 'number', description: 'Stop-loss price (must be on the protective side of entry). Optional, but at least one of stop_price or target_price must be provided.' },
249
- target_price: { type: 'number', description: 'Take-profit price (must be on the winning side of entry). Optional, but at least one of stop_price or target_price must be provided.' },
250
- },
251
- required: ['symbol'],
252
- },
253
- audit_bracket_protection: {
254
- type: 'object',
255
- properties: {},
256
- },
257
- record_position_reviews: {
258
- type: 'object',
259
- description: 'File a structured per-heartbeat review for every open position in one plural call. Pass one entry per open position; the validator REJECTS the whole call if any review is missing required fields, internally inconsistent, or contradicts itself (e.g. verdict=hold + thesis_status=invalidated). Required when positionReview.mode is shadow/observe/enforce. See SKILL.md "Position Reviews" + docs/POSITION_DECISION_JOURNAL_PLAN.md.',
260
- properties: {
261
- reviews: {
262
- type: 'array',
263
- description: 'One review per currently-open position. Order does not matter; validator enforces uniqueness by symbol.',
264
- items: {
265
- type: 'object',
266
- properties: {
267
- symbol: { type: 'string', description: 'Position symbol (e.g. BTC/USDT, no settle suffix).' },
268
- position_open_at: { type: 'number', description: 'Epoch ms when the position opened — match the value the heartbeat snapshot gave you.' },
269
- verdict: {
270
- type: 'string',
271
- enum: ['hold', 'add_on', 'close_recommended'],
272
- description: 'hold = thesis intact, no action; add_on = scale in (requires +0.5R AND +1.5 score delta); close_recommended = next heartbeat I will close unless something changes.',
273
- },
274
- thesis_status: {
275
- type: 'string',
276
- enum: ['intact', 'weakening', 'invalidated', 'evolving'],
277
- description: 'Health of the original entry thesis. invalidated forbids verdict=hold (call close_position instead).',
278
- },
279
- entry_score: { type: 'number', description: 'Confluence score frozen at entry (0-10).' },
280
- current_score: { type: 'number', description: 'Fresh confluence score this heartbeat (0-10).' },
281
- score_delta: { type: 'number', description: 'current_score - entry_score. Validator checks consistency to ±0.01.' },
282
- confluence_components: {
283
- type: 'object',
284
- description: 'REQUIRED. Which scorecard gates fire NOW, as a named-boolean object. Concrete shape: `{regime: true, structure: true, momentum: false, flow: true}` (four standard gates). At minimum supply ONE key; an empty `{}` passes the validator but corrupts the journal. Pre-2026-05-15 agents kept omitting this and burning a retry round-trip per heartbeat — it is documented here AND in the required[] array below.',
285
- },
286
- regime_entry: { type: 'string', description: 'Regime at entry.' },
287
- regime_current: { type: 'string', description: 'Regime now.' },
288
- regime_confidence: { type: 'number', description: 'Current regime confidence in [0, 1].' },
289
- r_multiple: { type: 'number', description: 'Current PnL in R-multiples (denominated against original stop). Cannot exceed mfe_r.' },
290
- distance_to_stop_bps: { type: 'number', description: 'Distance from current mark to stop, in basis points.' },
291
- distance_to_target_bps: { type: 'number', description: 'Distance from current mark to target, in basis points (optional).' },
292
- time_in_position_minutes: { type: 'number', description: 'Minutes since entry.' },
293
- mfe_r: { type: 'number', description: 'Max favourable excursion since entry, in R-multiples.' },
294
- give_back_pct: { type: 'number', description: 'Fraction of MFE peak surrendered: (mfe_r - r_multiple) / mfe_r when mfe_r > 0.' },
295
- what_changed: { type: 'string', description: 'What shifted since the previous review. ≥20 chars. Avoid template-filling — name the specific signal/level/event.' },
296
- invalidation_trigger: { type: 'string', description: 'The price/event/time that flips this position to verdict=close_recommended next cycle.' },
297
- },
298
- required: [
299
- 'symbol',
300
- 'position_open_at',
301
- 'verdict',
302
- 'thesis_status',
303
- 'entry_score',
304
- 'current_score',
305
- 'score_delta',
306
- 'confluence_components',
307
- 'regime_entry',
308
- 'regime_current',
309
- 'regime_confidence',
310
- 'r_multiple',
311
- 'distance_to_stop_bps',
312
- 'time_in_position_minutes',
313
- 'mfe_r',
314
- 'give_back_pct',
315
- 'what_changed',
316
- 'invalidation_trigger',
317
- ],
318
- },
319
- },
320
- },
321
- required: ['reviews'],
322
- },
323
- get_my_recent_reviews: {
324
- type: 'object',
325
- description: 'Phase 1 self-reflection: returns your last N position reviews per open position, plus a verdict-streak counter (e.g. "verdict=hold × 4 cycles, thesis_status: intact → weakening → weakening → weakening") and recent same-symbol re-entry context (last 24h opens / closes / realized R, minutes since last close). Call this BEFORE record_position_reviews each heartbeat. Without args returns context for ALL open positions. With symbol returns just that symbol.',
326
- properties: {
327
- symbol: {
328
- type: 'string',
329
- description: 'Optional. Canonical un-suffixed symbol (e.g. BTC/USDT). When omitted returns context for every currently-open position.',
330
- },
331
- review_limit: {
332
- type: 'number',
333
- description: 'Number of prior reviews to return per position (1–10, default 3).',
334
- },
335
- },
336
- },
337
- get_relevant_learnings: {
338
- type: 'object',
339
- description: 'Phase 4 curated learnings: returns up to 5 operator-confirmed learnings whose trigger_condition matches the current decision context. Each learning is a short directive (e.g. "Avoid macd_oversold_reversal entries unless funding < 30th percentile") backed by stats evidence from a mined pattern. Call BEFORE create_order (applies_at=entry), BEFORE record_position_reviews (applies_at=heartbeat), and BEFORE close_position (applies_at=close). Learnings INFORM your decision — they do not gate it. If a learning conflicts with your in-context reasoning, name the conflict in your thesis (entry) or what_changed (review). Empty list = no curated learning matches the current context; proceed normally.',
340
- properties: {
341
- applies_at: {
342
- type: 'string',
343
- enum: ['entry', 'heartbeat', 'close'],
344
- description: 'Which decision point you are currently at.',
345
- },
346
- setup_type: {
347
- type: 'string',
348
- description: 'Optional. Setup_type the entry/review/close is about (e.g. "breakout"). Filters learnings whose trigger_condition.setup_type matches.',
349
- },
350
- regime: {
351
- type: 'string',
352
- description: 'Optional. Current regime (e.g. "trending_up"). Filters learnings whose trigger_condition.regime matches.',
353
- },
354
- verdict: {
355
- type: 'string',
356
- enum: ['hold', 'add_on', 'close_recommended'],
357
- description: 'Optional, for applies_at=heartbeat. Verdict you intend to file. Filters learnings whose trigger_condition.verdict matches.',
358
- },
359
- },
360
- required: ['applies_at'],
361
- },
362
- get_my_mined_patterns: {
363
- type: 'object',
364
- description: 'Phase 4.5 self-assessment: read your own daily-mined statistical patterns from your own trade history. Each row is a pattern with sample_size, effect_size, p_value, match_conditions (e.g. {"setupType":"macd_oversold_reversal"}), and a human_summary. Use BEFORE propose_learning to pick which patterns deserve a directive. Filter by within_days (default 14), min_sample_size (default 20), max_p_value (default 0.05). Includes only your own user_id data — never other users. Returns at most 50 rows ranked by absolute effect size.',
365
- properties: {
366
- within_days: {
367
- type: 'number',
368
- description: 'Mined-pattern recency window in days (default 14, max 90).',
369
- },
370
- min_sample_size: {
371
- type: 'number',
372
- description: 'Minimum trades in the pattern to consider (default 20).',
373
- },
374
- max_p_value: {
375
- type: 'number',
376
- description: 'Maximum p-value (default 0.05). Statistical significance bar.',
377
- },
378
- include_promoted: {
379
- type: 'boolean',
380
- description: 'When true, also returns patterns already promoted to learnings. Default false.',
381
- },
382
- },
383
- },
384
- get_my_proposed_learnings: {
385
- type: 'object',
386
- description: 'Phase 4.5 self-audit: read your own learnings across all confidence states (hypothesis / confirmed / retired). Distinct from get_relevant_learnings, which returns only confirmed + context-matched. Call BEFORE propose_learning so you do not draft a duplicate of something already pending review or curated. Returns rows tagged with proposed_by (agent / operator) and auto_confirmed.',
387
- properties: {
388
- include_hypothesis: { type: 'boolean', description: 'Include hypothesis state (default true).' },
389
- include_confirmed: { type: 'boolean', description: 'Include confirmed state (default true).' },
390
- include_retired: { type: 'boolean', description: 'Include retired state (default false).' },
391
- proposed_by: {
392
- type: 'string',
393
- enum: ['agent', 'operator'],
394
- description: 'Optional: filter to drafts by agent or by operator.',
395
- },
396
- },
397
- },
398
- propose_learning: {
399
- type: 'object',
400
- description: 'Phase 4.5: draft a new learning candidate for the operator to review. Server-side rules: proposed_by is forced to "agent"; confidence is server-determined from the operator\'s trust mode (manual → hypothesis, auto → confirmed). Throttle: max 3 proposals per rolling 24h per user. You MUST cite evidence — either by supplying source_mined_pattern_id (recommended; auto-attaches the pattern stats) or by supplying evidence.sample_size + evidence.p_value yourself. Directive style: quote n / effect size / p-value / a recent concrete example / default action / override conditions. Operators retain agency; learnings inform, they do not gate.',
401
- properties: {
402
- applies_at: {
403
- type: 'string',
404
- enum: ['entry', 'heartbeat', 'close'],
405
- description: 'Which decision point this directive applies to.',
406
- },
407
- title: {
408
- type: 'string',
409
- description: 'Short title for the learning (operator sees this in the curation list). Min 4 chars.',
410
- },
411
- directive: {
412
- type: 'string',
413
- description: 'The full directive the agent will read at decision time. Include n, effect size, p-value, recent example, default action, and override conditions. Min 20 chars.',
414
- },
415
- source_mined_pattern_id: {
416
- type: 'string',
417
- description: 'UUID of the mined_pattern row this proposal is derived from (recommended). The server auto-attaches the pattern stats and back-links promotion.',
418
- },
419
- trigger_condition: {
420
- type: 'object',
421
- description: 'Optional. When the directive should match. E.g. {"setupType":"macd_oversold_reversal"} or {"verdict":"hold","thesisStatus":"weakening"}. If omitted with source_mined_pattern_id, the pattern\'s match_conditions are used.',
422
- },
423
- evidence: {
424
- type: 'object',
425
- description: 'Optional. Stats backing the directive. Required when source_mined_pattern_id is omitted: must contain sample_size (positive number) and p_value (0..1).',
426
- },
427
- },
428
- required: ['applies_at', 'title', 'directive'],
429
- },
430
- query_review_outcomes: {
431
- type: 'object',
432
- description: 'Phase 2 outcome attribution: aggregate stats over your past position reviews joined to their close outcomes. Returns count, win rate, median R, mean R, median give-back, p25/p75 R distribution, and a histogram of close_reason — over reviews matching the filters. Plus a small newest-first sample. Use to find verdict patterns that bleed edge: if (verdict=hold + thesis_status=weakening) shows median R < 0 across 5+ closed samples, that combination is consistently underperforming and your future verdicts in similar conditions need stronger justification. Call at session start (and daily after first fill) for your most-used (verdict, thesis_status) combinations.',
433
- properties: {
434
- verdict: {
435
- type: 'string',
436
- enum: ['hold', 'add_on', 'close_recommended'],
437
- description: 'Filter to reviews with this verdict.',
438
- },
439
- thesis_status: {
440
- type: 'string',
441
- enum: ['intact', 'weakening', 'invalidated', 'evolving'],
442
- description: 'Filter to reviews with this thesis_status.',
443
- },
444
- regime_current: { type: 'string', description: 'Filter to reviews where regime_current matched (exact match).' },
445
- symbol: { type: 'string', description: 'Filter to reviews on this symbol (canonical un-suffixed).' },
446
- side: { type: 'string', enum: ['long', 'short'], description: 'Filter to reviews on positions of this side.' },
447
- setup_type: { type: 'string', description: 'Filter to reviews on positions whose first non-scale-in entry used this setup_type.' },
448
- days_back: {
449
- type: 'number',
450
- description: 'Window length in days from now (default 7, max 90). Only reviews filed within this window are counted.',
451
- },
452
- min_score_delta: { type: 'number', description: 'Filter to reviews with score_delta >= this (e.g. -1.0).' },
453
- max_score_delta: { type: 'number', description: 'Filter to reviews with score_delta <= this.' },
454
- closed_only: {
455
- type: 'boolean',
456
- description: 'When true, includes only reviews on positions that have closed. Default false (all reviews; outcome columns NULL for open positions).',
457
- },
458
- sample_limit: { type: 'number', description: 'Number of sample reviews to return alongside the stats (1–25, default 5).' },
459
- },
460
- },
461
- get_market_structure: {
462
- type: 'object',
463
- properties: {
464
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
465
- timeframes: {
466
- type: 'array',
467
- items: { type: 'string' },
468
- description: 'Timeframes to analyze (default: all). Options: 5m, 15m, 1h, 4h, 1d, 1w',
469
- },
470
- },
471
- required: ['symbol'],
472
- },
473
- get_crypto_metrics: {
474
- type: 'object',
475
- properties: {
476
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
477
- },
478
- required: ['symbol'],
479
- },
480
- get_volume_analysis: {
481
- type: 'object',
482
- properties: {
483
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
484
- timeframe: { type: 'string', description: 'Candle timeframe for volume analysis (default: 5m)', default: '5m' },
485
- },
486
- required: ['symbol'],
487
- },
488
- get_risk_summary: {
489
- type: 'object',
490
- properties: {},
491
- },
492
- get_market_intel: {
493
- type: 'object',
494
- properties: {
495
- category: {
496
- type: 'string',
497
- enum: ['sentiment', 'news', 'calendar', 'overview', 'derivatives', 'onchain', 'social'],
498
- description: 'Category of market intelligence: sentiment (fear/greed, dominance), news (crypto headlines), calendar (economic events), overview (top coins, trending), derivatives (OI, funding, long/short ratios), onchain (BTC network, mempool, whale txs), social (community stats, trending posts)',
499
- },
500
- symbols: {
501
- type: 'array',
502
- items: { type: 'string' },
503
- description: 'Optional symbols to filter results (e.g. ["BTC", "ETH"])',
504
- },
505
- },
506
- required: ['category'],
507
- },
508
- get_regime: {
509
- type: 'object',
510
- properties: {
511
- symbol: { type: 'string', description: 'Symbol to classify, e.g. BTCUSDT' },
512
- },
513
- required: ['symbol'],
514
- },
515
- get_signals: {
516
- type: 'object',
517
- properties: {
518
- symbol: { type: 'string', description: 'Symbol to get signals for, e.g. BTCUSDT' },
519
- hours: { type: 'number', description: 'Hours of signal history to include (default: 24)', default: 24 },
520
- },
521
- required: ['symbol'],
522
- },
523
- get_wave9_status: {
524
- type: 'object',
525
- properties: {},
526
- additionalProperties: false,
527
- },
528
- get_analytics: {
529
- type: 'object',
530
- properties: {
531
- symbol: { type: 'string', description: 'Symbol to get analytics for, e.g. BTCUSDT' },
532
- },
533
- required: ['symbol'],
534
- },
535
- get_orderbook: {
536
- type: 'object',
537
- properties: {
538
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
539
- depth: { type: 'number', description: 'Number of price levels (5-100, default: 20)', default: 20 },
540
- },
541
- required: ['symbol'],
542
- },
543
- get_volume_profile: {
544
- type: 'object',
545
- properties: {
546
- symbol: { type: 'string', description: 'Symbol to get volume profile for, e.g. BTCUSDT' },
547
- period: { type: 'number', description: 'Hours of data to aggregate (24, 168, 720). Default: 24', default: 24 },
548
- },
549
- required: ['symbol'],
550
- },
551
- get_liquidation_levels: {
552
- type: 'object',
553
- properties: {
554
- symbol: { type: 'string', description: 'Symbol to get liquidation levels for, e.g. BTCUSDT' },
555
- hours: { type: 'number', description: 'Hours of liquidation history (1-168). Default: 24', default: 24 },
556
- },
557
- required: ['symbol'],
558
- },
559
- get_trade_feedback: {
560
- type: 'object',
561
- properties: {
562
- symbol: { type: 'string', description: 'Symbol to get feedback for, e.g. BTCUSDT' },
563
- hours: { type: 'number', description: 'Hours of trade history to analyze (default: 168 = 7 days)', default: 168 },
564
- },
565
- required: ['symbol'],
566
- },
567
- get_trade_flow: {
568
- type: 'object',
569
- properties: {
570
- symbol: { type: 'string', description: 'Symbol to get trade flow for, e.g. BTCUSDT' },
571
- minutes: { type: 'number', description: 'Minutes of flow data to aggregate (1-60, default: 5)', default: 5 },
572
- },
573
- required: ['symbol'],
574
- },
575
- score_setup: {
576
- type: 'object',
577
- properties: {
578
- symbol: { type: 'string', description: 'Symbol (intelligence format), e.g. BTCUSDT' },
579
- direction: { type: 'string', enum: ['LONG', 'SHORT'], description: 'Trade direction' },
580
- setup_type: { type: 'string', description: 'Setup type tag, e.g. pullback_to_ema, breakout_retest' },
581
- entry_price: { type: 'number', description: 'Planned entry price' },
582
- stop_price: { type: 'number', description: 'Planned stop-loss price' },
583
- target_price: { type: 'number', description: 'Planned take-profit price' },
584
- },
585
- required: ['symbol', 'direction', 'setup_type', 'entry_price', 'stop_price', 'target_price'],
586
- },
587
- get_sizing: {
588
- type: 'object',
589
- properties: {
590
- symbol: { type: 'string', description: 'Symbol (intelligence format), e.g. BTCUSDT' },
591
- },
592
- required: ['symbol'],
593
- },
594
- check_position_health: {
595
- type: 'object',
596
- properties: {
597
- positions: {
598
- type: 'array',
599
- items: {
600
- type: 'object',
601
- properties: {
602
- symbol: { type: 'string', description: 'Trading pair, e.g. BTC/USDT' },
603
- side: { type: 'string', enum: ['LONG', 'SHORT'], description: 'Position direction' },
604
- entry_price: { type: 'number', description: 'Entry price' },
605
- current_price: { type: 'number', description: 'Current market price' },
606
- stop_price: { type: 'number', description: 'Current stop-loss price' },
607
- target_price: { type: 'number', description: 'Target price (optional)' },
608
- quantity: { type: 'number', description: 'Position size in base currency' },
609
- entry_time: { type: 'string', description: 'Position open time (ISO 8601)' },
610
- },
611
- required: ['symbol', 'side', 'entry_price', 'current_price', 'stop_price', 'quantity', 'entry_time'],
612
- },
613
- description: 'Array of open positions to check',
614
- },
615
- account_balance: { type: 'number', description: 'Total account balance in USDT' },
616
- },
617
- required: ['positions', 'account_balance'],
618
- },
619
- get_risk_scenario: {
620
- type: 'object',
621
- properties: {
622
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
623
- moves: { type: 'string', description: 'Comma-separated % moves to test, e.g. "-3,-5,+5". If omitted, auto-generates ±1/2/3 ATR scenarios.' },
624
- },
625
- required: ['symbol'],
626
- },
627
- get_session_review: {
628
- type: 'object',
629
- properties: {
630
- symbol: { type: 'string', description: 'Symbol (intelligence format), e.g. BTCUSDT' },
631
- hours: { type: 'number', description: 'Hours of trading history to review (default: 24, max: 720)', default: 24 },
632
- },
633
- required: ['symbol'],
634
- },
635
- get_backtest: {
636
- type: 'object',
637
- properties: {
638
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
639
- strategy: { type: 'string', description: 'Strategy to test: trend_continuation, liquidity_sweep, funding_reversion, or "all" (default: all). Also accepts custom strategy names from the Strategy Box.' },
640
- months: { type: 'number', description: 'How many months of history to backtest (default: 6)' },
641
- sizing: { type: 'string', description: 'Position sizing method: "fixed" (default) or "kelly"' },
642
- regime_filter: { type: 'string', description: 'Comma-separated regimes to trade in, e.g. "TREND_UP,TREND_DOWN". If omitted, trades in all regimes each strategy allows.' },
643
- timeframe: { type: 'string', description: 'Candle timeframe for evaluation: "15m", "1h" (default), "4h", "1d". Shorter = more trades but slower. Indicators adapt to the chosen timeframe.' },
644
- },
645
- required: ['symbol'],
646
- },
647
- get_pattern_scan: {
648
- type: 'object',
649
- properties: {
650
- symbol: { type: 'string', description: 'Trading pair symbol, e.g. BTC/USDT' },
651
- timeframe: { type: 'string', description: 'Candle timeframe to analyze (default: 1h). Options: 5m, 15m, 1h, 4h' },
652
- },
653
- required: ['symbol'],
654
- },
655
- save_strategy: {
656
- type: 'object',
657
- properties: {
658
- name: { type: 'string', description: 'Unique strategy name (lowercase, underscores, 3-50 chars), e.g. "ema_bounce_trending"' },
659
- description: { type: 'string', description: 'Human-readable description of what this strategy does' },
660
- regimes: { type: 'string', description: 'Comma-separated regimes: TREND_UP, TREND_DOWN, RANGE_TIGHT, RANGE_WIDE' },
661
- direction_rule: { type: 'string', description: 'How to determine trade direction: from_regime, from_sweep, from_funding, fixed_long, fixed_short' },
662
- conditions: { type: 'string', description: 'JSON array of conditions: [{"type":"ema_proximity","params":{"period":21,"maxDistPct":0.003}}, ...]. 23 types: ema_proximity, orderbook_imbalance, funding_not_crowded, oi_slope, no_liquidation_cluster, price_sweep, liquidations_at_sweep, order_flow_absorption, funding_contrarian, funding_extreme, oi_elevated, price_at_level, macd_crossover, macd_divergence, bollinger_squeeze, bollinger_breakout, vwap_position, stoch_rsi_extreme, adx_trending, adx_ranging, ichimoku_cloud, obv_divergence, supertrend_direction' },
663
- entry_type: { type: 'string', description: 'Entry zone rule: ema_offset (params: period, atrFraction), sweep_range, atr_offset (params: atrFraction)' },
664
- entry_params: { type: 'string', description: 'JSON params for entry rule, e.g. {"period":21,"atrFraction":0.3}' },
665
- stop_type: { type: 'string', description: 'Stop rule: swing_atr (params: swingCount, atrFraction, fallbackAtrMultiple), recent_extreme_atr (params: atrFraction), fixed_atr (params: atrMultiple)' },
666
- stop_params: { type: 'string', description: 'JSON params for stop rule, e.g. {"swingCount":3,"atrFraction":0.3}' },
667
- target_r_multiples: { type: 'string', description: 'Comma-separated R-multiple targets, e.g. "1.5,2.5,3.5"' },
668
- },
669
- required: ['name', 'regimes', 'direction_rule', 'conditions', 'entry_type', 'stop_type', 'target_r_multiples'],
670
- },
671
- list_strategies: {
672
- type: 'object',
673
- properties: {
674
- active_only: { type: 'boolean', description: 'Required. Use true for every question about strategies active, usable, available, or tradeable now; use false only when the operator explicitly asks to include inactive strategies.' },
675
- },
676
- required: ['active_only'],
677
- additionalProperties: false,
678
- },
679
- toggle_strategy: {
680
- type: 'object',
681
- properties: {
682
- name: { type: 'string', description: 'Strategy name to activate/deactivate' },
683
- active: { type: 'boolean', description: 'true to activate, false to deactivate' },
684
- },
685
- required: ['name', 'active'],
686
- },
687
- scan_pairs: {
688
- type: 'object',
689
- properties: {
690
- min_score: { type: 'number', description: 'Minimum confluence score (0-10) to include in results (default: 4). Lower = more results, higher = stricter filter.' },
691
- symbols: {
692
- type: 'array',
693
- items: { type: 'string' },
694
- description: 'Optional symbol whitelist (e.g. ["BTCUSDT","ETHUSDT","SOLUSDT"]). When omitted scans all 20 supported pairs. Use this to focus the scan on a specific watchlist when you already know which pairs you care about.',
695
- },
696
- },
697
- },
698
- get_setup_detail: {
699
- type: 'object',
700
- properties: {
701
- symbol: { type: 'string', description: 'Symbol to get detailed setup for, e.g. SOLUSDT, BTCUSDT' },
702
- },
703
- required: ['symbol'],
704
- },
705
- query_trades: {
706
- type: 'object',
707
- properties: {
708
- symbol: { type: 'string', description: 'Filter by symbol, e.g. BTCUSDT' },
709
- setup_type: { type: 'string', description: 'Filter by setup type, e.g. pullback_to_ema' },
710
- strategy: { type: 'string', description: 'Filter by strategy name' },
711
- regime: { type: 'string', description: 'Filter by regime: TREND_UP, TREND_DOWN, RANGE_TIGHT, RANGE_WIDE, VOLATILITY_EXPANSION, AVOID' },
712
- direction: { type: 'string', enum: ['LONG', 'SHORT'], description: 'Filter by trade direction' },
713
- outcome: { type: 'string', enum: ['win', 'loss'], description: 'Filter by outcome (win = positive P&L, loss = negative)' },
714
- hours: { type: 'number', description: 'Look back N hours (default: all time)' },
715
- min_duration_seconds: { type: 'number', description: 'Min hold duration in seconds' },
716
- max_duration_seconds: { type: 'number', description: 'Max hold duration in seconds' },
717
- min_pnl_pct: { type: 'number', description: 'Min realized P&L % (e.g. -5 for losses > 5%)' },
718
- max_pnl_pct: { type: 'number', description: 'Max realized P&L %' },
719
- min_confluence_score: { type: 'number', description: 'Min confluence score at entry' },
720
- feature_filters: { type: 'string', description: 'JSON array of feature_snapshot filters: [{"field":"adx","op":"gt","value":40}]. Available fields: adx, rsi_14, funding_rate, funding_zscore, oi_ratio, oi_slope, stoch_rsi, bb_width, vwap_distance, macd_histogram, supertrend_direction, volume_ratio, atr_pct, obv_slope, imbalance_ratio, ema20_distance, ema50_distance, ema200_distance, williams_r' },
721
- group_by: { type: 'string', description: 'Aggregate results by: setup_type, strategy, regime, direction, or symbol. Returns win_rate, expectancy, count, avg_pnl per group.' },
722
- limit: { type: 'number', description: 'Max results (default: 200, max: 500)' },
723
- },
724
- },
725
- get_agent_profile: {
726
- type: 'object',
727
- properties: {},
728
- },
729
- get_cvd: {
730
- type: 'object',
731
- properties: {
732
- symbol: { type: 'string', description: 'Symbol to get CVD for, e.g. BTCUSDT' },
733
- },
734
- required: ['symbol'],
735
- },
736
- get_market_breadth: {
737
- type: 'object',
738
- properties: {
739
- hours: { type: 'number', description: 'Lookback hours for correlation computation (1-168, default: 24)', default: 24 },
740
- },
741
- },
742
- get_basis: {
743
- type: 'object',
744
- properties: {
745
- symbol: { type: 'string', description: 'Symbol to get basis for, e.g. BTCUSDT' },
746
- hours: { type: 'number', description: 'Lookback hours for basis history (1-168, default: 24)', default: 24 },
747
- },
748
- required: ['symbol'],
749
- },
750
- get_cascade_risk: {
751
- type: 'object',
752
- properties: {
753
- symbol: { type: 'string', description: 'Symbol to assess cascade risk for, e.g. BTCUSDT' },
754
- hours: { type: 'number', description: 'Lookback hours for liquidation history (1-168, default: 24)', default: 24 },
755
- },
756
- required: ['symbol'],
757
- },
758
- get_sentiment: {
759
- type: 'object',
760
- properties: {},
761
- },
762
- get_divergences: {
763
- type: 'object',
764
- properties: {
765
- symbol: { type: 'string', description: 'Symbol to scan for divergences, e.g. BTCUSDT' },
766
- },
767
- required: ['symbol'],
768
- },
769
- get_liquidation_pulse: {
770
- type: 'object',
771
- properties: {
772
- symbol: { type: 'string', description: 'Symbol to get the cascade pulse for, e.g. BTCUSDT. Omit to get the cross-majors aggregate (BTC+ETH+BNB+SOL+XRP).' },
773
- window_seconds: { type: 'number', description: 'Rolling window length in seconds (5-300, default: 60).', default: 60 },
774
- },
775
- },
776
- get_resting_liquidity: {
777
- type: 'object',
778
- properties: {
779
- symbol: { type: 'string', description: 'Symbol to get resting limit-order liquidity for, e.g. BTCUSDT' },
780
- },
781
- required: ['symbol'],
782
- },
783
- get_funding_context: {
784
- type: 'object',
785
- properties: {
786
- symbol: { type: 'string', description: 'Symbol to get funding-rate percentile context for, e.g. BTCUSDT' },
787
- },
788
- required: ['symbol'],
789
- },
790
- // ---- Operator-only tools (called by the skill's operator scope, NOT the agent) ----
791
- set_exchange_credentials: {
792
- type: 'object',
793
- properties: {
794
- apiKey: { type: 'string', description: 'Binance API key' },
795
- secret: { type: 'string', description: 'Binance API secret' },
796
- testnet: { type: 'boolean', description: 'Use Binance testnet (default: false)' },
797
- },
798
- required: ['apiKey', 'secret'],
799
- },
800
- set_trading_mode: {
801
- type: 'object',
802
- properties: {
803
- mode: { type: 'string', enum: ['PAPER', 'MICRO_LIVE', 'LIVE'], description: 'Target trading mode' },
804
- acknowledged: { type: 'boolean', description: 'Required when moving to LIVE — explicit operator acknowledgment' },
805
- },
806
- required: ['mode'],
807
- },
808
- test_exchange_credentials: {
809
- type: 'object',
810
- properties: {
811
- apiKey: { type: 'string', description: 'Binance API key to verify (not persisted)' },
812
- secret: { type: 'string', description: 'Binance API secret to verify (not persisted)' },
813
- testnet: { type: 'boolean', description: 'Test against Binance testnet' },
814
- },
815
- required: ['apiKey', 'secret'],
816
- },
817
- clear_exchange_credentials: {
818
- type: 'object',
819
- properties: {
820
- confirm: { type: 'boolean', description: 'Must be true — safety gate against accidental probe wipes' },
821
- },
822
- required: ['confirm'],
823
- },
824
- get_bracket_config: {
825
- type: 'object',
826
- properties: {},
827
- },
828
- set_bracket_requirement: {
829
- type: 'object',
830
- properties: {
831
- flag: { type: 'string', enum: ['requireStopLoss', 'requireTakeProfit'], description: 'Which requirement to flip' },
832
- value: { type: 'boolean', description: 'New value. False disables the requirement and returns a warning banner copy.' },
833
- },
834
- required: ['flag', 'value'],
835
- },
836
- };
837
- // ---- Helpers ----
838
- /** Wrap tool result in OpenClaw AgentToolResult format */
839
- function jsonResult(data) {
840
- return {
841
- content: [{ type: 'text', text: JSON.stringify(data) }],
842
- details: data,
843
- };
844
- }
845
- // ---- Plugin definition (OpenClaw format) ----
846
- // Module-level idempotency guard. OpenClaw's `plugins install --link` calls
847
- // register() 3× inside a single gateway process (once for validation, once
848
- // for metadata extraction, once for real activation). Without this guard,
849
- // each call instantiates a fresh LiveAdapter → fresh Binance REST pollers +
850
- // user-data WebSocket + listenKey. On 2026-04-20 that caused 3× the normal
851
- // weight consumption → Binance IP ban (418 "Way too many requests"), which
852
- // each poller's retry cycle then refreshed, creating a self-perpetuating
853
- // ban. The agent was unable to place protective brackets on live positions
854
- // for the duration. Fix: first register() wins, subsequent calls no-op.
855
- // Tools are re-registered on the second+ calls so OpenClaw's tool map stays
856
- // consistent (the api.registerTool factory closes over the singleton state
857
- // created on the first call).
858
- let pluginInitialised = false;
859
- let pluginToolsFactory = null;
860
- let pluginToolNames = [];
861
- const paperTradingPlugin = {
862
- id: PLUGIN_ID,
863
- name: 'ReefClaw Paper Trading',
864
- description: 'Paper trading plugin with real Binance market data and simulated execution. No API keys required.',
865
- configSchema: {
866
- type: 'object',
867
- properties: {
868
- startingBalance: {
869
- type: 'number',
870
- default: 10000,
871
- description: 'Starting USDT balance for paper trading',
872
- },
873
- symbol: {
874
- type: 'string',
875
- default: 'BTC/USDT',
876
- description: 'Default trading symbol',
877
- },
878
- },
879
- },
880
- register(api) {
881
- setLogLevel('info');
882
- // Idempotency guard (see note above module singleton). If already
883
- // initialised, re-register the same tools so OpenClaw's tool map stays
884
- // populated across its multiple register() calls, but do NOT spin up a
885
- // second adapter / stream / poller stack.
886
- if (pluginInitialised) {
887
- logger.warn(TAG, 'register() called again — reusing existing adapter (singleton)');
888
- if (pluginToolsFactory) {
889
- api.registerTool(pluginToolsFactory, { names: pluginToolNames });
890
- }
891
- // The supervisor must be (re)evaluated on EVERY register call, not just
892
- // the first: the first full register can run BEFORE the installer has
893
- // written connectorSupervisor='on' (the gateway hot-reloads the plugin
894
- // the moment `plugins install --link` records it), and OpenClaw's
895
- // in-process restart re-invokes register() down THIS early-return path.
896
- // startConnectorSupervisor() is itself a singleton — repeat calls no-op.
897
- maybeStartConnectorSupervisor();
898
- return;
899
- }
900
- logger.info(TAG, 'Initializing paper trading plugin...');
901
- // Resolve config from plugin settings. The paper wallet's quote currency
902
- // follows the VENUE (USDC on hyperliquid, USDT on binance — issue #174:
903
- // a hardcoded USDT wallet reads as $0 equity on the USDC venue). The
904
- // authoritative plugin-config read happens further down, AFTER the
905
- // simulator exists — peek only the venue here; same file, same parse
906
- // rules, and an unreadable config falls back to the binance default
907
- // exactly like the main read does.
908
- let paperQuoteCurrency = DEFAULT_CONFIG.quoteCurrency;
909
- try {
910
- paperQuoteCurrency = venueQuoteCurrency(parseVenue(readPluginConfig().exchange?.venue).venue);
911
- }
912
- catch {
913
- /* unreadable plugin-config → binance default, matching the main read */
914
- }
915
- const pluginConfig = {
916
- startingBalance: DEFAULT_CONFIG.startingBalance,
917
- symbol: DEFAULT_CONFIG.symbol,
918
- quoteCurrency: paperQuoteCurrency,
919
- };
920
- logger.info(TAG, `Config: ${pluginConfig.startingBalance} ${pluginConfig.quoteCurrency}, symbol: ${pluginConfig.symbol}`);
921
- // Load or initialize state (sync — OpenClaw ignores async register())
922
- const stateManager = new StateManager(PLUGIN_ID);
923
- const state = stateManager.loadOrDefaultSync(pluginConfig.startingBalance, pluginConfig.quoteCurrency);
924
- // Create CCXT instance and simulator
925
- const binanceApi = new BinancePublicApi();
926
- const simulator = new ExchangeSimulator(state);
927
- // Wire persistence: save state on every mutation (debounced)
928
- simulator.on('stateChanged', (newState) => {
929
- stateManager.debouncedSave(newState);
930
- });
931
- // Reload state from disk before read operations.
932
- // Fixes two-process architecture: agent process saves state to disk,
933
- // gateway process must reload it before serving HTTP API calls.
934
- const reloadState = () => {
935
- try {
936
- const fresh = stateManager.loadSync();
937
- if (fresh) {
938
- simulator.replaceState(fresh);
939
- }
940
- }
941
- catch (err) {
942
- logger.warn(TAG, `State reload failed: ${formatError(err)}`);
943
- }
944
- };
945
- // Read config: ReefClaw plugin config from ~/.reefclaw/plugin-config.json
946
- // (OpenClaw's schema validation rejects custom keys in plugin entries)
947
- let connectionToken = '';
948
- let apiBaseUrl = 'https://www.reefclaw.com';
949
- let intelligenceUrl = 'https://intel.reefclaw.com';
950
- let exchangeConfig = null;
951
- let hlCredentials = null;
952
- let tradingMode = 'PAPER';
953
- // Trading venue (multi-venue Phase 0 — docs/HYPERLIQUID_INTEGRATION_PLAN.md).
954
- // Absent config field = 'binance', so every pre-venue install is untouched.
955
- let venue = 'binance';
956
- // exchange.testnet as written on disk — needed independently of
957
- // exchangeConfig because the hyperliquid venue never builds a Binance
958
- // credential config (Phase 0 gate) but its keyless public API still needs
959
- // the mainnet/testnet routing.
960
- let venueTestnet = false;
961
- let signalsEvaluator = 'central';
962
- let signalsSymbols = [];
963
- try {
964
- const rcConfigPath = join(homedir(), '.reefclaw', 'plugin-config.json');
965
- if (existsSync(rcConfigPath)) {
966
- const rcRaw = readFileSync(rcConfigPath, 'utf-8');
967
- const rcConfig = JSON.parse(rcRaw);
968
- connectionToken = rcConfig?.connectionToken ?? '';
969
- apiBaseUrl = rcConfig?.apiBaseUrl ?? apiBaseUrl;
970
- intelligenceUrl = rcConfig?.intelligenceUrl ?? intelligenceUrl;
971
- const ev = rcConfig?.signals?.evaluator;
972
- if (ev === 'shadow' || ev === 'enforce')
973
- signalsEvaluator = ev;
974
- if (Array.isArray(rcConfig?.signals?.symbols)) {
975
- signalsSymbols = rcConfig.signals.symbols.map((s) => String(s).toUpperCase());
976
- }
977
- // Read exchange credentials (Phase 9b) + venue (multi-venue Phase 0)
978
- const exchange = rcConfig?.exchange;
979
- const venueParse = parseVenue(exchange?.venue);
980
- venue = venueParse.venue;
981
- venueTestnet = exchange?.testnet === true;
982
- if (venueParse.unrecognized) {
983
- logger.warn(TAG, `Unrecognized exchange.venue '${venueParse.unrecognized}' in plugin-config — treating as 'binance'`);
984
- }
985
- // apiKey/secret are a BINANCE credential pair; never build a Binance
986
- // client config for another venue (a hyperliquid block carries
987
- // walletAddress/agentPrivateKey instead — consumed from Phase 3 on).
988
- if (venue === 'binance' && exchange?.apiKey && exchange?.secret) {
989
- exchangeConfig = {
990
- apiKey: exchange.apiKey,
991
- secret: exchange.secret,
992
- testnet: exchange.testnet ?? false,
993
- };
994
- }
995
- // Hyperliquid credentials (Phase 3): a MASTER address (queries; holds the
996
- // funds) + an AGENT wallet private key (signs; holds nothing, cannot
997
- // withdraw or transfer — verified live 2026-07-12). Both are required:
998
- // an agent key alone cannot be queried against, and an address alone
999
- // cannot sign.
1000
- if (venue === 'hyperliquid' && exchange?.walletAddress && exchange?.agentPrivateKey) {
1001
- hlCredentials = {
1002
- walletAddress: String(exchange.walletAddress),
1003
- agentPrivateKey: String(exchange.agentPrivateKey),
1004
- testnet: exchange.testnet === true,
1005
- };
1006
- }
1007
- // Read trading mode (default: PAPER)
1008
- const mode = rcConfig?.tradingMode?.toUpperCase();
1009
- if (mode === 'SHADOW' || mode === 'MICRO_LIVE' || mode === 'LIVE') {
1010
- tradingMode = mode;
1011
- }
1012
- }
1013
- }
1014
- catch (err) {
1015
- logger.warn(TAG, `Could not read ReefClaw config: ${formatError(err)}`);
1016
- }
1017
- if (!connectionToken) {
1018
- // Chat/npx onboarding saves the connection ONLY in OpenClaw's config
1019
- // (skills.entries.reefclaw.config) — the agent writes it there and the
1020
- // connector reads it there. Fall back so a fresh install's intel tools,
1021
- // ingest, and config poller aren't token-starved (first-user bug).
1022
- const conn = readOpenClawConnection();
1023
- if (conn.token) {
1024
- connectionToken = conn.token;
1025
- logger.info(TAG, 'Connection token loaded from OpenClaw config (skills.entries.reefclaw)');
1026
- }
1027
- }
1028
- if (connectionToken) {
1029
- logger.info(TAG, `Connection token loaded (prefix: ${connectionToken.slice(0, 7)}...)`);
1030
- }
1031
- else {
1032
- logger.warn(TAG, 'No connection token found — get_market_intel will return errors');
1033
- }
1034
- // Venue support gate (multi-venue Phase 0). Checked BEFORE the credentials
1035
- // gate so a hyperliquid config gets the accurate diagnosis ("venue not
1036
- // supported yet"), not a misleading "missing API keys". Fallback-to-PAPER,
1037
- // never a throw — register() must stay non-crashing.
1038
- if (tradingMode !== 'PAPER' && !isLiveVenueSupported(venue)) {
1039
- logger.warn(TAG, `Trading mode ${tradingMode} on venue '${venue}' is not supported in this build — falling back to PAPER (live support arrives in Phase 3 of docs/HYPERLIQUID_INTEGRATION_PLAN.md)`);
1040
- tradingMode = 'PAPER';
1041
- }
1042
- // Validate trading mode vs credentials — PER VENUE (the credential SHAPES
1043
- // differ: Binance = apiKey/secret, Hyperliquid = master address + agent key).
1044
- if (tradingMode !== 'PAPER') {
1045
- if (venue === 'binance' && !exchangeConfig) {
1046
- logger.warn(TAG, `Trading mode ${tradingMode} requires exchange API keys — falling back to PAPER`);
1047
- tradingMode = 'PAPER';
1048
- }
1049
- else if (venue === 'hyperliquid' && !hlCredentials) {
1050
- logger.warn(TAG, `Trading mode ${tradingMode} on Hyperliquid requires exchange.walletAddress (master) + ` +
1051
- 'exchange.agentPrivateKey (agent wallet) — falling back to PAPER');
1052
- tradingMode = 'PAPER';
1053
- }
1054
- }
1055
- // Create a BinancePrivateApi for SHADOW mode balance reads whenever
1056
- // credentials exist — even if we start in PAPER, the user can switch
1057
- // to SHADOW at runtime via the dashboard.
1058
- let shadowPrivateApi = null;
1059
- if (exchangeConfig) {
1060
- try {
1061
- shadowPrivateApi = new BinancePrivateApi(exchangeConfig);
1062
- }
1063
- catch (err) {
1064
- logger.warn(TAG, `Failed to create shadow API client: ${formatError(err)}`);
1065
- }
1066
- }
1067
- // Initialize shadow tracker if in shadow mode
1068
- let shadowTracker = null;
1069
- if (tradingMode === 'SHADOW' && shadowPrivateApi) {
1070
- try {
1071
- shadowTracker = new ShadowTracker(shadowPrivateApi, simulator);
1072
- shadowTracker.start();
1073
- // Validate API keys asynchronously (don't block register)
1074
- shadowPrivateApi.validatePermissions().then(result => {
1075
- if (result.canReadBalance && result.canReadPositions) {
1076
- logger.info(TAG, 'Shadow mode: API key validation passed');
1077
- }
1078
- else {
1079
- logger.warn(TAG, `Shadow mode: API key validation issues: ${result.errors.join(', ')}`);
1080
- }
1081
- }).catch(err => {
1082
- logger.warn(TAG, `Shadow mode: API key validation failed: ${formatError(err)}`);
1083
- });
1084
- }
1085
- catch (err) {
1086
- logger.warn(TAG, `Failed to initialize shadow mode: ${formatError(err)}`);
1087
- tradingMode = 'PAPER';
1088
- }
1089
- }
1090
- // ---- Position Decision Journal singletons (hoisted) ----
1091
- // Mode-agnostic: the state-store + decisions-client run in both PAPER and
1092
- // LIVE modes once the ingest env vars are present. The record_position_reviews
1093
- // tool is always registered; off-mode just skips the SKILL.md mandate +
1094
- // stale-gate. See docs/POSITION_DECISION_JOURNAL_PLAN.md.
1095
- //
1096
- // Hoisted above the adapter construction so the autoCapture context can be
1097
- // passed to LiveAdapter at construction time — the WS-driven ws-ingest
1098
- // path needs it to capture limit-order entries + scale-ins (PR1 deferral
1099
- // resolution, 2026-05-03).
1100
- const positionStateStore = new PositionStateStore();
1101
- const pendingEntryStore = new PendingEntryStore();
1102
- const positionReviewMode = loadPositionReviewMode();
1103
- if (positionReviewMode !== 'off') {
1104
- logger.info(TAG, `Position-review mode=${positionReviewMode}`);
1105
- }
1106
- let positionDecisionsClient;
1107
- let positionDecisionsUserId;
1108
- {
1109
- let cfgForIngest;
1110
- try {
1111
- cfgForIngest = readPluginConfig();
1112
- }
1113
- catch {
1114
- cfgForIngest = undefined;
1115
- }
1116
- // Ingest token: plugin-config.json `connectionToken` preferred, legacy
1117
- // WEBAPP_INGEST_TOKEN env as fallback — see resolveIngestToken.
1118
- const ingestToken = resolveIngestToken(cfgForIngest);
1119
- const reefclawUserId = resolveReefclawUserId();
1120
- if (ingestToken && reefclawUserId) {
1121
- const ingestBaseUrl = getUserDataStreamIngestBaseUrl(cfgForIngest);
1122
- positionDecisionsClient = new PositionDecisionsClient({
1123
- baseUrl: ingestBaseUrl,
1124
- ingestToken,
1125
- });
1126
- positionDecisionsUserId = reefclawUserId;
1127
- logger.info(TAG, `Position-decisions ingest wired to ${ingestBaseUrl} (userId=${reefclawUserId.slice(0, 8)}…)`);
1128
- }
1129
- else {
1130
- logger.info(TAG, `Position-decisions ingest disabled (ingest token [plugin-config connectionToken / WEBAPP_INGEST_TOKEN] or REEFCLAW_USER_ID missing)`);
1131
- }
1132
- }
1133
- // Reduce-only-fill close capture: when a bracket SL/TP (or other reduce-only
1134
- // fill) takes a journaled position flat, auto-journal an exact close so the
1135
- // position doesn't leak as status='open' forever (close-bypass class). On by
1136
- // default; env kill-switch RC_JOURNAL_CLOSE_ON_FILL=off disables it.
1137
- const closeOnReduceOnlyFill = process.env.RC_JOURNAL_CLOSE_ON_FILL !== 'off';
1138
- const autoCapture = {
1139
- decisionsClient: positionDecisionsClient,
1140
- stateStore: positionStateStore,
1141
- pendingEntries: pendingEntryStore,
1142
- userId: positionDecisionsUserId,
1143
- closeOnReduceOnlyFill,
1144
- // Tag each journaled position with the active book so paper and live
1145
- // entries stay segregated. Read from `runtime` at capture time (deferred
1146
- // closure — only invoked on a fill, long after `runtime` is built), so it
1147
- // follows a live<->paper reconnect.
1148
- resolveMode: () => (runtime.adapter.isLive ? 'live' : 'paper'),
1149
- // Venue tag (positions.exchange, migration 0058) — static per process;
1150
- // a venue change requires config edit + restart.
1151
- venue,
1152
- };
1153
- if (positionDecisionsClient) {
1154
- logger.info(TAG, `Journal close-on-reduce-only-fill ${closeOnReduceOnlyFill ? 'ENABLED' : 'disabled'}`);
1155
- }
1156
- // ---- Create exchange adapter based on trading mode ----
1157
- let adapter;
1158
- // Hoisted so the SIGTERM handler (declared after this block) can drain it
1159
- // on shutdown. Only populated in live modes when dbWrite=on + env present.
1160
- let tradeIngest;
1161
- // Approval-mode Phase A — set when APPROVAL_SHADOW_MODE=1 + ingest deps present.
1162
- // Read by the create_order tool below to dual-write proposals. See §12 of
1163
- // docs/APPROVAL_MODE_DESIGN.md.
1164
- let proposalManagerCtx;
1165
- let proposalListenerCredentials;
1166
- // Approval mode read at init from env + plugin-config.json; passed to the
1167
- // create_order tool so it picks the right branch (off / shadow / per_trade).
1168
- let approvalModeForTool = 'off';
1169
- // Approval-mode Phase B — listener that fires operator-approved proposals.
1170
- // Started below only when config.approval.mode === 'per_trade' AND we have
1171
- // ingest credentials. See docs/APPROVAL_MODE_DESIGN.md §2 + §7.3.
1172
- let proposalDecisionListener;
1173
- if (tradingMode === 'MICRO_LIVE' || tradingMode === 'LIVE') {
1174
- if (!exchangeConfig) {
1175
- // Already handled above (falls back to PAPER), but safety check
1176
- logger.error(TAG, `${tradingMode} mode requires exchange config — this should not happen`);
1177
- adapter = new PaperAdapter(simulator);
1178
- tradingMode = 'PAPER';
1179
- }
1180
- else {
1181
- // Read micro-live config
1182
- let microLiveConfig;
1183
- try {
1184
- const rcConfigPath = join(homedir(), '.reefclaw', 'plugin-config.json');
1185
- if (existsSync(rcConfigPath)) {
1186
- const rcConfig = JSON.parse(readFileSync(rcConfigPath, 'utf-8'));
1187
- if (rcConfig?.microLive) {
1188
- microLiveConfig = {
1189
- sizeCapPercent: rcConfig.microLive.sizeCapPercent,
1190
- maxPositionUSDT: rcConfig.microLive.maxPositionUSDT,
1191
- };
1192
- }
1193
- }
1194
- }
1195
- catch { /* already read above, best-effort */ }
1196
- // Bracket-orders feature flag read from plugin-config at construction time.
1197
- // Default 'off' keeps legacy stop-watcher behaviour while the feature rolls out.
1198
- const bracketMode = loadBracketMode();
1199
- if (bracketMode !== 'off') {
1200
- logger.info(TAG, `Bracket orders enabled in mode=${bracketMode}`);
1201
- }
1202
- // User-data WebSocket stream flag — same mode-ladder pattern as brackets.
1203
- // Default 'off' keeps REST polling authoritative. Phase 1 ships dead-code;
1204
- // the flag flip to 'shadow' / 'observe' / 'enforce' is operator-driven.
1205
- const userDataStreamMode = loadUserDataStreamMode();
1206
- const userDataStreamTunables = loadUserDataStreamTunables();
1207
- if (userDataStreamMode !== 'off') {
1208
- logger.info(TAG, `User-data stream enabled in mode=${userDataStreamMode}`);
1209
- }
1210
- // TRADE_AUDIT_TRAIL_PLAN Phase 1 — opt-in WS audit-trail ingest.
1211
- // Activates only when ALL of:
1212
- // - userDataStream.mode != 'off' (read path running)
1213
- // - userDataStream.dbWrite = 'on' (operator opted in)
1214
- // - ingest token resolvable (plugin-config connectionToken,
1215
- // else legacy WEBAPP_INGEST_TOKEN env)
1216
- // - REEFCLAW_USER_ID env set (trader identity)
1217
- // Any missing field → no ingest wired (silent dead-code default).
1218
- const dbWriteMode = loadUserDataStreamDbWrite();
1219
- let cfgForIngest;
1220
- try {
1221
- cfgForIngest = readPluginConfig();
1222
- }
1223
- catch {
1224
- cfgForIngest = undefined;
1225
- }
1226
- const ingestToken = resolveIngestToken(cfgForIngest);
1227
- const reefclawUserId = resolveReefclawUserId();
1228
- if (userDataStreamMode !== 'off' &&
1229
- dbWriteMode === 'on' &&
1230
- ingestToken &&
1231
- reefclawUserId) {
1232
- const ingestBaseUrl = getUserDataStreamIngestBaseUrl(cfgForIngest);
1233
- tradeIngest = {
1234
- client: new TradeStoreClient({ baseUrl: ingestBaseUrl, ingestToken }),
1235
- userId: reefclawUserId,
1236
- // Venue-derived FillEvent.exchange — 'binance_futures' for the
1237
- // binance venue, i.e. byte-identical to the pre-venue literal
1238
- // WsIngest defaulted to. Half of the (exchange, exchange_trade_id)
1239
- // audit-trail idempotency key; never a fresh string literal.
1240
- exchange: fillExchangeId(venue),
1241
- };
1242
- logger.info(TAG, `User-data stream dbWrite=on — WS audit-trail ingest wired to ${ingestBaseUrl} (userId=${reefclawUserId.slice(0, 8)}…)`);
1243
- }
1244
- else if (userDataStreamMode !== 'off' && dbWriteMode === 'on') {
1245
- logger.warn(TAG, `User-data stream dbWrite=on but ingest token (plugin-config connectionToken / WEBAPP_INGEST_TOKEN) or REEFCLAW_USER_ID missing — audit-trail ingest disabled`);
1246
- }
1247
- // ---- Approval-mode wiring ----
1248
- // Two switches gate proposal-related behaviour:
1249
- //
1250
- // 1. config approval.mode='per_trade' → THE feature. create_order
1251
- // returns pending_approval (does NOT fire). ProposalDecisionListener
1252
- // polls /pending-decisions and fires when the operator approves.
1253
- //
1254
- // 2. env APPROVAL_SHADOW_MODE=1 → Phase A telemetry only.
1255
- // create_order dual-writes a shadow proposal row AND fires the
1256
- // real order. Used to collect proposal-shape distribution before
1257
- // flipping per_trade. Retired once per_trade has soaked.
1258
- //
1259
- // If both are set, per_trade wins — no point dual-writing shadow rows
1260
- // when we're collecting real ones. The ProposalManager itself is the
1261
- // same instance either way; `origin` on each insert distinguishes them.
1262
- //
1263
- // See docs/APPROVAL_MODE_DESIGN.md §12.
1264
- const approvalShadowEnabled = (process.env.APPROVAL_SHADOW_MODE ?? '').trim() === '1';
1265
- try {
1266
- const cfg = readPluginConfig();
1267
- if (cfg.approval?.mode === 'per_trade') {
1268
- approvalModeForTool = 'per_trade';
1269
- }
1270
- else if (approvalShadowEnabled) {
1271
- approvalModeForTool = 'shadow';
1272
- }
1273
- }
1274
- catch {
1275
- // Config read failed; fall back to env-only.
1276
- if (approvalShadowEnabled)
1277
- approvalModeForTool = 'shadow';
1278
- }
1279
- if (approvalModeForTool !== 'off' && ingestToken && reefclawUserId) {
1280
- let cfgForUrl;
1281
- try {
1282
- cfgForUrl = readPluginConfig();
1283
- }
1284
- catch {
1285
- cfgForUrl = undefined;
1286
- }
1287
- const ingestBaseUrl = getUserDataStreamIngestBaseUrl(cfgForUrl);
1288
- proposalManagerCtx = {
1289
- manager: new ProposalManager({ baseUrl: ingestBaseUrl, ingestToken }),
1290
- userId: reefclawUserId,
1291
- };
1292
- proposalListenerCredentials = { ingestToken, userId: reefclawUserId };
1293
- logger.info(TAG, `Approval wiring active (mode=${approvalModeForTool}) → ${ingestBaseUrl} (userId=${reefclawUserId.slice(0, 8)}…)`);
1294
- }
1295
- else if (approvalModeForTool !== 'off') {
1296
- logger.warn(TAG, `approval mode=${approvalModeForTool} but WEBAPP_INGEST_TOKEN or REEFCLAW_USER_ID env missing — proposal path disabled`);
1297
- approvalModeForTool = 'off';
1298
- }
1299
- // Venue-dispatched construction (multi-venue Phase 0). For 'binance'
1300
- // this is a pure pass-through to `new LiveAdapter(...)` — identical
1301
- // args, identical behavior; the unsupported-venue arm is unreachable
1302
- // here because the isLiveVenueSupported() gate above already fell back
1303
- // to PAPER. Phase 3 adds the hyperliquid adapter inside the factory.
1304
- const liveAdapter = createLiveAdapter(venue === 'hyperliquid'
1305
- ? {
1306
- venue: 'hyperliquid',
1307
- args: {
1308
- // Non-null by the per-venue credential gate above.
1309
- credentials: hlCredentials,
1310
- mode: tradingMode,
1311
- marketSlippagePct: readPluginConfig().hl?.marketSlippagePct,
1312
- },
1313
- }
1314
- : {
1315
- venue: 'binance',
1316
- args: [
1317
- exchangeConfig,
1318
- tradingMode,
1319
- microLiveConfig,
1320
- bracketMode,
1321
- userDataStreamMode,
1322
- userDataStreamTunables,
1323
- tradeIngest,
1324
- autoCapture,
1325
- ],
1326
- });
1327
- adapter = liveAdapter;
1328
- // Reconciler-driven close-bypass cleanup. The reconciler emits
1329
- // 'drift_detected' on every poll; we filter for 'closed' drifts
1330
- // (position observed flat on exchange while local thinks it's open).
1331
- // Fires for bracket SL/TP fills, opposite-side market orders, and
1332
- // external closes — all paths that bypass close_position() and
1333
- // would otherwise leave webapp DB + state-store stale.
1334
- // See plugin/src/ingest/reconciler-cleanup.ts.
1335
- liveAdapter.on('drift_detected', (result) => {
1336
- for (const drift of result.drifts) {
1337
- if (drift.type !== 'closed')
1338
- continue;
1339
- void onReconcilerObservedClose({
1340
- decisionsClient: positionDecisionsClient,
1341
- stateStore: positionStateStore,
1342
- userId: positionDecisionsUserId,
1343
- lastPriceFn: (sym) => liveAdapter.getLastPrice(sym),
1344
- }, {
1345
- symbol: drift.symbol,
1346
- lastContracts: drift.localContracts,
1347
- observedAtMs: new Date(result.timestamp).getTime(),
1348
- }).catch((err) => {
1349
- logger.error(TAG, `reconciler cleanup failed for ${drift.symbol}: ${formatError(err)}`);
1350
- });
1351
- }
1352
- });
1353
- // Post-registration async init (fire-and-forget — register must be sync)
1354
- liveAdapter.initialize().then(async () => {
1355
- // After init, run a one-shot startup reconciliation between
1356
- // state-store and exchange truth. The runtime drift_detected hook
1357
- // only catches NEW close-bypass events (was-on-exchange-now-gone);
1358
- // entries that became stale BEFORE the plugin started are invisible
1359
- // to that loop. This pass closes that gap by sweeping state-store
1360
- // against the current exchange snapshot.
1361
- try {
1362
- // One trusted fetch reused for all three boot ops (no extra Binance
1363
- // weight). getPositionsOrNull() returns null on a failed/weight-paced
1364
- // fetch; getPositions() would collapse that to [] (null≠empty trap).
1365
- const trusted = await liveAdapter.getPositionsOrNull();
1366
- if (trusted !== null) {
1367
- // State-store sweep ONLY on a TRUSTED snapshot: this call treats
1368
- // every state-store symbol absent from the list as closed and
1369
- // removes it (incl. its webappPositionId journal linkage), so
1370
- // collapsing a null (failed) fetch to [] would wipe the store
1371
- // for every open position on a transient boot-time 429/paced
1372
- // read — the null≠empty trap, with no self-healing retry.
1373
- await reconcileStateStoreOnStartup({
1374
- decisionsClient: positionDecisionsClient,
1375
- stateStore: positionStateStore,
1376
- userId: positionDecisionsUserId,
1377
- lastPriceFn: (sym) => liveAdapter.getLastPrice(sym),
1378
- }, trusted.map((p) => p.symbol));
1379
- // Seed remaining-contracts for positions still open on the exchange
1380
- // so the reduce-only-fill close handler can detect flat for entries
1381
- // that opened before this plugin instance started (or before the
1382
- // size-tracking field existed). seedRemainingContracts never clobbers
1383
- // live fill-tracked state.
1384
- for (const p of trusted) {
1385
- const contracts = Math.abs(Number(p.contracts));
1386
- if (Number.isFinite(contracts) && contracts > 0) {
1387
- positionStateStore.seedRemainingContracts(p.symbol, contracts);
1388
- }
1389
- }
1390
- // DB-authoritative orphan close: close webapp `positions` rows that
1391
- // are status='open' but absent from the exchange (closed on-exchange
1392
- // while down / state-store wiped). Same trusted-snapshot-only rule —
1393
- // a null fetch must never be read as "flat" and close the book.
1394
- await reconcileDbOpenVsExchange({ decisionsClient: positionDecisionsClient, userId: positionDecisionsUserId }, trusted.map((p) => p.symbol));
1395
- }
1396
- else {
1397
- logger.warn(TAG, 'Startup reconciliation skipped — positions fetch untrusted (null); state-store left untouched. Runtime drift_detected + periodic reconcilers cover the gap.');
1398
- }
1399
- }
1400
- catch (err) {
1401
- logger.warn(TAG, `Startup state-store reconciliation failed: ${formatError(err)}`);
1402
- }
1403
- }).catch(err => {
1404
- logger.error(TAG, `Live adapter init failed: ${formatError(err)}`);
1405
- });
1406
- }
1407
- }
1408
- else {
1409
- adapter = new PaperAdapter(simulator);
1410
- }
1411
- // PAPER market-data source selection.
1412
- //
1413
- // Venue precedence (Hyperliquid Phase 1 — docs/HYPERLIQUID_INTEGRATION_PLAN.md):
1414
- // the configured VENUE decides which exchange prices paper mode, full stop.
1415
- // venue='hyperliquid' → every public read (PaperMarketFeed marks, the
1416
- // simulator's fill prices, fetch_ticker/fetch_ohlcv/get_orderbook tools)
1417
- // comes from Hyperliquid's keyless /info endpoints; the Binance-only
1418
- // `paperMarketDataSource:'intel'` escape hatch does not apply (intel has
1419
- // no HL rows until Phase 2 — silently serving Binance prices for a
1420
- // Hyperliquid book would be a lie, the exact class the symbol-translation
1421
- // rule forbids).
1422
- //
1423
- // Binance venue (default) is unchanged: a host Binance geo-blocks (HTTP
1424
- // 451) can route PRICE reads through the intel service by setting
1425
- // plugin-config `paperMarketDataSource:'intel'` (GET /api/price/:symbol).
1426
- // LIVE always uses the real exchange, and the readiness reporter probes
1427
- // the CONFIGURED venue. Kill-switch RC_PAPER_MARKET_DATA=binance forces
1428
- // the Binance-direct default (binance venue only).
1429
- //
1430
- // hlPublicApi is constructed once per process when the venue is
1431
- // hyperliquid — shared by the paper data path and the readiness probe.
1432
- const hlPublicApi = venue === 'hyperliquid' ? new HyperliquidPublicApi({ testnet: venueTestnet }) : null;
1433
- let configuredPaperSource;
1434
- try {
1435
- configuredPaperSource = readPluginConfig().paperMarketDataSource;
1436
- }
1437
- catch {
1438
- configuredPaperSource = undefined;
1439
- }
1440
- const paperDataSource = process.env.RC_PAPER_MARKET_DATA === 'binance'
1441
- ? 'binance'
1442
- : configuredPaperSource ?? 'binance';
1443
- const useIntelPaperData = venue === 'binance' &&
1444
- !adapter.isLive && paperDataSource === 'intel' && connectionToken.length > 0;
1445
- const marketDataApi = !adapter.isLive && hlPublicApi
1446
- ? hlPublicApi
1447
- : useIntelPaperData
1448
- ? new IntelPublicApi({ connectionToken, intelligenceUrl })
1449
- : binanceApi;
1450
- if (!adapter.isLive && hlPublicApi) {
1451
- logger.info(TAG, `PAPER market data sourced from Hyperliquid${venueTestnet ? ' TESTNET' : ''} (venue=hyperliquid) — use USDC pairs (e.g. BTC/USDC)`);
1452
- if (paperDataSource === 'intel') {
1453
- logger.warn(TAG, 'paperMarketDataSource=intel is a Binance-venue option — ignored on venue=hyperliquid (intel has no Hyperliquid rows until Phase 2)');
1454
- }
1455
- }
1456
- else if (useIntelPaperData) {
1457
- logger.warn(TAG, 'PAPER market data routed through intel (paperMarketDataSource=intel) — prices from GET /api/price; live + readiness still use Binance directly');
1458
- }
1459
- else if (venue === 'binance' && !adapter.isLive && paperDataSource === 'intel' && connectionToken.length === 0) {
1460
- logger.warn(TAG, 'paperMarketDataSource=intel requested but no connection token — falling back to Binance direct (which will 451 on a geo-blocked host)');
1461
- }
1462
- // Build tool dependencies
1463
- // `simDeps` for tools that need the simulator directly (ticker, market structure, risk scenario)
1464
- // `adapterDeps` for the 7 adapter-based trading tools
1465
- // Both carry `marketDataApi` (Binance direct, or intel in paper mode) so
1466
- // every paper market-data read follows the selected source at once.
1467
- const simDeps = { binanceApi: marketDataApi, simulator };
1468
- const adapterDeps = { binanceApi: marketDataApi, adapter };
1469
- // ---- Plugin runtime (mutable holder) ----
1470
- // Owns the adapter + mode + stop-watcher and exposes `reconnect()` so the
1471
- // operator can swap credentials / trading mode from the dashboard without
1472
- // restarting the gateway. Tools read `runtime.adapter` / `runtime.mode`
1473
- // through closures so a reconnect is visible everywhere at once.
1474
- //
1475
- // The stop-watcher polls open positions every 3s and auto-closes any
1476
- // where mark crosses metadata.stopPrice (see stop-watcher.ts). It runs
1477
- // independently of the LLM agent.
1478
- // EFFICIENCY_QUICK_WINS Change 1: default cadence is now 10s (was 3s) —
1479
- // see stop-watcher.ts. Operators can override / revert with zero deploy
1480
- // via plugin-config.json `stopWatcher.intervalMs`. Best-effort read;
1481
- // any failure falls back to the (already-reduced) default.
1482
- let stopWatcherIntervalMs;
1483
- try {
1484
- const swMs = readPluginConfig().stopWatcher?.intervalMs;
1485
- if (typeof swMs === 'number' && Number.isFinite(swMs) && swMs > 0) {
1486
- stopWatcherIntervalMs = swMs;
1487
- logger.info(TAG, `Stop-watcher interval overridden via plugin-config: ${swMs}ms`);
1488
- }
1489
- }
1490
- catch { /* best-effort — default applies */ }
1491
- const wave9OperationLock = new SerialTradingOperationLock();
1492
- const bootstrapWatcher = stopWatcherIntervalMs !== undefined
1493
- ? new PositionWatcher(adapter, stopWatcherIntervalMs, wave9OperationLock)
1494
- : new PositionWatcher(adapter, undefined, wave9OperationLock);
1495
- // Paper-mode background market-data feed. Refreshes the simulator's marks
1496
- // for every symbol with an open position/order (not just the dashboard
1497
- // symbol), so the stop-watcher sees a fresh mark, NAV/uPnL stay live, and
1498
- // resting limits fill. Paper-only — started here when the boot adapter is
1499
- // paper; PluginRuntime.reconnect() stops it on a switch to live and
1500
- // restarts it on a switch back to paper. Reuses the ban-gated fetchTicker.
1501
- const marketFeed = new PaperMarketFeed(marketDataApi, simulator);
1502
- if (!adapter.isLive) {
1503
- marketFeed.start();
1504
- }
1505
- const runtime = new PluginRuntime({
1506
- adapter,
1507
- mode: tradingMode,
1508
- simulator,
1509
- stopWatcher: bootstrapWatcher,
1510
- marketFeed,
1511
- operationLock: wave9OperationLock,
1512
- });
1513
- const intelDeps = { connectionToken, apiBaseUrl };
1514
- // venue rides the deps so every intel tool maps agent-facing symbols to
1515
- // the venue's intel namespace (plan §5.3) — absent/'binance' is
1516
- // byte-identical passthrough.
1517
- const intelApiDeps = { connectionToken, intelligenceUrl, venue };
1518
- const wave9AdmissionGuard = new Wave9PaperAdmissionGuard();
1519
- let wave9LiveLedger;
1520
- try {
1521
- wave9LiveLedger = new Wave9LiveExecutionLedger();
1522
- }
1523
- catch (error) {
1524
- // A malformed durable ledger must disable Wave 9 LIVE admission without
1525
- // taking unrelated trading and risk-reducing tools offline.
1526
- logger.error(TAG, `Wave 9 live execution ledger unavailable: ${formatError(error)}`);
1527
- }
1528
- const checkWave9LiveSymbolOwnership = async (symbol) => {
1529
- const ledger = wave9LiveLedger;
1530
- if (!ledger) {
1531
- return {
1532
- status: 'unavailable',
1533
- reason: 'durable_wave9_live_execution_ledger_unavailable',
1534
- };
1535
- }
1536
- return inspectWave9LiveSymbolOwnership(runtime.adapter, ledger, symbol);
1537
- };
1538
- if (wave9LiveLedger) {
1539
- const liveLedger = wave9LiveLedger;
1540
- const canonicalWave9Symbol = (symbol) => symbol.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
1541
- const resolveSingleOpenOwner = (symbol) => {
1542
- const canonical = canonicalWave9Symbol(symbol);
1543
- const matches = liveLedger.getAll().filter((record) => record.state === 'open'
1544
- && record.symbol === canonical);
1545
- if (matches.length > 1) {
1546
- throw new Error(`multiple durable OPEN Wave 9 candidates own ${symbol}; refusing ambiguous lifecycle mutation`);
1547
- }
1548
- return matches[0];
1549
- };
1550
- const logWave9Recovery = (trigger, result) => {
1551
- if (result.status === 'flat') {
1552
- logger.info(TAG, `Wave 9 LIVE ${trigger}: ${result.symbol} is stably flat and candidate ` +
1553
- `${result.candidateId} is durably CLOSED (${result.detail})`);
1554
- }
1555
- else if (result.status === 'protected') {
1556
- logger.warn(TAG, `Wave 9 LIVE ${trigger}: residual ${result.symbol} is protected by the exact ` +
1557
- `frozen stop ${result.stopPrice} (${result.detail})`);
1558
- }
1559
- else {
1560
- logger.error(TAG, `Wave 9 LIVE ${trigger}: ${result.detail}; candidate ${result.candidateId} ` +
1561
- 'remains durably OPEN');
1562
- }
1563
- };
1564
- const logWave9ReadyReconciliation = (trigger, result) => {
1565
- for (const row of result.rows) {
1566
- const message = `Wave 9 LIVE ${trigger} reconciliation ${row.status} for ${row.symbol}/` +
1567
- `${row.candidateId}: ${row.detail}`;
1568
- if (row.status === 'protected')
1569
- logger.info(TAG, message);
1570
- else if (row.status === 'closed')
1571
- logger.info(TAG, message);
1572
- else
1573
- logger.error(TAG, message);
1574
- }
1575
- };
1576
- const wave9DurableReconciliationScheduler = new Wave9LiveDurableReconciliationScheduler({
1577
- operationLock: wave9OperationLock,
1578
- isCurrentSource: (source) => runtime.mode === 'LIVE' && runtime.adapter === source,
1579
- reconcile: (source) => reconcileWave9LiveDurableRows(source, liveLedger),
1580
- onResult: logWave9ReadyReconciliation,
1581
- onError: (trigger, error) => {
1582
- logger.error(TAG, `Wave 9 LIVE ${trigger} durable reconciliation failed: ${formatError(error)}; ` +
1583
- 'IMMEDIATE MANUAL INTERVENTION REQUIRED');
1584
- },
1585
- });
1586
- const handleProtectionLoss = (source, event) => wave9OperationLock.withAccountLock(`wave9_protection_loss:${event.symbol}`, async () => {
1587
- if (runtime.mode !== 'LIVE' || runtime.adapter !== source) {
1588
- logger.warn(TAG, `Ignoring stale Wave 9 protection-loss event for ${event.symbol} from a replaced/non-LIVE adapter`);
1589
- return;
1590
- }
1591
- const owner = resolveSingleOpenOwner(event.symbol);
1592
- if (!owner)
1593
- return; // Generic/non-Wave9 bracket behavior stays unchanged.
1594
- if (event.entryClientOrderId !== owner.clientOrderId
1595
- || event.entrySide !== owner.side) {
1596
- throw new Error(`IMMEDIATE MANUAL INTERVENTION REQUIRED: ${event.symbol} protection-loss event ` +
1597
- `belongs to entry ${event.entryClientOrderId}/${event.entrySide}, not durable ` +
1598
- `Wave 9 owner ${owner.clientOrderId}/${owner.side}; refusing stale/generic mutation`);
1599
- }
1600
- const bracketLedger = source.getBracketLedger();
1601
- const row = bracketLedger?.getBySymbol(event.symbol);
1602
- if (row && (row.entryCid !== owner.clientOrderId || row.entrySide !== owner.side)) {
1603
- throw new Error(`IMMEDIATE MANUAL INTERVENTION REQUIRED: ${event.symbol} has durable Wave 9 owner ` +
1604
- `${owner.candidateId}, but its current bracket belongs to a different entry; ` +
1605
- 'refusing to auto-close or replace potentially generic protection');
1606
- }
1607
- if (row && row.bracketId !== event.bracketId) {
1608
- logger.info(TAG, `Ignoring stale Wave 9 stop-loss event ${event.bracketId} for ${event.symbol}; ` +
1609
- `current bracket is ${row.bracketId}`);
1610
- return;
1611
- }
1612
- if (row?.slCid && event.stopClientOrderId && row.slCid !== event.stopClientOrderId) {
1613
- logger.info(TAG, `Ignoring stale Wave 9 stop CID ${event.stopClientOrderId} for ${event.symbol}`);
1614
- return;
1615
- }
1616
- let terminalStopProven = event.source !== 'reconciler_missing';
1617
- if (event.source === 'reconciler_missing' && event.stopClientOrderId) {
1618
- const liveness = await source.resolveBracketLegLiveness(event.stopClientOrderId);
1619
- if (liveness === 'live' || liveness === 'unknown') {
1620
- logger.info(TAG, `Wave 9 reconciler reported ${event.symbol} stop missing, but exact CID ` +
1621
- `${event.stopClientOrderId} is ${liveness}; deferring destructive recovery ` +
1622
- 'until terminal loss is proven');
1623
- return;
1624
- }
1625
- terminalStopProven = liveness === 'terminal';
1626
- }
1627
- else if (event.source === 'reconciler_missing' && !event.stopClientOrderId) {
1628
- // A stop was required by the row but no exact stop CID was ever
1629
- // persisted or observed in the trusted open-order snapshot.
1630
- terminalStopProven = true;
1631
- }
1632
- if (row && terminalStopProven
1633
- && row.state !== 'cancelled'
1634
- && row.state !== 'failed'
1635
- && row.state !== 'triggered_sl'
1636
- && row.state !== 'triggered_tp') {
1637
- try {
1638
- bracketLedger?.markState(event.symbol, 'cancelled', {
1639
- closeReason: 'cancelled_auto',
1640
- lastError: event.detail,
1641
- });
1642
- }
1643
- catch (error) {
1644
- logger.error(TAG, `Could not durably terminalize lost Wave 9 stop for ${event.symbol}: ${formatError(error)}; ` +
1645
- 'continuing to exact re-protection/emergency flatten');
1646
- }
1647
- }
1648
- const result = await recoverWave9LiveExposure(source, liveLedger, owner.candidateId, event.symbol);
1649
- logWave9Recovery(event.source, result);
1650
- });
1651
- runtime.setWave9LiveLifecycleHooks({
1652
- configureLiveAdapter: (source) => {
1653
- source.setWave9ProtectionLossHandler((event) => handleProtectionLoss(source, event));
1654
- wave9DurableReconciliationScheduler.attach(source);
1655
- },
1656
- configurePositionWatcher: (watcher, source) => {
1657
- wave9DurableReconciliationScheduler.stopIfSourceChanged(source);
1658
- watcher.setWave9CloseLifecycle({
1659
- resolveCandidateId: (position) => {
1660
- if (!source.isLive || runtime.mode !== 'LIVE' || runtime.adapter !== source) {
1661
- return undefined;
1662
- }
1663
- const owner = resolveSingleOpenOwner(position.symbol);
1664
- if (!owner)
1665
- return undefined;
1666
- const expectedSide = owner.side === 'buy' ? 'long' : 'short';
1667
- if (position.side !== expectedSide) {
1668
- throw new Error(`durable Wave 9 owner ${owner.candidateId} is ${expectedSide}, but exchange position is ${position.side}`);
1669
- }
1670
- if (position.candidateId && position.candidateId !== owner.candidateId) {
1671
- throw new Error(`position candidate ${position.candidateId} does not match durable owner ${owner.candidateId}`);
1672
- }
1673
- if (!position.candidateId) {
1674
- if (!(source instanceof LiveAdapter)) {
1675
- throw new Error('candidate-bound native bracket proof is unavailable');
1676
- }
1677
- const bracket = source.getBracketLedger()?.getBySymbol(position.symbol);
1678
- if (!bracket
1679
- || bracket.entryCid !== owner.clientOrderId
1680
- || bracket.entrySide !== owner.side) {
1681
- throw new Error(`native bracket ownership does not match durable Wave 9 owner ${owner.candidateId}`);
1682
- }
1683
- }
1684
- return owner.candidateId;
1685
- },
1686
- settleAfterClose: async (candidateId, symbol) => {
1687
- if (!source.isLive || runtime.mode !== 'LIVE' || runtime.adapter !== source) {
1688
- const record = liveLedger.getByCandidateId(candidateId);
1689
- return {
1690
- status: 'manual_intervention',
1691
- detail: 'IMMEDIATE MANUAL INTERVENTION REQUIRED: adapter changed during Wave 9 stop close recovery',
1692
- candidateId,
1693
- symbol,
1694
- stopPrice: record?.initialStop ?? Number.NaN,
1695
- };
1696
- }
1697
- const result = await recoverWave9LiveExposure(source, liveLedger, candidateId, symbol);
1698
- logWave9Recovery('stop_watcher', result);
1699
- return result;
1700
- },
1701
- });
1702
- },
1703
- });
1704
- }
1705
- // Start only after the durable Wave 9 close lifecycle has been installed.
1706
- // If the ledger failed to load, Wave 9 admission is disabled and generic
1707
- // watcher behavior still starts normally here.
1708
- bootstrapWatcher.start();
1709
- // Start the approved-order listener only after runtime, the shared
1710
- // mutation lock, and durable Wave 9 ownership state are fully resolved.
1711
- if (runtime.adapter.isLive
1712
- && approvalModeForTool === 'per_trade'
1713
- && proposalManagerCtx
1714
- && proposalListenerCredentials) {
1715
- try {
1716
- const approvalCfg = readPluginConfig().approval;
1717
- let cfgForUrl;
1718
- try {
1719
- cfgForUrl = readPluginConfig();
1720
- }
1721
- catch {
1722
- cfgForUrl = undefined;
1723
- }
1724
- const ingestBaseUrl = getUserDataStreamIngestBaseUrl(cfgForUrl);
1725
- proposalDecisionListener = new ProposalDecisionListener({
1726
- baseUrl: ingestBaseUrl,
1727
- ingestToken: proposalListenerCredentials.ingestToken,
1728
- userId: proposalListenerCredentials.userId,
1729
- adapter: runtime.adapter,
1730
- binanceApi,
1731
- autoCapture,
1732
- operationLock: wave9OperationLock,
1733
- checkWave9LiveSymbolOwnership,
1734
- pollIntervalMs: approvalCfg?.pollIntervalMs ?? 3_000,
1735
- });
1736
- proposalDecisionListener.start();
1737
- logger.info(TAG, `ProposalDecisionListener started (poll ${approvalCfg?.pollIntervalMs ?? 3_000} ms)`);
1738
- }
1739
- catch (err) {
1740
- logger.warn(TAG, `Failed to start ProposalDecisionListener: ${String(err)}`);
1741
- }
1742
- }
1743
- const wave9ActivationClient = new IntelWave9ActivationClient(intelApiDeps);
1744
- const getWave9AccountSnapshot = async () => {
1745
- if (!runtime.adapter.isLive) {
1746
- reloadState();
1747
- // Seed the generic daily-drawdown anchor before binding it into a
1748
- // Wave 9 fingerprint. Normal create_order keeps getSessionStartNav().
1749
- runtime.adapter.getSimulator().getSessionStartNav();
1750
- const capture = captureWave9PaperAccountSnapshot(runtime.adapter);
1751
- const quote = capture.snapshot.wallet.find((row) => row.currency === capture.snapshot.quoteCurrency);
1752
- const positions = capture.snapshot.positions.map((position) => {
1753
- let positionFingerprint;
1754
- try {
1755
- positionFingerprint = captureWave9PaperPositionFingerprint(runtime.adapter, position.symbol).fingerprint;
1756
- }
1757
- catch {
1758
- // Malformed or duplicate local geometry remains visible but cannot
1759
- // receive a mechanical exit authorization.
1760
- }
1761
- const strategy = position.strategy === 'tsmom_28d_long_flat_reversal_1d'
1762
- || position.strategy === 'tsmom_28d_short_flat_reversal_1d'
1763
- ? position.strategy
1764
- : undefined;
1765
- return {
1766
- symbol: position.symbol,
1767
- side: position.side,
1768
- quantity: position.quantity,
1769
- entryPrice: position.entryPrice,
1770
- markPrice: position.markPrice,
1771
- ...(position.stopPrice !== null ? { stopPrice: position.stopPrice } : {}),
1772
- ...(position.missionId ? { missionId: position.missionId } : {}),
1773
- ...(position.setupType ? { setupType: position.setupType } : {}),
1774
- ...(position.candidateId ? { candidateId: position.candidateId } : {}),
1775
- ...(strategy ? { strategy } : {}),
1776
- ...(position.accruedFundingUsd !== null
1777
- ? { accruedFundingUsd: position.accruedFundingUsd }
1778
- : {}),
1779
- openedAt: position.openedAt,
1780
- ...(positionFingerprint ? { positionFingerprint } : {}),
1781
- };
1782
- });
1783
- return {
1784
- tradingMode: runtime.mode,
1785
- positionsAvailable: true,
1786
- openOrdersAvailable: true,
1787
- openOrderCount: capture.snapshot.openOrders.length,
1788
- cashEquityAvailable: quote !== undefined && Number.isFinite(quote.total),
1789
- ...(quote !== undefined && Number.isFinite(quote.total)
1790
- ? { cashEquityUsd: quote.total }
1791
- : {}),
1792
- positions,
1793
- executedCandidateIds: capture.snapshot.closedWave9CandidateIds,
1794
- source: 'paper_simulator_wallet_positions_and_orders',
1795
- accountFingerprint: capture.fingerprint,
1796
- authoritative: false,
1797
- runtimeParity: false,
1798
- provenance: capture.provenance,
1799
- reasons: [
1800
- 'cross_process_single_owner_lease_absent',
1801
- 'cross_process_lease_absent_duplicate_concurrent_admission_not_ruled_out',
1802
- 'market_mark_fingerprint_drift_requires_immediate_status_recall',
1803
- ],
1804
- };
1805
- }
1806
- const nativeStopProtectionAvailable = runtime.mode === 'LIVE'
1807
- && runtime.adapter instanceof LiveAdapter
1808
- && bracketsEnabled(loadBracketMode())
1809
- && runtime.adapter.getBracketManager() !== null
1810
- && runtime.adapter.getBracketLedger() !== null;
1811
- if (runtime.mode === 'LIVE' && runtime.adapter.isLive && wave9LiveLedger) {
1812
- try {
1813
- const capture = await captureWave9LiveAccount(runtime.adapter, wave9LiveLedger, 'LIVE');
1814
- return {
1815
- tradingMode: 'LIVE',
1816
- positionsAvailable: true,
1817
- openOrdersAvailable: true,
1818
- openOrderCount: capture.pendingExposureOrderCount,
1819
- cashEquityAvailable: true,
1820
- cashEquityUsd: capture.cashEquityUsd,
1821
- positions: capture.positions,
1822
- executedCandidateIds: capture.executedCandidateIds,
1823
- source: 'live_exchange_wallet_positions_orders_and_wave9_ledger',
1824
- accountFingerprint: capture.fingerprint,
1825
- authoritative: capture.authoritative,
1826
- runtimeParity: false,
1827
- provenance: capture.provenance,
1828
- adapterReadiness: runtime.adapter.readiness,
1829
- nativeStopProtectionAvailable: nativeStopProtectionAvailable && capture.nativeStopProtectionComplete,
1830
- reasons: [
1831
- 'live_candidate_identity_uses_local_durable_execution_ledger',
1832
- 'cross_process_lease_absent_duplicate_concurrent_admission_not_ruled_out',
1833
- 'live_funding_is_not_attributed_per_wave9_position',
1834
- ...capture.nativeStopIssues.map((issue) => `native_stop_issue:${issue}`),
1835
- ],
1836
- };
1837
- }
1838
- catch (error) {
1839
- return {
1840
- tradingMode: 'LIVE',
1841
- positionsAvailable: false,
1842
- openOrdersAvailable: false,
1843
- cashEquityAvailable: false,
1844
- positions: [],
1845
- source: 'live_exchange_snapshot_unavailable',
1846
- authoritative: false,
1847
- runtimeParity: false,
1848
- provenance: 'live_exchange_snapshot_unavailable',
1849
- adapterReadiness: runtime.adapter.readiness,
1850
- nativeStopProtectionAvailable,
1851
- reasons: [`live_snapshot_failed:${formatError(error)}`],
1852
- };
1853
- }
1854
- }
1855
- return {
1856
- tradingMode: runtime.mode,
1857
- positionsAvailable: false,
1858
- openOrdersAvailable: false,
1859
- cashEquityAvailable: false,
1860
- positions: [],
1861
- source: 'wave9_execution_runtime_unavailable',
1862
- authoritative: false,
1863
- runtimeParity: false,
1864
- provenance: 'wave9_execution_runtime_unavailable',
1865
- adapterReadiness: runtime.adapter.readiness,
1866
- nativeStopProtectionAvailable,
1867
- reasons: [
1868
- wave9LiveLedger
1869
- ? 'Wave 9 execution requires the current mode to be PAPER or an exact LIVE exchange runtime'
1870
- : 'Wave 9 live execution ledger is unavailable',
1871
- ],
1872
- };
1873
- };
1874
- const captureWave9AccountFingerprint = async () => {
1875
- if (runtime.mode !== 'LIVE' || !runtime.adapter.isLive || !wave9LiveLedger) {
1876
- throw new Error('Wave 9 authoritative LIVE account capture is unavailable');
1877
- }
1878
- const capture = await captureWave9LiveAccount(runtime.adapter, wave9LiveLedger, 'LIVE');
1879
- return { fingerprint: capture.fingerprint };
1880
- };
1881
- const captureWave9PositionFingerprint = async (symbol) => {
1882
- if (runtime.mode !== 'LIVE' || !runtime.adapter.isLive || !wave9LiveLedger) {
1883
- throw new Error('Wave 9 authoritative LIVE position capture is unavailable');
1884
- }
1885
- return captureWave9LivePosition(runtime.adapter, wave9LiveLedger, 'LIVE', symbol);
1886
- };
1887
- const confirmWave9LiveStop = async (input) => {
1888
- if (runtime.mode !== 'LIVE' || !(runtime.adapter instanceof LiveAdapter))
1889
- return false;
1890
- const liveAdapter = runtime.adapter;
1891
- const bracketLedger = liveAdapter.getBracketLedger();
1892
- if (!bracketLedger || !liveAdapter.getBracketManager())
1893
- return false;
1894
- const deadline = Date.now() + 20_000;
1895
- while (Date.now() < deadline) {
1896
- if (runtime.mode !== 'LIVE' || runtime.adapter !== liveAdapter)
1897
- return false;
1898
- const bracket = bracketLedger.getBySymbol(input.symbol);
1899
- if (bracket?.state === 'failed'
1900
- || bracket?.state === 'cancelled'
1901
- || bracket?.state === 'triggered_sl'
1902
- || bracket?.state === 'triggered_tp')
1903
- return false;
1904
- if (bracket && (bracket.entryCid !== input.entryClientOrderId
1905
- || bracket.entrySide !== input.entrySide))
1906
- return false;
1907
- if (bracket
1908
- && (bracket.state === 'active' || bracket.state === 'partial')) {
1909
- return confirmWave9LiveNativeStop(liveAdapter, input);
1910
- }
1911
- await new Promise((resolve) => setTimeout(resolve, 250));
1912
- }
1913
- return false;
1914
- };
1915
- // Facts-out local signal service (step 5). Created ONLY when the operator
1916
- // flips signals.evaluator to shadow/enforce in plugin-config — default
1917
- // 'central' leaves the get_signals path byte-identical (no service, no SSE
1918
- // connection, no behaviour change). Requires the intel fact stream on
1919
- // (RC_INTEL_FACTS_STREAM=on) + a configured symbols list.
1920
- let localSignalService = null;
1921
- if (signalsEvaluator !== 'central' && connectionToken && signalsSymbols.length > 0) {
1922
- localSignalService = new LocalSignalService({ intelligenceUrl, connectionToken, symbols: signalsSymbols });
1923
- localSignalService.start().catch((err) => logger.warn(TAG, `Local signal service start failed: ${formatError(err)}`));
1924
- logger.info(TAG, `Facts-out local evaluator=${signalsEvaluator} for ${signalsSymbols.length} symbols`);
1925
- }
1926
- const getSignalsDeps = {
1927
- ...intelApiDeps,
1928
- evaluatorMode: signalsEvaluator,
1929
- localSignalService,
1930
- logger,
1931
- };
1932
- // Microstructure heartbeat-push assembler. When the operator flips
1933
- // `microstructure.heartbeatPush=on` in plugin-config, every
1934
- // record_position_reviews call auto-fetches resting-liquidity +
1935
- // liquidation-pulse from intel and fills the orderbook columns
1936
- // (restingBandImbalance05 / cascadePulseAtReview / coveragePctAtReview).
1937
- // The assembler reads the flag itself; off-mode short-circuits to no
1938
- // network calls. Resolves PR1 deferral #3.
1939
- const microstructureAssembler = new IntelMicrostructureAssembler({
1940
- intelDeps: intelApiDeps,
1941
- });
1942
- // ---- Graceful shutdown ----
1943
- // Wired in a separate module (lifecycle/install-signal-handlers) so the
1944
- // network-send call sites (TradeStoreClient.drain, LiveAdapter.shutdown)
1945
- // never share a compiled file with the env-var reads above. Avoids a
1946
- // false-positive on OpenClaw's plugin-install heuristic that flags
1947
- // env+network co-location as possible credential harvesting.
1948
- installSignalHandlers({
1949
- runtime,
1950
- tradeStore: tradeIngest?.client,
1951
- proposalListener: proposalDecisionListener,
1952
- stateManager,
1953
- });
1954
- // Warm the trading params cache (fire-and-forget — register must be sync)
1955
- refreshTradingParams(intelApiDeps).catch(err => {
1956
- logger.warn(TAG, `Failed to warm trading params cache: ${formatError(err)}`);
1957
- });
1958
- logger.info(TAG, `Plugin initialized in ${tradingMode} mode. Registering tools...`);
1959
- // Build tools array
1960
- const tools = [
1961
- {
1962
- name: 'fetch_ticker',
1963
- label: 'Fetch Ticker',
1964
- description: 'Fetch current ticker data (price, volume, 24h change) for a trading symbol. Uses real Binance market data.',
1965
- parameters: TOOL_PARAMS.fetch_ticker,
1966
- execute: async (_id, params) => jsonResult(await fetchTickerTool(params, simDeps)),
1967
- },
1968
- {
1969
- name: 'fetch_ohlcv',
1970
- label: 'Fetch OHLCV',
1971
- description: 'Fetch OHLCV candlestick data for a symbol. Uses real Binance market data.',
1972
- parameters: TOOL_PARAMS.fetch_ohlcv,
1973
- execute: async (_id, params) => jsonResult(await fetchOhlcvTool(params, simDeps)),
1974
- },
1975
- {
1976
- name: 'fetch_balance',
1977
- label: 'Fetch Balance',
1978
- description: 'Fetch current wallet balance. Returns real balance in live mode, simulated in paper mode.',
1979
- parameters: TOOL_PARAMS.fetch_balance,
1980
- execute: async () => {
1981
- if (!runtime.adapter.isLive)
1982
- reloadState();
1983
- const balance = await fetchBalanceTool({}, adapterDeps);
1984
- // Piggyback trading mode + readiness on balance response
1985
- let displayBalance = { ...balance, tradingMode: runtime.mode, adapterReadiness: runtime.adapter.readiness };
1986
- // SHADOW mode: override wallet display with real Binance balance.
1987
- // Execution is still paper but KPI shows real funds.
1988
- if (runtime.mode === 'SHADOW' && shadowPrivateApi) {
1989
- try {
1990
- const liveBalance = await shadowPrivateApi.fetchBalance();
1991
- if (liveBalance) {
1992
- const quoteTotal = getQuoteWalletBalance(liveBalance);
1993
- const quoteFree = getQuoteBalance(liveBalance.free);
1994
- const quoteUsed = getQuoteBalance(liveBalance.used);
1995
- displayBalance = {
1996
- ...displayBalance,
1997
- free: liveBalance.free,
1998
- used: liveBalance.used,
1999
- total: liveBalance.total,
2000
- // Copy per-currency CCXT shape entries (e.g., USDT: {free,used,total}, USDC: {…})
2001
- ...(liveBalance.USDT ? { USDT: liveBalance.USDT } : {}),
2002
- ...(liveBalance.USDC ? { USDC: liveBalance.USDC } : {}),
2003
- equity: +quoteTotal.toFixed(4),
2004
- sessionStartNav: +quoteTotal.toFixed(4), // real balance IS the anchor
2005
- };
2006
- }
2007
- }
2008
- catch (err) {
2009
- logger.warn(TAG, `Shadow balance fallback to paper: ${formatError(err)}`);
2010
- }
2011
- }
2012
- return jsonResult(displayBalance);
2013
- },
2014
- },
2015
- {
2016
- name: 'fetch_positions',
2017
- label: 'Fetch Positions',
2018
- description: 'Fetch open positions with mark-to-market P&L. Returns real positions in live mode, simulated in paper mode.',
2019
- parameters: TOOL_PARAMS.fetch_positions,
2020
- execute: async (_id, params) => {
2021
- if (!runtime.adapter.isLive)
2022
- reloadState();
2023
- return jsonResult(await fetchPositionsTool(params, adapterDeps));
2024
- },
2025
- },
2026
- {
2027
- name: 'fetch_open_orders',
2028
- label: 'Fetch Open Orders',
2029
- description: 'Fetch pending orders. Returns real orders in live mode, simulated in paper mode.',
2030
- parameters: TOOL_PARAMS.fetch_open_orders,
2031
- execute: async (_id, params) => {
2032
- if (!runtime.adapter.isLive)
2033
- reloadState();
2034
- return jsonResult(await fetchOpenOrdersTool(params, adapterDeps));
2035
- },
2036
- },
2037
- {
2038
- name: 'create_order',
2039
- label: 'Create Order',
2040
- description: 'Create a buy or sell order. In paper mode, fills are simulated. In live mode, orders go to Binance Futures.',
2041
- parameters: TOOL_PARAMS.create_order,
2042
- execute: async (_id, params) => jsonResult(await createOrderTool(params, {
2043
- ...adapterDeps,
2044
- adapter: runtime.adapter,
2045
- autoCapture,
2046
- proposalManager: proposalManagerCtx?.manager,
2047
- userId: proposalManagerCtx?.userId,
2048
- approvalMode: approvalModeForTool,
2049
- wave9AdmissionGuard,
2050
- wave9ActivationCheck: () => wave9ActivationClient.getActivation(),
2051
- refreshPaperState: reloadState,
2052
- wave9TradingModeCheck: () => runtime.mode,
2053
- wave9VenueCheck: () => venue,
2054
- captureWave9AccountFingerprint,
2055
- confirmWave9LiveStop,
2056
- wave9LiveLedger,
2057
- checkWave9LiveSymbolOwnership,
2058
- operationLock: wave9OperationLock,
2059
- })),
2060
- },
2061
- {
2062
- name: 'cancel_order',
2063
- label: 'Cancel Order',
2064
- description: 'Cancel a single open order by its ID.',
2065
- parameters: TOOL_PARAMS.cancel_order,
2066
- execute: async (_id, params) => jsonResult(await cancelOrderTool(params, {
2067
- ...adapterDeps,
2068
- adapter: runtime.adapter,
2069
- operationLock: wave9OperationLock,
2070
- checkWave9LiveSymbolOwnership,
2071
- })),
2072
- },
2073
- {
2074
- name: 'cancel_all_orders',
2075
- label: 'Cancel All Orders',
2076
- description: 'Cancel all open orders, optionally filtered by symbol.',
2077
- parameters: TOOL_PARAMS.cancel_all_orders,
2078
- execute: async (_id, params) => jsonResult(await cancelAllOrdersTool(params, {
2079
- ...adapterDeps,
2080
- adapter: runtime.adapter,
2081
- operationLock: wave9OperationLock,
2082
- })),
2083
- },
2084
- {
2085
- name: 'close_position',
2086
- label: 'Close Position',
2087
- description: 'Close an open position at market price by creating an opposing market order.',
2088
- parameters: TOOL_PARAMS.close_position,
2089
- execute: async (_id, params) => jsonResult(await closePositionTool(params, {
2090
- ...adapterDeps,
2091
- adapter: runtime.adapter,
2092
- autoCapture,
2093
- wave9AdmissionGuard,
2094
- refreshPaperState: reloadState,
2095
- wave9TradingModeCheck: () => runtime.mode,
2096
- wave9VenueCheck: () => venue,
2097
- captureWave9PositionFingerprint,
2098
- wave9LiveLedger,
2099
- checkWave9LiveSymbolOwnership,
2100
- operationLock: wave9OperationLock,
2101
- })),
2102
- },
2103
- {
2104
- name: 'modify_stop',
2105
- label: 'Modify Stop',
2106
- description: 'Move the exchange-side stop-loss on an open live position. Atomic cancel-then-resubmit with automatic rollback. Live mode only; requires brackets.mode enabled.',
2107
- parameters: TOOL_PARAMS.modify_stop,
2108
- execute: async (_id, params) => jsonResult(await modifyStopTool(params, {
2109
- ...adapterDeps,
2110
- adapter: runtime.adapter,
2111
- operationLock: wave9OperationLock,
2112
- checkWave9LiveSymbolOwnership,
2113
- })),
2114
- },
2115
- {
2116
- name: 'modify_target',
2117
- label: 'Modify Target',
2118
- description: 'Move the exchange-side take-profit on an open live position. Atomic cancel-then-resubmit with automatic rollback. Live mode only; requires brackets.mode enabled.',
2119
- parameters: TOOL_PARAMS.modify_target,
2120
- execute: async (_id, params) => jsonResult(await modifyTargetTool(params, {
2121
- ...adapterDeps,
2122
- adapter: runtime.adapter,
2123
- operationLock: wave9OperationLock,
2124
- checkWave9LiveSymbolOwnership,
2125
- })),
2126
- },
2127
- {
2128
- name: 'attach_brackets',
2129
- label: 'Attach Brackets',
2130
- description: 'Bootstrap exchange-side STOP_MARKET + TAKE_PROFIT_MARKET protection on an existing open live position that is currently naked. Use this to recover when a position exists without active brackets (e.g. after kill-switch stripped them or brackets failed to attach at entry). Rejects if brackets are already active for the symbol — use modify_stop / modify_target instead. Live mode only; requires brackets.mode enabled.',
2131
- parameters: TOOL_PARAMS.attach_brackets,
2132
- execute: async (_id, params) => jsonResult(await attachBracketsTool(params, {
2133
- ...adapterDeps,
2134
- adapter: runtime.adapter,
2135
- operationLock: wave9OperationLock,
2136
- checkWave9LiveSymbolOwnership,
2137
- })),
2138
- },
2139
- {
2140
- name: 'audit_bracket_protection',
2141
- label: 'Audit Bracket Protection',
2142
- description: 'Return every open position with a verdict on whether exchange-side SL/TP protection is attached. Agent should run at SESSION START and every heartbeat — any has_stop=false must be resolved via attach_brackets or close_position before continuing. Works in both live and paper modes.',
2143
- parameters: TOOL_PARAMS.audit_bracket_protection,
2144
- execute: async () => jsonResult(await auditBracketProtectionTool({}, adapterDeps)),
2145
- },
2146
- {
2147
- name: 'record_position_reviews',
2148
- label: 'Record Position Reviews',
2149
- description: 'File a structured per-heartbeat review for every open position in one plural call. Schema-validated like v2.10.0 close_position. Required when positionReview.mode is shadow / observe / enforce; optional in off-mode. See SKILL.md "Position Reviews".',
2150
- parameters: TOOL_PARAMS.record_position_reviews,
2151
- execute: async (_id, params) => jsonResult(await recordPositionReviewsTool(params, {
2152
- adapter,
2153
- decisionsClient: positionDecisionsClient,
2154
- stateStore: positionStateStore,
2155
- userId: positionDecisionsUserId,
2156
- gateMode: loadPositionReviewMode(),
2157
- microstructureAssembler,
2158
- })),
2159
- },
2160
- {
2161
- name: 'get_my_recent_reviews',
2162
- label: 'Get My Recent Reviews',
2163
- description: 'Self-reflection lookup: per-position last N reviews + verdict-streak counter + 24h re-entry context. Call this BEFORE record_position_reviews each heartbeat to anchor your verdict in your own prior reasoning. Omit symbol to fetch all open positions in one call.',
2164
- parameters: TOOL_PARAMS.get_my_recent_reviews,
2165
- execute: async (_id, params) => jsonResult(await getMyRecentReviewsTool(params, {
2166
- decisionsClient: positionDecisionsClient,
2167
- stateStore: positionStateStore,
2168
- userId: positionDecisionsUserId,
2169
- })),
2170
- },
2171
- {
2172
- name: 'get_relevant_learnings',
2173
- label: 'Get Relevant Learnings',
2174
- description: 'Phase 4 curated learnings: returns up to 5 operator-confirmed learnings whose trigger_condition matches the current decision context. Call at entry/heartbeat/close to read short directives backed by mined-pattern evidence. Learnings inform, they do not gate — your decision still stands.',
2175
- parameters: TOOL_PARAMS.get_relevant_learnings,
2176
- execute: async (_id, params) => jsonResult(await getRelevantLearningsTool(params, {
2177
- decisionsClient: positionDecisionsClient,
2178
- userId: positionDecisionsUserId,
2179
- })),
2180
- },
2181
- {
2182
- name: 'get_my_mined_patterns',
2183
- label: 'Get My Mined Patterns',
2184
- description: 'Phase 4.5: read your own statistically-significant patterns from your trade history. Use BEFORE propose_learning to pick which patterns deserve a directive. Returns rows with effect_size, p_value, match_conditions, and human_summary. Per-user — never returns other users\' data.',
2185
- parameters: TOOL_PARAMS.get_my_mined_patterns,
2186
- execute: async (_id, params) => jsonResult(await getMyMinedPatternsTool(params, {
2187
- decisionsClient: positionDecisionsClient,
2188
- userId: positionDecisionsUserId,
2189
- })),
2190
- },
2191
- {
2192
- name: 'get_my_proposed_learnings',
2193
- label: 'Get My Proposed Learnings',
2194
- description: 'Phase 4.5: list your own learnings across all confidence states (hypothesis / confirmed / retired). Use BEFORE propose_learning to avoid duplicate drafts. Distinct from get_relevant_learnings, which only returns confirmed + context-matched.',
2195
- parameters: TOOL_PARAMS.get_my_proposed_learnings,
2196
- execute: async (_id, params) => jsonResult(await getMyProposedLearningsTool(params, {
2197
- decisionsClient: positionDecisionsClient,
2198
- userId: positionDecisionsUserId,
2199
- })),
2200
- },
2201
- {
2202
- name: 'propose_learning',
2203
- label: 'Propose Learning',
2204
- description: 'Phase 4.5 self-assessment write: draft a new learning candidate for the operator to review. proposed_by is forced to "agent" server-side; confidence is server-determined from the operator\'s trust mode (manual → hypothesis, auto → confirmed + auto_confirmed=true). Throttle: max 3 per rolling 24h per user. Cite source_mined_pattern_id when possible. Directive must include n / effect / p / recent example / default action / override conditions.',
2205
- parameters: TOOL_PARAMS.propose_learning,
2206
- execute: async (_id, params) => jsonResult(await proposeLearningTool(params, {
2207
- decisionsClient: positionDecisionsClient,
2208
- userId: positionDecisionsUserId,
2209
- })),
2210
- },
2211
- {
2212
- name: 'query_review_outcomes',
2213
- label: 'Query Review Outcomes',
2214
- description: 'Aggregate stats + sample over your past position reviews joined to their close outcomes. Filter by verdict, thesis_status, regime, symbol, setup_type, days_back, etc. Surfaces verdict patterns that bleed edge — median R < 0 across 5+ closed samples is your evidence that a (verdict, thesis_status) combination is consistently underperforming. Call at session start and daily.',
2215
- parameters: TOOL_PARAMS.query_review_outcomes,
2216
- execute: async (_id, params) => jsonResult(await queryReviewOutcomesTool(params, {
2217
- decisionsClient: positionDecisionsClient,
2218
- userId: positionDecisionsUserId,
2219
- })),
2220
- },
2221
- {
2222
- name: 'get_market_structure',
2223
- label: 'Get Market Structure',
2224
- description: 'Multi-timeframe technical analysis — trend direction, regime, RSI, EMA crossover, momentum for 5m/15m/1h/4h/1d/1w. Uses real Binance market data.',
2225
- parameters: TOOL_PARAMS.get_market_structure,
2226
- execute: async (_id, params) => {
2227
- const result = await getMarketStructureTool(params, simDeps);
2228
- // Cache ATR data in simulator for pre-trade risk gate (paper mode)
2229
- if (!runtime.adapter.isLive && 'atr' in result && result.atr) {
2230
- const atrData = result.atr;
2231
- simulator.updateBaselineAtr(atrData.atr14);
2232
- const baseline = simulator.getBaselineAtr();
2233
- if (baseline > 0) {
2234
- simulator.setVolFactor(computeVolFactor(atrData.atr14, baseline));
2235
- }
2236
- }
2237
- return jsonResult(result);
2238
- },
2239
- },
2240
- {
2241
- name: 'get_crypto_metrics',
2242
- label: 'Get Crypto Metrics',
2243
- description: 'Fetch funding rate (current + annualized) and open interest for a perpetual futures symbol. Useful for gauging market sentiment and crowding.',
2244
- parameters: TOOL_PARAMS.get_crypto_metrics,
2245
- execute: async (_id, params) => jsonResult(await getCryptoMetricsTool(params, simDeps)),
2246
- },
2247
- {
2248
- name: 'get_volume_analysis',
2249
- label: 'Get Volume Analysis',
2250
- description: 'Analyze recent volume: expansion ratio vs 20-bar average, directional thrust (-1 bearish to +1 bullish), close position in bar range, buy volume estimate. Detects volume surges and liquidity.',
2251
- parameters: TOOL_PARAMS.get_volume_analysis,
2252
- execute: async (_id, params) => jsonResult(await getVolumeAnalysisTool(params, simDeps)),
2253
- },
2254
- {
2255
- name: 'get_risk_summary',
2256
- label: 'Get Risk Summary',
2257
- description: 'Get current portfolio risk metrics: gross/net exposure, unrealized PnL, heat score (0-100), position breakdown, trading mode, shadow metrics. Use before trade decisions to assess risk budget.',
2258
- parameters: TOOL_PARAMS.get_risk_summary,
2259
- execute: async () => {
2260
- reloadState();
2261
- return jsonResult(await getRiskSummaryTool({}, { ...simDeps, tradingMode: runtime.mode, shadowTracker, adapter: runtime.adapter }));
2262
- },
2263
- },
2264
- {
2265
- name: 'get_market_intel',
2266
- label: 'Get Market Intelligence',
2267
- description: 'Fetch market intelligence from ReefClaw: sentiment (fear/greed, BTC dominance), news (crypto headlines with sentiment), calendar (upcoming economic events), overview (top coins, trending), derivatives (OI, funding rates, long/short ratios), onchain (BTC network stats, mempool fees, whale transactions), social (community data, trending posts). Requires Pro subscription.',
2268
- parameters: TOOL_PARAMS.get_market_intel,
2269
- execute: async (_id, params) => jsonResult(await getMarketIntelTool(params, intelDeps)),
2270
- },
2271
- {
2272
- name: 'get_regime',
2273
- label: 'Get Market Regime',
2274
- description: 'Get the current market regime classification (trend_up, trend_down, range_tight, range_wide, high_vol, avoid) with confidence score, class probabilities, and top contributing features. Powered by XGBoost model trained on 20 features from order flow, volatility, momentum, and derivatives data.',
2275
- parameters: TOOL_PARAMS.get_regime,
2276
- execute: async (_id, params) => jsonResult(await getRegimeTool(params, intelApiDeps)),
2277
- },
2278
- {
2279
- name: 'get_signals',
2280
- label: 'Get Trading Signals',
2281
- description: 'Get active trading signals from the intelligence engine. Returns strategy snapshots with confluence scores (how many conditions are met), entry zones, stop losses, price targets, and recent signal history. Three strategies: trend_continuation, liquidity_sweep, funding_reversion — each gated by regime classification.',
2282
- parameters: TOOL_PARAMS.get_signals,
2283
- execute: async (_id, params) => jsonResult(await getSignalsTool(params, getSignalsDeps)),
2284
- },
2285
- {
2286
- name: 'get_wave9_status',
2287
- label: 'Get Wave 9 Status',
2288
- description: 'MANDATORY fresh call for every question about which strategies are active, usable, available, or tradeable now: never answer from memory; also call list_strategies fresh with active_only=true in the same turn. The operator reply MUST reproduce every line from requiredOperatorReport.lines exactly enough to preserve all labels, counts, and reasons. These are read-only status calls; the question alone does not authorize placing or closing an order or toggling a strategy. Return any single-use entry or reversal token. An empty token set means wait for the next valid signal/window; it does not disable LIVE trading. LIVE orders use real capital.',
2289
- parameters: TOOL_PARAMS.get_wave9_status,
2290
- execute: async () => jsonResult(await getWave9StatusTool({
2291
- activationClient: wave9ActivationClient,
2292
- getAccountSnapshot: getWave9AccountSnapshot,
2293
- operationLock: wave9OperationLock,
2294
- authorizationIssuer: wave9AdmissionGuard,
2295
- executionVenue: venue,
2296
- })),
2297
- },
2298
- {
2299
- name: 'get_analytics',
2300
- label: 'Get Edge Analytics',
2301
- description: 'Get strategy performance analytics: per-strategy win rate (rolling 20/50/all), expectancy, profit factor, Sharpe ratio, avg hold duration, regime breakdown, equity curve with drawdown, and degradation alerts. Strategies with negative rolling-20 expectancy are auto-gated.',
2302
- parameters: TOOL_PARAMS.get_analytics,
2303
- execute: async (_id, params) => jsonResult(await getAnalyticsTool(params, intelApiDeps)),
2304
- },
2305
- {
2306
- name: 'get_orderbook',
2307
- label: 'Get Order Book',
2308
- description: 'Fetch L2 order book depth from Binance. Returns top N bid/ask levels with prices and quantities, plus computed metrics: spread (absolute + bps), mid-price, bid/ask volume totals, and order book imbalance ratio (positive = bid-heavy / bullish, negative = ask-heavy / bearish). Uses real Binance data.',
2309
- parameters: TOOL_PARAMS.get_orderbook,
2310
- execute: async (_id, params) => jsonResult(await getOrderbookTool(params, simDeps)),
2311
- },
2312
- {
2313
- name: 'get_volume_profile',
2314
- label: 'Get Volume Profile',
2315
- description: 'Get volume-at-price profile from the intelligence engine. Returns price buckets with buy/sell volume split, Point of Control (POC — highest volume level), Value Area High/Low (70% of volume), current price, and total volume. Use to identify key support/resistance and high-volume nodes.',
2316
- parameters: TOOL_PARAMS.get_volume_profile,
2317
- execute: async (_id, params) => jsonResult(await getVolumeProfileTool(params, intelApiDeps)),
2318
- },
2319
- {
2320
- name: 'get_liquidation_levels',
2321
- label: 'Get Liquidation Levels',
2322
- description: 'Get liquidation levels from the intelligence engine. Returns historical liquidation clusters (where liquidations actually happened) and estimated liquidation levels based on open interest and leverage tiers (5x/10x/25x/50x/100x). Shows price levels where cascading liquidations could accelerate moves. Use for risk assessment and stop placement.',
2323
- parameters: TOOL_PARAMS.get_liquidation_levels,
2324
- execute: async (_id, params) => jsonResult(await getLiquidationLevelsTool(params, intelApiDeps)),
2325
- },
2326
- {
2327
- name: 'get_trade_feedback',
2328
- label: 'Get Trade Feedback',
2329
- description: 'Get structured performance feedback: per-setup-type win rates (rolling 20 + all-time) and verdicts (STRONG/WORKING/MARGINAL/FAILING) with edge trend detection (improving/stable/declining), per-regime recommendations (best setup type per regime with stats), current streak, last 10 trade outcomes, and overall stats. Use before entering trades to check if your current setup type is working in the current regime. Tag your trades with setup types for better tracking.',
2330
- parameters: TOOL_PARAMS.get_trade_feedback,
2331
- execute: async (_id, params) => jsonResult(await getTradeFeedbackTool(params, intelApiDeps)),
2332
- },
2333
- {
2334
- name: 'get_trade_flow',
2335
- label: 'Get Trade Flow',
2336
- description: 'Get aggregated trade flow / institutional flow data. Returns buy/sell volume, large trade (>$50k whale) activity, net delta, buy/sell ratio, per-minute breakdown, and whale pressure verdict (strong_buy/buy/neutral/sell/strong_sell). Use before entering trades to confirm whether institutional flow supports your direction. Large trade ratio >15% with matching direction = strong institutional confirmation.',
2337
- parameters: TOOL_PARAMS.get_trade_flow,
2338
- execute: async (_id, params) => jsonResult(await getTradeFlowTool(params, intelApiDeps)),
2339
- },
2340
- {
2341
- name: 'score_setup',
2342
- label: 'Score Setup',
2343
- description: 'Pre-trade setup scorecard. Scores a proposed trade across 5 dimensions: risk/reward ratio, regime alignment, regime confidence, setup edge (from your track record), and institutional flow alignment. Returns a composite score (0-10) and verdict (STRONG_GO/GO/MARGINAL/NO_GO) with specific flags and a recommendation. Use this BEFORE every trade entry to enforce quality filters. NO_GO means do not trade.',
2344
- parameters: TOOL_PARAMS.score_setup,
2345
- execute: async (_id, params) => jsonResult(await scoreSetupTool(params, intelApiDeps)),
2346
- },
2347
- {
2348
- name: 'get_sizing',
2349
- label: 'Get Position Sizing',
2350
- description: 'Get recommended position size using Half-Kelly formula with volatility adjustment. Returns sizePct (fraction of account to risk), Kelly fraction, current win rate, avg win/loss ratio, volatility adjustment factor, and capping reason. Use this before sizing trades — multiply sizePct by your account equity to get the dollar risk amount.',
2351
- parameters: TOOL_PARAMS.get_sizing,
2352
- execute: async (_id, params) => jsonResult(await getSizingTool(params, intelApiDeps)),
2353
- },
2354
- {
2355
- name: 'check_position_health',
2356
- label: 'Check Position Health',
2357
- description: 'Real-time position health monitor. For each position, computes: P&L in dollars/percent/R-multiples, distance to stop (% and R), stop status (SAFE/CLOSE/CRITICAL/BREACHED), distance to target, heat (position risk as % of account), time in trade with staleness detection. Returns per-position alerts and portfolio-level heat assessment (LOW/MODERATE/ELEVATED/HIGH/CRITICAL). Use on every heartbeat to monitor open positions. Alerts when stops are close, positions are stale, or heat is too high.',
2358
- parameters: TOOL_PARAMS.check_position_health,
2359
- execute: async (_id, params) => jsonResult(checkPositionHealthTool(params)),
2360
- },
2361
- {
2362
- name: 'get_risk_scenario',
2363
- label: 'Get Risk Scenario',
2364
- description: 'Stress test your positions against hypothetical price moves. Shows what happens to your P&L, heat score, and risk limits if price drops 3%, dumps 5%, or rallies 5%. Auto-generates scenarios from current ATR volatility if no specific moves provided. Flags which stops would trigger and which risk limits would breach. Use BEFORE entering trades to understand downside, and while holding positions to prepare for adverse moves.',
2365
- parameters: TOOL_PARAMS.get_risk_scenario,
2366
- execute: async (_id, params) => {
2367
- if (!runtime.adapter.isLive)
2368
- reloadState();
2369
- const state = simulator.getState();
2370
- return jsonResult(await getRiskScenarioTool(params, {
2371
- getPositions: () => state.positions,
2372
- getWallet: () => state.wallet,
2373
- getLastPrice: (sym) => simulator.getLastTicker(sym)?.last ?? null,
2374
- intelApiDeps,
2375
- }));
2376
- },
2377
- },
2378
- {
2379
- name: 'get_session_review',
2380
- label: 'Get Session Review',
2381
- description: 'Automated end-of-session performance summary. Returns trade count, win/loss breakdown, net P&L, best/worst trades, per-setup-type stats, per-regime stats, session-over-session comparison (improving/stable/declining), and actionable lessons ("3/4 losses in RANGE_WIDE — sit out ranging markets"). Use at end of every trading session to log learnings to memory.',
2382
- parameters: TOOL_PARAMS.get_session_review,
2383
- execute: async (_id, params) => jsonResult(await getSessionReviewTool(params, intelApiDeps)),
2384
- },
2385
- {
2386
- name: 'get_backtest',
2387
- label: 'Get Backtest',
2388
- description: 'Backtest a trading strategy against historical data. Runs walk-forward simulation over specified months of history with realistic fills, fees, and stop management. Returns win rate, expectancy, Sharpe ratio, max drawdown, per-strategy and per-regime breakdown, best/worst trades, and a one-line verdict. Use in Step 2 (REVIEW) before trading a strategy, and when entering a new regime to check historical performance. Async job model: the tool queues a job on the intel service and polls every 5s; the call returns ONLY when the job completes (or hits the cap). Just call it and wait — DO NOT roll your own bash/curl wrapper. Measured wall-clock on 1h timeframe (BTCUSDT, 2026-05-15): ~3min/6mo, ~5-6min/12mo, ~10-12min/24mo, ~15-20min/36mo. Single-call cap is 30min (server-side worker timeout). For longer ranges (60-72mo) call this tool multiple times with non-overlapping 12-month windows via the `months` arg — stitch per-window expectancy/win-rate/regime-breakdown manually (Sharpe/maxDD/equity-curve continuity does NOT survive splitting). Identical inputs hit a 1-hour result cache, so repeat calls return instantly.',
2389
- parameters: TOOL_PARAMS.get_backtest,
2390
- execute: async (_id, params) => jsonResult(await getBacktestTool(params, intelApiDeps)),
2391
- },
2392
- {
2393
- name: 'get_pattern_scan',
2394
- label: 'Get Pattern Scan',
2395
- description: 'Scan chart for patterns: swing points, trendlines, and 10 pattern types (HH/HL, LH/LL, double top/bottom, head & shoulders, inverse H&S, bull/bear flag, ascending/descending channel). Returns detected patterns with confidence scores, key price levels, and descriptions. Use in Step 3 (SCAN) alongside signals for structural context. The agent applies judgment on top of algorithmic candidates — not all detected patterns are tradeable.',
2396
- parameters: TOOL_PARAMS.get_pattern_scan,
2397
- execute: async (_id, params) => jsonResult(await getPatternScanTool(params, intelApiDeps)),
2398
- },
2399
- {
2400
- name: 'save_strategy',
2401
- label: 'Save Strategy',
2402
- description: 'Save a new trading strategy to the Strategy Box, or update an existing one. 23 available conditions: ema_proximity, orderbook_imbalance, funding_not_crowded, oi_slope, no_liquidation_cluster, price_sweep, liquidations_at_sweep, order_flow_absorption, funding_contrarian, funding_extreme, oi_elevated, price_at_level, macd_crossover, macd_divergence, bollinger_squeeze, bollinger_breakout, vwap_position, stoch_rsi_extreme, adx_trending, adx_ranging, ichimoku_cloud, obv_divergence, supertrend_direction. After saving, backtest it with get_backtest to validate before activating.',
2403
- parameters: TOOL_PARAMS.save_strategy,
2404
- execute: async (_id, params) => jsonResult(await saveStrategyTool(params, intelApiDeps)),
2405
- },
2406
- {
2407
- name: 'list_strategies',
2408
- label: 'List Strategies',
2409
- description: 'MANDATORY for every question about which strategies are active, usable, available, or tradeable now: never answer from memory; call this tool fresh with active_only=true and also call get_wave9_status fresh in the same turn. The operator reply MUST reproduce every line from get_wave9_status.requiredOperatorReport.lines exactly enough to preserve all labels, counts, and reasons. List strategies in the Strategy Box with name, description, regime whitelist, conditions, active status, and whether each is built in. Active means the operator enabled the strategy; it does not mean an order is authorized this instant. These are read-only status calls; the question alone does not authorize placing or closing an order or toggling a strategy.',
2410
- parameters: TOOL_PARAMS.list_strategies,
2411
- prepareArguments: prepareListStrategiesArgs,
2412
- execute: async (_id, params) => jsonResult(await listStrategiesTool(prepareListStrategiesArgs(params), intelApiDeps)),
2413
- },
2414
- {
2415
- name: 'toggle_strategy',
2416
- label: 'Toggle Strategy',
2417
- description: 'Activate or deactivate a strategy. Deactivated strategies are excluded from live signal evaluation but can still be backtested. Use to temporarily disable an underperforming strategy without deleting it.',
2418
- parameters: TOOL_PARAMS.toggle_strategy,
2419
- execute: async (_id, params) => jsonResult(await toggleStrategyTool(params, intelApiDeps)),
2420
- },
2421
- {
2422
- name: 'scan_pairs',
2423
- label: 'Scan Pairs',
2424
- description: 'Scan all monitored pairs for strategy setups. Evaluates your active strategies against pre-computed market facts (indicators + conditions) for each symbol. Returns ranked setups by confluence score. Use this FIRST in Step 3 (SCAN) instead of calling per-pair tools individually — covers the whole universe in one compact call (~300 tokens) instead of thousands of tokens drilling in per pair. Follow up with get_setup_detail on the top-ranked pair. Setups whose (setup_type, regime) match one of your confirmed entry-time learnings appear under `vetoed_setups` instead of `rankings` — each carries the matching learning\'s directive inline so you can apply override conditions if the directive allows. Don\'t waste a drill-in on a vetoed setup unless the override conditions are clearly satisfied.',
2425
- parameters: TOOL_PARAMS.scan_pairs,
2426
- execute: async (_id, params) => jsonResult(await scanPairsTool(params ?? {}, intelApiDeps, {
2427
- decisionsClient: positionDecisionsClient,
2428
- userId: positionDecisionsUserId,
2429
- })),
2430
- },
2431
- {
2432
- name: 'get_setup_detail',
2433
- label: 'Get Setup Detail',
2434
- description: 'Get detailed trade setup for a specific symbol. Returns entry price, stop level, R-multiple targets, full indicator snapshot, and risk flags. Use AFTER scan_pairs identifies a top-ranked pair — this provides the precision data needed to make a go/no-go decision.',
2435
- parameters: TOOL_PARAMS.get_setup_detail,
2436
- execute: async (_id, params) => jsonResult(await getSetupDetailTool(params, intelApiDeps)),
2437
- },
2438
- // ---- Autonomous Learning Tools ----
2439
- {
2440
- name: 'query_trades',
2441
- label: 'Query Trades',
2442
- description: 'Flexible trade history query. Filter by symbol, setup_type, regime, direction, outcome, duration, P&L range, confluence score, and feature_snapshot values (e.g. ADX > 40). Use group_by for aggregated stats (win_rate, expectancy per group). This is your primary tool for discovering patterns in your own performance — ask any question about your trade history.',
2443
- parameters: TOOL_PARAMS.query_trades,
2444
- execute: async (_id, params) => jsonResult(await queryTradesTool(params ?? {}, intelApiDeps)),
2445
- },
2446
- {
2447
- name: 'get_agent_profile',
2448
- label: 'Get Agent Profile',
2449
- description: 'Returns your earned autonomy tier (novice/apprentice/journeyman/master) and permissions. Tier is based on your trade count, win rate, expectancy, and Sharpe ratio. Higher tiers unlock more latitude to adjust thresholds, create strategies, and override soft rules. Check this at session start.',
2450
- parameters: TOOL_PARAMS.get_agent_profile,
2451
- execute: async () => jsonResult(await getAgentProfileTool(intelApiDeps)),
2452
- },
2453
- // ---- Market Breadth & CVD Tools ----
2454
- {
2455
- name: 'get_cvd',
2456
- label: 'Get CVD Analysis',
2457
- description: 'Get Cumulative Volume Delta (CVD) analysis — buy/sell pressure, slope (5m and 1h), price-CVD divergence detection, z-score exhaustion, and accumulation state. CVD rises when buyers are aggressive (lifting asks), falls when sellers hit bids. Divergence between price and CVD is a leading reversal signal. Use alongside get_trade_flow for complete order flow picture.',
2458
- parameters: TOOL_PARAMS.get_cvd,
2459
- execute: async (_id, params) => jsonResult(await getCvdTool(params, intelApiDeps)),
2460
- },
2461
- {
2462
- name: 'get_market_breadth',
2463
- label: 'Get Market Breadth',
2464
- description: 'Cross-pair correlation and market breadth across all monitored symbols. Returns: per-pair momentum (1h/4h/24h returns), advance/decline ratios, BTC correlation per pair, strongest/weakest performers, and whether alts are confirming BTC\'s move. Use BEFORE entering any trade to check if the broader market supports your direction. If BTC dumps but alts hold = weak dump. If everything dumps together = conviction selloff.',
2465
- parameters: TOOL_PARAMS.get_market_breadth,
2466
- execute: async (_id, params) => jsonResult(await getMarketBreadthTool(params ?? {}, intelApiDeps)),
2467
- },
2468
- {
2469
- name: 'get_basis',
2470
- label: 'Get Spot-Futures Basis',
2471
- description: 'Spot-futures basis (mark-spot premium) analysis. Returns current premium/discount in bps, funding rate alignment, annualized carry, historical basis over lookback window, and dislocation detection. Positive basis = futures premium (longs paying). Negative = discount (shorts paying). When basis and funding diverge = dislocation signal. Extreme basis (>2 std dev) = mean reversion likely. Use alongside get_crypto_metrics for complete derivatives picture.',
2472
- parameters: TOOL_PARAMS.get_basis,
2473
- execute: async (_id, params) => jsonResult(await getBasisTool(params, intelApiDeps)),
2474
- },
2475
- {
2476
- name: 'get_cascade_risk',
2477
- label: 'Get Cascade Risk',
2478
- description: 'Liquidation cascade risk assessment. Returns: nearest liquidation clusters above/below current price, cascade zones with estimated price impact if triggered, recent liquidation activity (1h/4h), active cascade detection, and overall risk score (0-100). A cascade happens when liquidations push price into more liquidation levels, creating a chain reaction. Use BEFORE entering trades to avoid shorting into a short-squeeze zone or longing above a long-liquidation cliff. High risk score = stay flat or trade the opposite direction.',
2479
- parameters: TOOL_PARAMS.get_cascade_risk,
2480
- execute: async (_id, params) => jsonResult(await getCascadeRiskTool(params, intelApiDeps)),
2481
- },
2482
- {
2483
- name: 'get_sentiment',
2484
- label: 'Get Market Sentiment',
2485
- description: 'Free market sentiment data — no Pro required. Returns: Fear & Greed Index (0-100 with contrarian signal), BTC dominance %, total crypto market cap, 24h volume, and market cap change %. Fear <=25 = contrarian buy, Greed >=75 = contrarian sell. BTC dominance >60% = risk-off/alt weakness, <45% = alt season. Use at session start and before major decisions for macro context.',
2486
- parameters: TOOL_PARAMS.get_sentiment,
2487
- execute: async () => jsonResult(await getSentimentTool({}, intelApiDeps)),
2488
- },
2489
- {
2490
- name: 'get_divergences',
2491
- label: 'Get Divergence Scan',
2492
- description: 'Multi-timeframe RSI & MACD divergence scanner across 15m/1h/4h/1d. Detects when price makes new highs/lows but RSI or MACD histogram does not confirm — a leading reversal signal. Returns: all detected divergences with strength scores, per-timeframe indicator summary, multi-TF confirmation flag, and consensus direction. Bullish divergence (price lower low + RSI higher low) = selling exhaustion. Bearish divergence (price higher high + RSI lower high) = buying exhaustion. Multi-TF confirmed divergences are highest conviction.',
2493
- parameters: TOOL_PARAMS.get_divergences,
2494
- execute: async (_id, params) => jsonResult(await getDivergencesTool(params, intelApiDeps)),
2495
- },
2496
- {
2497
- name: 'get_liquidation_pulse',
2498
- label: 'Get Liquidation Pulse',
2499
- description: 'Sub-second cross-market liquidation pulse from Binance `!forceOrder@arr`. Returns a classification (quiet | elevated | active_cascade) over a rolling 60s window (configurable 5-300s), USD notional liquidated long-side vs short-side, dominant side (long/short/balanced), event count, and the 5 largest events. With no `symbol` argument, returns the cross-majors aggregate (BTC+ETH+BNB+SOL+XRP) — useful for detecting market-wide stress that leads correlated alt moves. With `symbol`, returns that symbol\'s pulse. ACTIVE_CASCADE = sustained liquidation chain in progress; pause new entries on the dominant side. Returns `{enabled: false}` when the operator has not enabled this feature.',
2500
- parameters: TOOL_PARAMS.get_liquidation_pulse,
2501
- execute: async (_id, params) => jsonResult(await getLiquidationPulseTool(params, intelApiDeps)),
2502
- },
2503
- {
2504
- name: 'get_resting_liquidity',
2505
- label: 'Get Resting Liquidity',
2506
- description: 'Banded resting limit-order liquidity for a symbol. Returns USD notional resting on bid + ask sides at four cumulative bands (0.2 / 0.5 / 1.0 / 1.5 % from mid) plus `coverage_pct` (the % range depth-20 actually reaches — < 1.5 means depth-20 is too shallow on this symbol). Use to size entries against where the book actually sits: a thin book on your side = expect slippage; a thick book on the opposite side = potential resistance/support level. Returns `{enabled: false}` when the operator has not enabled this feature.',
2507
- parameters: TOOL_PARAMS.get_resting_liquidity,
2508
- execute: async (_id, params) => jsonResult(await getRestingLiquidityTool(params, intelApiDeps)),
2509
- },
2510
- {
2511
- name: 'get_funding_context',
2512
- label: 'Get Funding Context',
2513
- description: '30-day funding-rate percentile context for a symbol. Returns the latest funding rate, the p10 / p50 / p90 of the last 30 days, and `percentileRank` 0..1 — where the current rate sits in that distribution. Use to gauge crowded positioning: rank > 0.90 = funding in top decile (crowded longs paying shorts; new LONG entries face top-tick risk), rank < 0.10 = bottom decile (crowded shorts paying longs; new SHORT entries face squeeze risk). Per-symbol adaptive: BTC/BNB tight ranges and DOT/WIF heavy tails both get sensible cutoffs. Returns `{enabled: false}` when the operator has not enabled this feature or the symbol has < 100 30d samples.',
2514
- parameters: TOOL_PARAMS.get_funding_context,
2515
- execute: async (_id, params) => jsonResult(await getFundingContextTool(params, intelApiDeps)),
2516
- },
2517
- // ---- Operator-only onboarding tools ----
2518
- // The skill forwards these from the dashboard's Settings page. The
2519
- // agent should NOT call them — PR2 enforces that via an operator-only
2520
- // scope gate in the skill layer. These are intentionally excluded from
2521
- // SKILL.md so the LLM never sees them in its tool list.
2522
- {
2523
- name: 'set_exchange_credentials',
2524
- label: 'Set Exchange Credentials',
2525
- description: 'Operator-only. Store Binance API key + secret in ~/.reefclaw/plugin-config.json. If current mode is live, reconnects the adapter. Never called by the agent.',
2526
- parameters: TOOL_PARAMS.set_exchange_credentials,
2527
- execute: async (_id, params) => jsonResult(await setExchangeCredentialsTool(params, { runtime, adapterDeps })),
2528
- },
2529
- {
2530
- name: 'set_trading_mode',
2531
- label: 'Set Trading Mode',
2532
- description: 'Operator-only. Move the plugin between PAPER / MICRO_LIVE / LIVE. Enforces the one-rung-at-a-time ladder. Never called by the agent.',
2533
- parameters: TOOL_PARAMS.set_trading_mode,
2534
- execute: async (_id, params) => jsonResult(await setTradingModeTool(params, { runtime, adapterDeps })),
2535
- },
2536
- {
2537
- name: 'test_exchange_credentials',
2538
- label: 'Test Exchange Credentials',
2539
- description: 'Operator-only. Verify a Binance API key + secret with a read-only call (fetchBalance) without persisting anything. Used by the dashboard pre-flight check before set_exchange_credentials. Never called by the agent.',
2540
- parameters: TOOL_PARAMS.test_exchange_credentials,
2541
- execute: async (_id, params) => jsonResult(await testExchangeCredentialsTool(params)),
2542
- },
2543
- {
2544
- name: 'clear_exchange_credentials',
2545
- label: 'Clear Exchange Credentials',
2546
- description: 'Operator-only. Remove stored Binance credentials from plugin-config.json and de-escalate to PAPER mode if currently running in MICRO_LIVE/LIVE. Requires {confirm: true}. Never called by the agent.',
2547
- parameters: TOOL_PARAMS.clear_exchange_credentials,
2548
- execute: async (_id, params) => jsonResult(await clearExchangeCredentialsTool(params, { runtime, adapterDeps })),
2549
- },
2550
- {
2551
- name: 'get_bracket_config',
2552
- label: 'Get Bracket Config',
2553
- description: 'Operator-only. Returns the current bracket-orders configuration (mode + requireStopLoss + requireTakeProfit). Used by the dashboard Trading Parameters panel.',
2554
- parameters: TOOL_PARAMS.get_bracket_config,
2555
- execute: async () => jsonResult(getBracketConfigTool({})),
2556
- },
2557
- {
2558
- name: 'set_bracket_requirement',
2559
- label: 'Set Bracket Requirement',
2560
- description: 'Operator-only. Flip requireStopLoss or requireTakeProfit in plugin-config. Returns a warning copy when a flag is disabled so the dashboard can render the confirmation banner. Never called by the agent.',
2561
- parameters: TOOL_PARAMS.set_bracket_requirement,
2562
- execute: async (_id, params) => jsonResult(setBracketRequirementTool(params)),
2563
- },
2564
- ];
2565
- // ---- Tool gate (central per-user config, slice 1) ----
2566
- // Wrap EVERY tool's execute with the ToolGate check — the one dispatch
2567
- // choke point. The wrapper reads the gate's live state per call, so a
2568
- // config change hot-applies on the next invocation (no re-registration,
2569
- // no restart). Safety floor: UNGOVERNABLE_TOOLS (core + operator names)
2570
- // is hardcoded in tool-gate.ts and checked before the disabled set — a
2571
- // config blob can never strip close_position/attach_brackets/etc.
2572
- // Kill-switch: RC_TOOL_GATE=off. See docs/TOOL_DISTRIBUTION_ARCHITECTURE.md §5f.
2573
- const toolGate = new ToolGate();
2574
- // Entitlement gate (account-level enforcement): wraps OUTSIDE the
2575
- // ToolGate so an expired subscription's message wins over an operator
2576
- // OFF-list message. It touches ONLY ENTITLEMENT_GATED_TOOLS (exactly
2577
- // create_order — new risk); closes/stops/brackets/emergency are not in
2578
- // the set by construction. No server verdict → fail-open. Kill-switch:
2579
- // RC_ENTITLEMENT_GATE=off. See config/entitlement-gate.ts.
2580
- const gatedTools = tools.map((t) => entitlementGate.wrapTool(toolGate.wrapTool(t, jsonResult), jsonResult));
2581
- // Poll the central config (boot + every ~60s), keyed by the same rc_
2582
- // token the ingest paths use (plugin-config connectionToken →
2583
- // WEBAPP_INGEST_TOKEN env fallback). No token → poller no-ops → gate
2584
- // stays empty → all tools enabled (byte-identical to pre-feature).
2585
- // gateStore carries centrally-delivered operational gates (slice 2:
2586
- // exitGate) — loadExitGateMode() consults it before the local file;
2587
- // kill-switch RC_CENTRAL_GATES=off. See config/gate-store.ts.
2588
- // entitlementGate receives the server-resolved billing verdict.
2589
- startAgentConfigPoller({
2590
- gate: toolGate,
2591
- gateStore,
2592
- entitlementGate,
2593
- apiBaseUrl,
2594
- token: resolveIngestToken({ connectionToken }),
2595
- });
2596
- // Register all tools with OpenClaw
2597
- const toolNames = gatedTools.map(t => t.name);
2598
- // OpenClaw 2026.6+ SILENTLY DROPS any registered tool not declared in
2599
- // openclaw.plugin.json `contracts.tools` (diagnostic-only error, tools
2600
- // just vanish from the agent). The manifest ships next to index.js in the
2601
- // installed layout — cross-check at register time so drift is loud in the
2602
- // journal instead of surfacing as "agent can't trade". Fail-soft: some
2603
- // deploy layouts don't ship the manifest (prod pre-dates it) — skip then.
2604
- try {
2605
- const manifestPath = join(dirname(fileURLToPath(import.meta.url)), 'openclaw.plugin.json');
2606
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
2607
- const declared = new Set(manifest.contracts?.tools ?? []);
2608
- if (declared.size > 0) {
2609
- const undeclared = toolNames.filter((n) => !declared.has(n));
2610
- if (undeclared.length > 0) {
2611
- logger.error(TAG, `openclaw.plugin.json contracts.tools is missing ${undeclared.length} registered tool(s) — OpenClaw 2026.6+ will silently drop them: ${undeclared.join(', ')}. Add them to plugin/openclaw.plugin.json.`);
2612
- }
2613
- }
2614
- }
2615
- catch {
2616
- // Manifest not shipped alongside dist (legacy deploy layout) — skip.
2617
- }
2618
- const toolsFactory = () => gatedTools;
2619
- api.registerTool(toolsFactory, { names: toolNames });
2620
- // Cache factory + names on the module singleton so repeat register()
2621
- // calls can re-register without spinning up duplicate adapters (the
2622
- // factory closes over the already-wrapped tools — idempotent).
2623
- pluginToolsFactory = toolsFactory;
2624
- pluginToolNames = toolNames;
2625
- pluginInitialised = true;
2626
- logger.info(TAG, `Registered ${gatedTools.length} tools (gate mode=${toolGate.getMode()}): ${toolNames.join(', ')}. Plugin v3.8.0 (${runtime.mode} mode)`);
2627
- // Agent-readiness reporter (docs/AGENT_READINESS_GATE_PLAN.md Phase 1):
2628
- // probe host→venue reachability (Binance HTTP 451 geo-block / Hyperliquid
2629
- // /info) + clock drift on the host and POST a plain-English report to the
2630
- // webapp, so a silently-broken agent shows an actionable dashboard alert
2631
- // instead of a false green. Probes ONLY the configured venue — on a
2632
- // Binance-451 host trading Hyperliquid a Binance probe would be a
2633
- // permanent false alarm. Advisory + fire-and-forget; no token → no-op.
2634
- // Runs here once (guarded by the pluginInitialised early-return → once per
2635
- // process) + on an unref'd interval inside the reporter.
2636
- startReadinessReporter({
2637
- apiBaseUrl,
2638
- token: resolveIngestToken({ connectionToken }),
2639
- venue,
2640
- publicApi: hlPublicApi ?? binanceApi,
2641
- toolCount: toolNames.length,
2642
- });
2643
- maybeStartConnectorSupervisor();
2644
- },
2645
- };
2646
- /** Plugin-supervised connector (frictionless onboarding): when plugin-config
2647
- * says connectorSupervisor='on' (written by the npx installer on fresh
2648
- * installs — NEVER defaulted on, prod runs the bridge under systemd and
2649
- * would double-connect the relay room), the plugin spawns + restarts the
2650
- * relay connector as a child of the gateway process. OpenClaw is the process
2651
- * manager: no systemd, works in containers, dies with the gateway.
2652
- * Kill-switch: RC_CONNECTOR_SUPERVISOR=off. Called from EVERY register()
2653
- * path (config may appear between calls); the supervisor itself is a
2654
- * singleton so repeat calls no-op. */
2655
- function maybeStartConnectorSupervisor() {
2656
- try {
2657
- const supervisorMode = readPluginConfig().connectorSupervisor;
2658
- if (process.env.RC_CONNECTOR_SUPERVISOR === 'off') {
2659
- if (supervisorMode === 'on')
2660
- logger.warn(TAG, 'connector supervisor disabled by RC_CONNECTOR_SUPERVISOR=off');
2661
- return;
2662
- }
2663
- if (supervisorMode === 'off')
2664
- return;
2665
- // 'on' → explicit opt-in (the npx installer writes this).
2666
- // undefined → auto-start ONLY when the connector is bundled inside this
2667
- // plugin package (the npm-channel @reefclaw/openclaw-plugin distribution
2668
- // is self-contained by design). Prod's linked plugin dir has no bundled
2669
- // bridge and no flag → stays off; systemd keeps owning prod's bridge.
2670
- if (supervisorMode === 'on' || hasBundledBridge()) {
2671
- startConnectorSupervisor();
2672
- }
2673
- }
2674
- catch (err) {
2675
- logger.warn(TAG, `connector supervisor init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
2676
- }
2677
- }
2678
- export default paperTradingPlugin;