@reefclaw/openclaw-plugin 0.1.0

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 (371) hide show
  1. package/.gitignore +8 -0
  2. package/audit/mode-transition-audit.d.ts +11 -0
  3. package/audit/mode-transition-audit.js +29 -0
  4. package/balance-utils.d.ts +36 -0
  5. package/balance-utils.js +98 -0
  6. package/bridge/bridge.d.ts +109 -0
  7. package/bridge/bridge.js +1036 -0
  8. package/bridge/config.d.ts +43 -0
  9. package/bridge/config.js +176 -0
  10. package/bridge/connector.d.ts +52 -0
  11. package/bridge/connector.js +293 -0
  12. package/bridge/event-replay-buffer.d.ts +43 -0
  13. package/bridge/event-replay-buffer.js +109 -0
  14. package/bridge/gateway/event-parser.d.ts +209 -0
  15. package/bridge/gateway/event-parser.js +793 -0
  16. package/bridge/gateway/gateway-config.d.ts +39 -0
  17. package/bridge/gateway/gateway-config.js +86 -0
  18. package/bridge/gateway/gateway-http-client.d.ts +50 -0
  19. package/bridge/gateway/gateway-http-client.js +165 -0
  20. package/bridge/gateway/gateway-ws-client.d.ts +116 -0
  21. package/bridge/gateway/gateway-ws-client.js +417 -0
  22. package/bridge/gateway/poller.d.ts +146 -0
  23. package/bridge/gateway/poller.js +505 -0
  24. package/bridge/gateway/tool-discovery.d.ts +25 -0
  25. package/bridge/gateway/tool-discovery.js +198 -0
  26. package/bridge/index.d.ts +2 -0
  27. package/bridge/index.js +253 -0
  28. package/bridge/logger.d.ts +2 -0
  29. package/bridge/logger.js +2 -0
  30. package/bridge/provider.d.ts +156 -0
  31. package/bridge/provider.js +2 -0
  32. package/bridge/providers/emergency-commands.d.ts +54 -0
  33. package/bridge/providers/emergency-commands.js +235 -0
  34. package/bridge/providers/gateway.d.ts +322 -0
  35. package/bridge/providers/gateway.js +2302 -0
  36. package/bridge/providers/mock.d.ts +37 -0
  37. package/bridge/providers/mock.js +385 -0
  38. package/bridge/providers/onboarding-commands.d.ts +83 -0
  39. package/bridge/providers/onboarding-commands.js +213 -0
  40. package/bridge/providers/risk-calculator.d.ts +96 -0
  41. package/bridge/providers/risk-calculator.js +369 -0
  42. package/bridge/setup.d.ts +32 -0
  43. package/bridge/setup.js +226 -0
  44. package/bridge/types.d.ts +584 -0
  45. package/bridge/types.js +50 -0
  46. package/bridge/utils/reconnect.d.ts +6 -0
  47. package/bridge/utils/reconnect.js +6 -0
  48. package/bridge/utils/skill-signing.d.ts +51 -0
  49. package/bridge/utils/skill-signing.js +138 -0
  50. package/bridge/utils/skill-version.d.ts +17 -0
  51. package/bridge/utils/skill-version.js +74 -0
  52. package/ccxt/binance-ban-gate.d.ts +47 -0
  53. package/ccxt/binance-ban-gate.js +409 -0
  54. package/ccxt/binance-private.d.ts +325 -0
  55. package/ccxt/binance-private.js +1415 -0
  56. package/ccxt/binance-public.d.ts +18 -0
  57. package/ccxt/binance-public.js +147 -0
  58. package/config/agent-config-client.d.ts +55 -0
  59. package/config/agent-config-client.js +145 -0
  60. package/config/agent-config-poller.d.ts +25 -0
  61. package/config/agent-config-poller.js +100 -0
  62. package/config/brackets-config.d.ts +22 -0
  63. package/config/brackets-config.js +58 -0
  64. package/config/gate-store.d.ts +18 -0
  65. package/config/gate-store.js +61 -0
  66. package/config/plugin-config-io.d.ts +181 -0
  67. package/config/plugin-config-io.js +84 -0
  68. package/config/position-review-config.d.ts +35 -0
  69. package/config/position-review-config.js +105 -0
  70. package/config/tool-gate.d.ts +53 -0
  71. package/config/tool-gate.js +125 -0
  72. package/config/user-data-stream-config.d.ts +85 -0
  73. package/config/user-data-stream-config.js +224 -0
  74. package/connector-supervisor.d.ts +36 -0
  75. package/connector-supervisor.js +149 -0
  76. package/exchange-adapter.d.ts +49 -0
  77. package/exchange-adapter.js +4 -0
  78. package/index.d.ts +30 -0
  79. package/index.js +2030 -0
  80. package/ingest/pending-entry-metadata.d.ts +52 -0
  81. package/ingest/pending-entry-metadata.js +182 -0
  82. package/ingest/position-auto-capture.d.ts +98 -0
  83. package/ingest/position-auto-capture.js +394 -0
  84. package/ingest/position-decisions-client.d.ts +318 -0
  85. package/ingest/position-decisions-client.js +296 -0
  86. package/ingest/reconcile-db-vs-exchange.d.ts +13 -0
  87. package/ingest/reconcile-db-vs-exchange.js +114 -0
  88. package/ingest/reconciler-cleanup.d.ts +37 -0
  89. package/ingest/reconciler-cleanup.js +147 -0
  90. package/ingest/rest-gap-filler.d.ts +191 -0
  91. package/ingest/rest-gap-filler.js +565 -0
  92. package/ingest/touched-symbols-store.d.ts +25 -0
  93. package/ingest/touched-symbols-store.js +96 -0
  94. package/ingest/trade-store-client.d.ts +40 -0
  95. package/ingest/trade-store-client.js +125 -0
  96. package/ingest/ws-ingest.d.ts +43 -0
  97. package/ingest/ws-ingest.js +126 -0
  98. package/learning/setup-family.d.ts +21 -0
  99. package/learning/setup-family.js +103 -0
  100. package/lifecycle/install-signal-handlers.d.ts +33 -0
  101. package/lifecycle/install-signal-handlers.js +112 -0
  102. package/lifecycle/shutdown-coordinator.d.ts +43 -0
  103. package/lifecycle/shutdown-coordinator.js +131 -0
  104. package/live/bracket-id.d.ts +18 -0
  105. package/live/bracket-id.js +81 -0
  106. package/live/bracket-ledger.d.ts +54 -0
  107. package/live/bracket-ledger.js +267 -0
  108. package/live/bracket-manager.d.ts +82 -0
  109. package/live/bracket-manager.js +478 -0
  110. package/live/bracket-params.d.ts +22 -0
  111. package/live/bracket-params.js +124 -0
  112. package/live/bracket-reconciler.d.ts +95 -0
  113. package/live/bracket-reconciler.js +573 -0
  114. package/live/bracket-types.d.ts +102 -0
  115. package/live/bracket-types.js +8 -0
  116. package/live/deposit-tracker.d.ts +62 -0
  117. package/live/deposit-tracker.js +97 -0
  118. package/live/emergency-controls.d.ts +32 -0
  119. package/live/emergency-controls.js +226 -0
  120. package/live/exchange-errors.d.ts +12 -0
  121. package/live/exchange-errors.js +130 -0
  122. package/live/exchange-info-cache.d.ts +35 -0
  123. package/live/exchange-info-cache.js +119 -0
  124. package/live/fact-subscriber.d.ts +78 -0
  125. package/live/fact-subscriber.js +182 -0
  126. package/live/intent-journal.d.ts +42 -0
  127. package/live/intent-journal.js +122 -0
  128. package/live/listen-key-manager.d.ts +70 -0
  129. package/live/listen-key-manager.js +169 -0
  130. package/live/live-adapter.d.ts +264 -0
  131. package/live/live-adapter.js +1665 -0
  132. package/live/live-balance-enricher.d.ts +32 -0
  133. package/live/live-balance-enricher.js +104 -0
  134. package/live/live-bracket-api.d.ts +13 -0
  135. package/live/live-bracket-api.js +20 -0
  136. package/live/live-state-store.d.ts +194 -0
  137. package/live/live-state-store.js +450 -0
  138. package/live/local-signal-service.d.ts +57 -0
  139. package/live/local-signal-service.js +146 -0
  140. package/live/local-strategy-evaluator.d.ts +62 -0
  141. package/live/local-strategy-evaluator.js +127 -0
  142. package/live/microstructure-assembler.d.ts +54 -0
  143. package/live/microstructure-assembler.js +148 -0
  144. package/live/order-poller.d.ts +29 -0
  145. package/live/order-poller.js +125 -0
  146. package/live/position-state-store.d.ts +83 -0
  147. package/live/position-state-store.js +237 -0
  148. package/live/proposal-decision-listener.d.ts +64 -0
  149. package/live/proposal-decision-listener.js +288 -0
  150. package/live/proposal-manager.d.ts +76 -0
  151. package/live/proposal-manager.js +140 -0
  152. package/live/rate-limiter.d.ts +47 -0
  153. package/live/rate-limiter.js +159 -0
  154. package/live/reconciler.d.ts +39 -0
  155. package/live/reconciler.js +175 -0
  156. package/live/setup-buckets.d.ts +7 -0
  157. package/live/setup-buckets.js +33 -0
  158. package/live/slippage-tracker.d.ts +45 -0
  159. package/live/slippage-tracker.js +78 -0
  160. package/live/stop-watcher.d.ts +34 -0
  161. package/live/stop-watcher.js +158 -0
  162. package/live/user-data-active-probe.d.ts +54 -0
  163. package/live/user-data-active-probe.js +180 -0
  164. package/live/user-data-stream-controller.d.ts +200 -0
  165. package/live/user-data-stream-controller.js +579 -0
  166. package/live/user-data-stream-ws.d.ts +22 -0
  167. package/live/user-data-stream-ws.js +63 -0
  168. package/live/user-data-stream.d.ts +243 -0
  169. package/live/user-data-stream.js +704 -0
  170. package/logger.d.ts +2 -0
  171. package/logger.js +2 -0
  172. package/mfe.d.ts +21 -0
  173. package/mfe.js +68 -0
  174. package/onboarding/mode-ladder.d.ts +1 -0
  175. package/onboarding/mode-ladder.js +3 -0
  176. package/onboarding/runtime.d.ts +71 -0
  177. package/onboarding/runtime.js +153 -0
  178. package/openclaw.plugin.json +94 -0
  179. package/package.json +27 -0
  180. package/paper-adapter.d.ts +24 -0
  181. package/paper-adapter.js +91 -0
  182. package/persistence/state-manager.d.ts +42 -0
  183. package/persistence/state-manager.js +164 -0
  184. package/pinned-plan.d.ts +9 -0
  185. package/pinned-plan.js +23 -0
  186. package/risk/pre-trade-check.d.ts +38 -0
  187. package/risk/pre-trade-check.js +345 -0
  188. package/risk/pre-trade-types.d.ts +60 -0
  189. package/risk/pre-trade-types.js +3 -0
  190. package/scripts/assemble.mjs +114 -0
  191. package/shadow/shadow-tracker.d.ts +36 -0
  192. package/shadow/shadow-tracker.js +151 -0
  193. package/shadow/types.d.ts +42 -0
  194. package/shadow/types.js +20 -0
  195. package/shared/indicators-extended.d.ts +52 -0
  196. package/shared/indicators-extended.js +291 -0
  197. package/shared/indicators.d.ts +15 -0
  198. package/shared/indicators.js +114 -0
  199. package/signals/conditions/registry.d.ts +16 -0
  200. package/signals/conditions/registry.js +1274 -0
  201. package/signals/conditions/types.d.ts +1 -0
  202. package/signals/conditions/types.js +4 -0
  203. package/signals/direction-rules.d.ts +3 -0
  204. package/signals/direction-rules.js +24 -0
  205. package/signals/entry-rules.d.ts +6 -0
  206. package/signals/entry-rules.js +33 -0
  207. package/signals/serialize-context.d.ts +4 -0
  208. package/signals/serialize-context.js +39 -0
  209. package/signals/stop-rules.d.ts +3 -0
  210. package/signals/stop-rules.js +48 -0
  211. package/signals/strategy-adapter.d.ts +14 -0
  212. package/signals/strategy-adapter.js +122 -0
  213. package/signals/types.d.ts +1 -0
  214. package/signals/types.js +8 -0
  215. package/simulator/exchange-simulator.d.ts +93 -0
  216. package/simulator/exchange-simulator.js +684 -0
  217. package/simulator/fill-engine.d.ts +53 -0
  218. package/simulator/fill-engine.js +276 -0
  219. package/simulator/paper-market-feed.d.ts +26 -0
  220. package/simulator/paper-market-feed.js +104 -0
  221. package/simulator/realistic-fills.d.ts +59 -0
  222. package/simulator/realistic-fills.js +175 -0
  223. package/simulator/types.d.ts +219 -0
  224. package/simulator/types.js +43 -0
  225. package/skills/reefclaw/SKILL.md +97 -0
  226. package/strategy/builtin-strategies.d.ts +2 -0
  227. package/strategy/builtin-strategies.js +109 -0
  228. package/strategy/condition-registry.d.ts +3 -0
  229. package/strategy/condition-registry.js +153 -0
  230. package/strategy/evaluator.d.ts +67 -0
  231. package/strategy/evaluator.js +93 -0
  232. package/tools/assessment-validation.d.ts +118 -0
  233. package/tools/assessment-validation.js +415 -0
  234. package/tools/attach-brackets.d.ts +34 -0
  235. package/tools/attach-brackets.js +363 -0
  236. package/tools/audit-bracket-protection.d.ts +49 -0
  237. package/tools/audit-bracket-protection.js +527 -0
  238. package/tools/cancel-all-orders.d.ts +7 -0
  239. package/tools/cancel-all-orders.js +5 -0
  240. package/tools/cancel-order.d.ts +10 -0
  241. package/tools/cancel-order.js +14 -0
  242. package/tools/check-position-health.d.ts +46 -0
  243. package/tools/check-position-health.js +194 -0
  244. package/tools/clear-exchange-credentials.d.ts +24 -0
  245. package/tools/clear-exchange-credentials.js +70 -0
  246. package/tools/close-position.d.ts +22 -0
  247. package/tools/close-position.js +449 -0
  248. package/tools/create-order.d.ts +54 -0
  249. package/tools/create-order.js +338 -0
  250. package/tools/exit-gate.d.ts +58 -0
  251. package/tools/exit-gate.js +162 -0
  252. package/tools/fetch-balance.d.ts +5 -0
  253. package/tools/fetch-balance.js +4 -0
  254. package/tools/fetch-ohlcv.d.ts +11 -0
  255. package/tools/fetch-ohlcv.js +8 -0
  256. package/tools/fetch-open-orders.d.ts +7 -0
  257. package/tools/fetch-open-orders.js +4 -0
  258. package/tools/fetch-positions.d.ts +7 -0
  259. package/tools/fetch-positions.js +4 -0
  260. package/tools/fetch-ticker.d.ts +11 -0
  261. package/tools/fetch-ticker.js +5 -0
  262. package/tools/get-agent-profile.d.ts +4 -0
  263. package/tools/get-agent-profile.js +6 -0
  264. package/tools/get-analytics.d.ts +6 -0
  265. package/tools/get-analytics.js +7 -0
  266. package/tools/get-backtest.d.ts +12 -0
  267. package/tools/get-backtest.js +91 -0
  268. package/tools/get-basis.d.ts +7 -0
  269. package/tools/get-basis.js +7 -0
  270. package/tools/get-bracket-config.d.ts +11 -0
  271. package/tools/get-bracket-config.js +24 -0
  272. package/tools/get-cascade-risk.d.ts +7 -0
  273. package/tools/get-cascade-risk.js +8 -0
  274. package/tools/get-crypto-metrics.d.ts +18 -0
  275. package/tools/get-crypto-metrics.js +45 -0
  276. package/tools/get-cvd.d.ts +6 -0
  277. package/tools/get-cvd.js +6 -0
  278. package/tools/get-divergences.d.ts +6 -0
  279. package/tools/get-divergences.js +6 -0
  280. package/tools/get-funding-context.d.ts +6 -0
  281. package/tools/get-funding-context.js +16 -0
  282. package/tools/get-liquidation-levels.d.ts +7 -0
  283. package/tools/get-liquidation-levels.js +7 -0
  284. package/tools/get-liquidation-pulse.d.ts +9 -0
  285. package/tools/get-liquidation-pulse.js +22 -0
  286. package/tools/get-market-breadth.d.ts +6 -0
  287. package/tools/get-market-breadth.js +8 -0
  288. package/tools/get-market-intel.d.ts +19 -0
  289. package/tools/get-market-intel.js +116 -0
  290. package/tools/get-market-structure.d.ts +47 -0
  291. package/tools/get-market-structure.js +198 -0
  292. package/tools/get-my-mined-patterns.d.ts +20 -0
  293. package/tools/get-my-mined-patterns.js +61 -0
  294. package/tools/get-my-proposed-learnings.d.ts +20 -0
  295. package/tools/get-my-proposed-learnings.js +55 -0
  296. package/tools/get-my-recent-reviews.d.ts +22 -0
  297. package/tools/get-my-recent-reviews.js +66 -0
  298. package/tools/get-orderbook.d.ts +21 -0
  299. package/tools/get-orderbook.js +32 -0
  300. package/tools/get-pattern-scan.d.ts +7 -0
  301. package/tools/get-pattern-scan.js +8 -0
  302. package/tools/get-regime.d.ts +6 -0
  303. package/tools/get-regime.js +7 -0
  304. package/tools/get-relevant-learnings.d.ts +21 -0
  305. package/tools/get-relevant-learnings.js +65 -0
  306. package/tools/get-resting-liquidity.d.ts +6 -0
  307. package/tools/get-resting-liquidity.js +11 -0
  308. package/tools/get-risk-scenario.d.ts +29 -0
  309. package/tools/get-risk-scenario.js +47 -0
  310. package/tools/get-risk-summary.d.ts +51 -0
  311. package/tools/get-risk-summary.js +118 -0
  312. package/tools/get-sentiment.d.ts +4 -0
  313. package/tools/get-sentiment.js +6 -0
  314. package/tools/get-session-review.d.ts +7 -0
  315. package/tools/get-session-review.js +8 -0
  316. package/tools/get-setup-detail.d.ts +7 -0
  317. package/tools/get-setup-detail.js +303 -0
  318. package/tools/get-signals.d.ts +15 -0
  319. package/tools/get-signals.js +54 -0
  320. package/tools/get-sizing.d.ts +6 -0
  321. package/tools/get-sizing.js +6 -0
  322. package/tools/get-trade-feedback.d.ts +7 -0
  323. package/tools/get-trade-feedback.js +8 -0
  324. package/tools/get-trade-flow.d.ts +7 -0
  325. package/tools/get-trade-flow.js +7 -0
  326. package/tools/get-volume-analysis.d.ts +21 -0
  327. package/tools/get-volume-analysis.js +74 -0
  328. package/tools/get-volume-profile.d.ts +7 -0
  329. package/tools/get-volume-profile.js +7 -0
  330. package/tools/helpers.d.ts +25 -0
  331. package/tools/helpers.js +38 -0
  332. package/tools/intel-api.d.ts +14 -0
  333. package/tools/intel-api.js +52 -0
  334. package/tools/intel-cache.d.ts +25 -0
  335. package/tools/intel-cache.js +133 -0
  336. package/tools/list-strategies.d.ts +7 -0
  337. package/tools/list-strategies.js +6 -0
  338. package/tools/modify-stop.d.ts +17 -0
  339. package/tools/modify-stop.js +81 -0
  340. package/tools/modify-target.d.ts +17 -0
  341. package/tools/modify-target.js +71 -0
  342. package/tools/propose-learning.d.ts +22 -0
  343. package/tools/propose-learning.js +65 -0
  344. package/tools/query-review-outcomes.d.ts +30 -0
  345. package/tools/query-review-outcomes.js +64 -0
  346. package/tools/query-trades.d.ts +21 -0
  347. package/tools/query-trades.js +37 -0
  348. package/tools/record-position-reviews.d.ts +38 -0
  349. package/tools/record-position-reviews.js +147 -0
  350. package/tools/save-strategy.d.ts +16 -0
  351. package/tools/save-strategy.js +46 -0
  352. package/tools/scan-pairs.d.ts +18 -0
  353. package/tools/scan-pairs.js +220 -0
  354. package/tools/score-setup.d.ts +31 -0
  355. package/tools/score-setup.js +268 -0
  356. package/tools/set-bracket-requirement.d.ts +18 -0
  357. package/tools/set-bracket-requirement.js +81 -0
  358. package/tools/set-exchange-credentials.d.ts +25 -0
  359. package/tools/set-exchange-credentials.js +80 -0
  360. package/tools/set-trading-mode.d.ts +26 -0
  361. package/tools/set-trading-mode.js +135 -0
  362. package/tools/test-exchange-credentials.d.ts +16 -0
  363. package/tools/test-exchange-credentials.js +100 -0
  364. package/tools/toggle-strategy.d.ts +8 -0
  365. package/tools/toggle-strategy.js +8 -0
  366. package/trading-params-cache.d.ts +26 -0
  367. package/trading-params-cache.js +52 -0
  368. package/types.d.ts +110 -0
  369. package/types.js +7 -0
  370. package/util/plugin-paths.d.ts +3 -0
  371. package/util/plugin-paths.js +15 -0
@@ -0,0 +1,235 @@
1
+ // Stateless emergency command handlers extracted from GatewayProvider.
2
+ // Functions return results — callers apply state changes (agentMode, emitAgentState).
3
+ import { logger } from '../logger.js';
4
+ const TAG = 'emergency';
5
+ /** Distinct human-readable reasons from the rejected entries of an
6
+ * allSettled batch — surfaced in EmergencyResult.details so the dashboard
7
+ * shows WHY a close/cancel failed, not just that it did. Capped so a
8
+ * many-position failure doesn't produce an unreadable wall. */
9
+ function rejectionReasons(results, max = 3) {
10
+ const reasons = results
11
+ .filter((r) => r.status === 'rejected')
12
+ .map((r) => (r.reason instanceof Error ? r.reason.message : String(r.reason)));
13
+ const distinct = [...new Set(reasons)];
14
+ const shown = distinct.slice(0, max).join('; ');
15
+ return distinct.length > max ? `${shown}; …` : shown;
16
+ }
17
+ // ---- Async commands (need HTTP + tools) ----
18
+ /**
19
+ * Kill: stop the agent and cancel WORKING orders, while PRESERVING protective
20
+ * brackets. A protective order (exchange-side STOP_MARKET / TAKE_PROFIT_MARKET)
21
+ * is the only thing guarding an open position once the agent is halted (nothing
22
+ * will re-attach a stop). Kill cancels only the agent's working orders (pending
23
+ * entries / adds), PORTFOLIO-WIDE, and keeps every protective leg in place. To
24
+ * actually exit a position, use Flatten.
25
+ *
26
+ * Bracket preservation is defence-in-depth: the plugin's `cancelAllOrders`
27
+ * already skips ReefClaw-cid brackets, so the old per-symbol kill never stripped
28
+ * a ReefClaw bracket — this skill-side `protective` filter is BROADER (it also
29
+ * preserves externally-placed / non-ReefClaw-cid stops) and portfolio-wide.
30
+ *
31
+ * Caller must set agentMode = 'STOPPED' after this returns, and should pass a
32
+ * FRESH all-symbols order snapshot (see the gateway wrapper).
33
+ */
34
+ export async function executeKill(ctx, openOrders) {
35
+ if (!ctx.http) {
36
+ return { action: 'kill', executed: false, timestamp: new Date().toISOString(), details: 'HTTP client not initialized' };
37
+ }
38
+ // Protective = a stop/TP bracket (reduceOnly OR closePosition OR stop-type;
39
+ // see mapCcxtOrder/isProtectiveOrder). Working = everything else (entries /
40
+ // adds). Preserve protective so an open position is never left naked.
41
+ const working = openOrders.filter((o) => o.protective !== true);
42
+ const preserved = openOrders.length - working.length;
43
+ const preservedNote = preserved > 0 ? `; preserved ${preserved} protective bracket order(s)` : '';
44
+ // executed:true ONLY when the agent halt (the caller sets STOPPED
45
+ // unconditionally after this returns) is accompanied by a clean order
46
+ // sweep. Failed/impossible cancels leave live working orders behind an
47
+ // operator who just hit Kill — that must render as Failed + details on the
48
+ // dashboard, never a clean green.
49
+ let executed = true;
50
+ let details;
51
+ const cancelTool = ctx.toolMap.cancel_order;
52
+ if (cancelTool && working.length > 0) {
53
+ // Primary: cancel each working order individually (portfolio-wide), so the
54
+ // protective brackets are never touched.
55
+ const results = await Promise.allSettled(working.map((order) => ctx.http.invokeEmergency(cancelTool, { id: order.id, symbol: order.symbol })));
56
+ const succeeded = results.filter((r) => r.status === 'fulfilled').length;
57
+ const failed = results.filter((r) => r.status === 'rejected').length;
58
+ if (failed > 0) {
59
+ executed = false;
60
+ details = `Agent halted, but cancelled only ${succeeded}/${working.length} working order(s) (${failed} failed: ${rejectionReasons(results)})${preservedNote}`;
61
+ logger.error(TAG, details);
62
+ }
63
+ else {
64
+ details = `Cancelled ${succeeded}/${working.length} working order(s)${preservedNote}`;
65
+ }
66
+ }
67
+ else if (working.length === 0) {
68
+ details = preserved > 0
69
+ ? `No working orders to cancel${preservedNote}`
70
+ : 'No open orders to cancel';
71
+ }
72
+ else {
73
+ // Degenerate fallback: cancel_order wasn't discovered. cancel_all_orders is
74
+ // per-symbol; the plugin's implementation preserves ReefClaw-cid brackets,
75
+ // but (unlike the skill `protective` filter above) it can't spare an
76
+ // externally-placed / non-ReefClaw-cid stop — so warn.
77
+ const cancelAllTool = ctx.toolMap.cancel_all_orders;
78
+ if (cancelAllTool) {
79
+ const symbols = [...new Set(working.map((o) => o.symbol))];
80
+ const results = await Promise.allSettled(symbols.map((s) => ctx.http.invokeEmergency(cancelAllTool, { symbol: s })));
81
+ const failed = results.filter((r) => r.status === 'rejected').length;
82
+ if (failed > 0) {
83
+ executed = false;
84
+ details = `Agent halted, but cancel_all_orders failed on ${failed}/${symbols.length} symbol(s) (${rejectionReasons(results)}) — some orders may remain live`;
85
+ logger.error(TAG, details);
86
+ }
87
+ else {
88
+ details = `cancel_order unavailable — used cancel_all_orders on ${symbols.length} symbol(s) (plugin preserves ReefClaw-cid brackets; a non-cid/external stop is not spared)`;
89
+ logger.warn(TAG, details);
90
+ }
91
+ }
92
+ else {
93
+ executed = false;
94
+ details = 'No cancel tool available (neither cancel_order nor cancel_all_orders discovered) — agent halted, orders left in place';
95
+ logger.warn(TAG, details);
96
+ }
97
+ }
98
+ return { action: 'kill', executed, timestamp: new Date().toISOString(), details };
99
+ }
100
+ /**
101
+ * Flatten: close all open positions at market.
102
+ * Caller must set agentMode = 'STOPPED' after this returns.
103
+ */
104
+ export async function executeFlatten(ctx, cachedPositions) {
105
+ if (!ctx.http) {
106
+ return { action: 'flatten', executed: false, timestamp: new Date().toISOString(), details: 'HTTP client not initialized' };
107
+ }
108
+ // Try to fetch fresh positions; fall back to cached on failure.
109
+ // Flatten is PORTFOLIO-WIDE — fetch ALL symbols (no symbol filter). Passing
110
+ // ctx.symbol here narrowed the result to a single symbol, so positions on
111
+ // every other symbol were silently skipped and never closed. The cached
112
+ // fallback (this.positions) is already all-symbols.
113
+ let positions = cachedPositions;
114
+ const fetchTool = ctx.toolMap.fetch_positions;
115
+ if (fetchTool) {
116
+ try {
117
+ const result = await ctx.http.invokeEmergency(fetchTool, {});
118
+ if (Array.isArray(result.data)) {
119
+ positions = result.data;
120
+ }
121
+ else {
122
+ // Mirror gateway.ts fetchFreshPositions: a non-array response must
123
+ // fall back to cached — letting it through would make the .filter
124
+ // below throw and abort the entire flatten.
125
+ logger.warn(TAG, `fetch_positions returned non-array (${typeof result.data}) — using cached positions`);
126
+ }
127
+ }
128
+ catch (err) {
129
+ logger.warn(TAG, `Fresh position fetch failed, using cached: ${err instanceof Error ? err.message : String(err)}`);
130
+ }
131
+ }
132
+ // Filter to non-zero positions
133
+ const openPositions = positions.filter((p) => (p.contracts ?? 0) !== 0);
134
+ if (openPositions.length === 0) {
135
+ return { action: 'flatten', executed: true, timestamp: new Date().toISOString(), details: 'No positions to close' };
136
+ }
137
+ // Close each position
138
+ const closeTool = ctx.toolMap.close_position;
139
+ const createTool = ctx.toolMap.create_order;
140
+ const results = await Promise.allSettled(openPositions.map((pos) => {
141
+ if (closeTool) {
142
+ // reason='operator_command' is MANDATORY: close_position's schema gate
143
+ // (validateClosePosition) rejects any close lacking a reason, and only
144
+ // operator_command bypasses the assessment requirement. Without it the
145
+ // operator Flatten is silently rejected at the API boundary and the
146
+ // position never closes. See plugin/src/tools/close-position.ts.
147
+ return ctx.http.invokeEmergency(closeTool, { symbol: pos.symbol, reason: 'operator_command' });
148
+ }
149
+ else if (createTool) {
150
+ // Fallback: opposing market order. reduceOnly is MANDATORY — this
151
+ // order may only shrink the position. Without it, a race (position
152
+ // already closed by a bracket fill / a smaller live quantity than the
153
+ // snapshot) turns the "close" into a NEW opposing position — a flip.
154
+ const side = pos.side === 'long' ? 'sell' : 'buy';
155
+ return ctx.http.invokeEmergency(createTool, {
156
+ symbol: pos.symbol,
157
+ type: 'market',
158
+ side,
159
+ amount: Math.abs(pos.contracts ?? 0),
160
+ reduceOnly: true,
161
+ });
162
+ }
163
+ else {
164
+ return Promise.reject(new Error('No close_position or create_order tool available'));
165
+ }
166
+ }));
167
+ const succeeded = results.filter((r) => r.status === 'fulfilled').length;
168
+ const failed = results.filter((r) => r.status === 'rejected').length;
169
+ // Any un-closed position = NOT executed. "Closed 1/5" rendering as a green
170
+ // "Flattened" would leave the operator believing they're flat while 4 real
171
+ // positions stay live — the dashboard must show Failed + which/why.
172
+ if (succeeded === 0 && openPositions.length > 0) {
173
+ const details = `Failed to close any of ${openPositions.length} position(s): ${rejectionReasons(results)}`;
174
+ logger.error(TAG, details);
175
+ return { action: 'flatten', executed: false, timestamp: new Date().toISOString(), details };
176
+ }
177
+ if (failed > 0) {
178
+ const details = `Closed ${succeeded}/${openPositions.length} positions (${failed} failed: ${rejectionReasons(results)})`;
179
+ logger.error(TAG, details);
180
+ return { action: 'flatten', executed: false, timestamp: new Date().toISOString(), details };
181
+ }
182
+ const details = `Closed ${succeeded}/${openPositions.length} positions`;
183
+ return { action: 'flatten', executed: true, timestamp: new Date().toISOString(), details };
184
+ }
185
+ // ---- Sync commands (pure state transitions) ----
186
+ /**
187
+ * Pause: prevent new entries, keep existing positions.
188
+ * Returns the new agent mode if the transition is valid.
189
+ */
190
+ export function executePause(currentMode) {
191
+ if (currentMode === 'PAUSED') {
192
+ return {
193
+ result: { action: 'pause', executed: true, timestamp: new Date().toISOString(), details: 'Already paused' },
194
+ newMode: 'PAUSED',
195
+ };
196
+ }
197
+ if (currentMode === 'STOPPED') {
198
+ return {
199
+ result: { action: 'pause', executed: false, timestamp: new Date().toISOString(), details: 'Cannot pause — agent is stopped' },
200
+ newMode: 'STOPPED',
201
+ };
202
+ }
203
+ return {
204
+ result: { action: 'pause', executed: true, timestamp: new Date().toISOString(), details: 'Trading paused' },
205
+ newMode: 'PAUSED',
206
+ };
207
+ }
208
+ /**
209
+ * Resume: re-enable entries.
210
+ * Returns the new agent mode if the transition is valid.
211
+ */
212
+ export function executeResume(currentMode) {
213
+ if (currentMode === 'ACTIVE') {
214
+ return {
215
+ result: { action: 'resume', executed: true, timestamp: new Date().toISOString(), details: 'Already active' },
216
+ newMode: 'ACTIVE',
217
+ };
218
+ }
219
+ if (currentMode === 'STOPPED') {
220
+ return {
221
+ result: { action: 'resume', executed: true, timestamp: new Date().toISOString(), details: 'Trading resumed from stopped state' },
222
+ newMode: 'ACTIVE',
223
+ };
224
+ }
225
+ if (currentMode === 'ERROR') {
226
+ return {
227
+ result: { action: 'resume', executed: false, timestamp: new Date().toISOString(), details: 'Cannot resume — agent is in error state' },
228
+ newMode: 'ERROR',
229
+ };
230
+ }
231
+ return {
232
+ result: { action: 'resume', executed: true, timestamp: new Date().toISOString(), details: 'Trading resumed' },
233
+ newMode: 'ACTIVE',
234
+ };
235
+ }
@@ -0,0 +1,322 @@
1
+ import type { OpenClawProvider, ProviderEvents, ProviderEventName } from '../provider.js';
2
+ import type { EmergencyAction, ReconciliationSnapshot, TradingMode } from '../types.js';
3
+ import type { GatewayConfig } from '../gateway/gateway-config.js';
4
+ import { type GetBracketConfigOutcome, type SetBracketRequirementOutcome, type BracketRequirementFlag, type SetTradingModeOutcome, type SetExchangeCredentialsOutcome, type TestExchangeCredentialsOutcome, type ClearExchangeCredentialsOutcome } from './onboarding-commands.js';
5
+ /** Decide whether the session-start-NAV anchor needs to be (re)set. Pure so it
6
+ * can be unit-tested without the full provider. Returns true when the anchor
7
+ * has never been set, OR — for PAPER only — when it belongs to a previous UTC
8
+ * day (so Day P&L re-anchors at midnight). Live/shadow anchor once and then
9
+ * sync to the plugin's Binance income-endpoint value, so they never
10
+ * self-re-anchor here. */
11
+ export declare function shouldAnchorSessionNav(args: {
12
+ sessionStartNav: number;
13
+ sessionDate: string | null;
14
+ today: string;
15
+ mode: TradingMode;
16
+ }): boolean;
17
+ export declare class GatewayProvider implements OpenClawProvider {
18
+ private readonly config;
19
+ private http;
20
+ private wsClient;
21
+ private poller;
22
+ private eventParser;
23
+ private toolMap;
24
+ private agentMode;
25
+ private lastKnownStrategy;
26
+ /** Agent display name + live model id, resolved from the gateway (best-effort).
27
+ * Surfaced in agent_state so the dashboard header can show the real agent +
28
+ * model instead of a hardcoded placeholder. Empty until the gateway answers. */
29
+ private agentIdentity;
30
+ /** Emit the one-time rollout probe (gateway response shape) only on the first attempt. */
31
+ private loggedAgentIdentityProbe;
32
+ /** Live heartbeat cadence (seconds), read from the OpenClaw cron store; cached 60s. */
33
+ private heartbeatSeconds?;
34
+ private heartbeatReadAtMs;
35
+ private loggedHeartbeat;
36
+ private lastTicker;
37
+ private positions;
38
+ private balance;
39
+ /** Today's realized PnL (filtered by closedAt) from plugin trade history. */
40
+ private realizedPnlToday;
41
+ /** Cumulative all-time realized PnL — used for the webapp KPI strip. */
42
+ private realizedPnlAllTime;
43
+ /** Paper session execution costs (since plugin boot), forwarded to the
44
+ * dashboard cost cells so "Fees"/"Avg Slip" reflect the drag already in NAV
45
+ * instead of reading $0 (M1). Null in live (LiveAdapter omits them). */
46
+ private paperSessionFees;
47
+ private paperAvgSlippageBps;
48
+ /** Mark-to-market equity from plugin (wallet + collateral + unrealized). */
49
+ private equity;
50
+ private openOrders;
51
+ private startTime;
52
+ /** Session start NAV — -1 means "not yet set", 0 is valid (fresh account). */
53
+ private sessionStartNav;
54
+ /** UTC date (YYYY-MM-DD) the current sessionStartNav anchor belongs to.
55
+ * Null until first anchored. Used to re-anchor Day P&L at UTC midnight —
56
+ * without it a long-running PAPER session kept yesterday's anchor and Day
57
+ * P&L / Realized accumulated across days. */
58
+ private sessionDate;
59
+ /** Track whether we've received at least one positions poll (needed for equity calculation). */
60
+ private hasReceivedPositions;
61
+ /** Track whether we've received at least one balance poll (needed for equity calculation). */
62
+ private hasReceivedBalance;
63
+ private readonly quoteCurrency;
64
+ private orderTimestamps;
65
+ private cancelTimestamps;
66
+ private agentStateInterval;
67
+ private toolRetryInterval;
68
+ private regimeInterval;
69
+ private signalInterval;
70
+ private missionInterval;
71
+ private analyticsInterval;
72
+ private decisionTraceInterval;
73
+ private pendingStartTimers;
74
+ private started;
75
+ private startedAt;
76
+ private lastSeenMissionIds;
77
+ private intelErrors;
78
+ private lastCryptoMetrics;
79
+ private lastVolumeAnalysis;
80
+ private atrData;
81
+ private atrSampleCount;
82
+ private currentRegime;
83
+ /** Timestamp of the most recent trade (fill event) — used in buildAgentState */
84
+ private lastTradeTs;
85
+ /** Tracks open position entries for computing trade results on close.
86
+ * Key: `symbol:side` (e.g. "BTC/USDT:long"), Value: { entryPrice, quantity, side, entryTime, missionId?, setupType?, regime?, confluenceScore? } */
87
+ private openTradeEntries;
88
+ /** Potential closes awaiting 2-poll confirmation. Binance's fetchPositions
89
+ * occasionally returns a held position as missing or zero-contract for a
90
+ * single poll; those transient drops were the root cause of ~350 phantom
91
+ * trade_results rows/day. We only treat a position as closed after it's
92
+ * been absent on TWO consecutive polls. Key: `symbol:side`. */
93
+ private pendingCloses;
94
+ /** Tracks whether the previous poll returned an empty / all-flat positions
95
+ * snapshot while we were holding non-zero positions. A single empty poll is
96
+ * treated as tentative (most often a Binance 429 / partial response). We
97
+ * skip the diff detector and do NOT overwrite this.positions until the
98
+ * next poll confirms — without this guard, the next non-empty poll re-emits
99
+ * synthetic OPEN fills for positions that were there all along (the
100
+ * open-side mirror of the close-side phantom that pendingCloses guards
101
+ * against). See Track 1 verification 2026-04-21 +
102
+ * docs/TRADE_AUDIT_TRAIL_PLAN.md Phase 0. */
103
+ private pendingEmptyPoll;
104
+ /** In-flight trade result reports — drained on stop() to prevent data loss during kill/flatten. */
105
+ private inFlightReports;
106
+ /** File-backed retry queue for failed trade reports */
107
+ private readonly retryQueuePath;
108
+ private retryTimer;
109
+ private tradingMode;
110
+ private readonly listeners;
111
+ constructor(config: GatewayConfig);
112
+ start(): void;
113
+ stop(): void;
114
+ /**
115
+ * Drain in-flight trade result reports. Call during shutdown to prevent
116
+ * data loss during kill/flatten sequences. Returns when all pending
117
+ * reports have completed (or timed out).
118
+ */
119
+ drainReports(): Promise<void>;
120
+ private loadRetryQueue;
121
+ private saveRetryQueue;
122
+ /** Guards processRetryQueue against overlapping runs — the retry timer fires
123
+ * every 60s and startRetryTimer also kicks an immediate run, so a slow
124
+ * (100-item × 10s-timeout) drain could still be in flight when the next
125
+ * timer ticks. Two concurrent drains would double-POST every queued report
126
+ * AND race their reconcile-saves against each other. */
127
+ private retryInProgress;
128
+ /** Monotonic tag stamped on every queued report so processRetryQueue can
129
+ * remove EXACTLY the reports it delivered when it reconciles the file — a
130
+ * blind `save(remaining)` would clobber any report enqueued DURING the
131
+ * network phase (read-modify-write race with enqueueFailedReport). */
132
+ private nextRetryQid;
133
+ private enqueueFailedReport;
134
+ private processRetryQueue;
135
+ private startRetryTimer;
136
+ private stopRetryTimer;
137
+ /** Dual-fire dedup. Every critical command fires on BOTH WebSocket and
138
+ * HTTP simultaneously (CLAUDE.md: "Agent handles idempotency; first to
139
+ * arrive wins") — so each emergency action normally arrives here TWICE,
140
+ * ~0-200ms apart. Without dedup a second `flatten` re-fetches positions
141
+ * before the first close settles on-exchange and submits a second market
142
+ * close — a position-flip risk. Duplicates of the same action while one
143
+ * is in flight (or within a short post-completion window) share the
144
+ * first invocation's promise/result instead of executing again. */
145
+ private static readonly EMERGENCY_DEDUP_MS;
146
+ private emergencyInFlight;
147
+ executeEmergency(action: EmergencyAction): Promise<{
148
+ action: string;
149
+ executed: boolean;
150
+ timestamp: string;
151
+ details?: string;
152
+ }>;
153
+ private runEmergency;
154
+ private executeKill;
155
+ private executeFlatten;
156
+ private executePause;
157
+ private executeResume;
158
+ handleChat(content: string): Promise<{
159
+ received: boolean;
160
+ error?: string;
161
+ }>;
162
+ closePosition(symbol: string): Promise<{
163
+ closed: boolean;
164
+ symbol: string;
165
+ error?: string;
166
+ }>;
167
+ setTradingMode(mode: TradingMode, acknowledged?: boolean): Promise<SetTradingModeOutcome>;
168
+ setExchangeCredentials(apiKey: string, secret: string, testnet?: boolean): Promise<SetExchangeCredentialsOutcome>;
169
+ testExchangeCredentials(apiKey: string, secret: string, testnet?: boolean): Promise<TestExchangeCredentialsOutcome>;
170
+ clearExchangeCredentials(): Promise<ClearExchangeCredentialsOutcome>;
171
+ /** Operator-only. Read the current bracket-orders config from the plugin. */
172
+ getBracketConfig(): Promise<GetBracketConfigOutcome>;
173
+ /** Operator-only. Flip requireStopLoss or requireTakeProfit. */
174
+ setBracketRequirement(flag: BracketRequirementFlag, value: boolean): Promise<SetBracketRequirementOutcome>;
175
+ getSnapshot(): Promise<ReconciliationSnapshot>;
176
+ on<E extends ProviderEventName>(event: E, listener: ProviderEvents[E]): void;
177
+ off<E extends ProviderEventName>(event: E, listener: ProviderEvents[E]): void;
178
+ private initialize;
179
+ /**
180
+ * Wait for the WS handshake to complete and return the device token.
181
+ * Returns null if handshake fails or times out.
182
+ */
183
+ private waitForDeviceToken;
184
+ /**
185
+ * Essential optional tools — retry loop keeps running until these are discovered.
186
+ * These are needed for position tracking, emergency controls, and trade display.
187
+ */
188
+ private static readonly ESSENTIAL_OPTIONAL_TOOLS;
189
+ /** Check if all essential tools are discovered. */
190
+ private hasAllEssentialTools;
191
+ /** Retry tool discovery every 30s until all essential tools are found. */
192
+ private startToolRetry;
193
+ private createPoller;
194
+ private wireWsEvents;
195
+ private onWsConnected;
196
+ private onAgentEvent;
197
+ private onPollerTicker;
198
+ /** Strip the live-Binance settle suffix (`SOL/USDT:USDT` → `SOL/USDT`)
199
+ * to ONE canonical form, matching the webapp/plugin normalizeSymbol
200
+ * convention (chart/DB are unsuffixed). The plugin's getPositions
201
+ * returns the suffixed CCXT form via REST but the unsuffixed form via
202
+ * the WS store; under weight pressure isStoreTrusted() flickers between
203
+ * the two, so the raw symbol alternates poll-to-poll. */
204
+ private canonicalSymbol;
205
+ private onPollerPositions;
206
+ /**
207
+ * Compare previous and current positions from polling.
208
+ * When a position appears, changes size, or disappears,
209
+ * emit synthetic orderUpdate events so chart markers show trades.
210
+ */
211
+ /** Apply a fresh mark price to a position (mutates in place). */
212
+ private applyMarkPrice;
213
+ private detectPositionChanges;
214
+ /**
215
+ * Emit a synthetic orderUpdate event derived from position polling data.
216
+ * Used when WS agent events are unavailable (gateway event filtering).
217
+ */
218
+ private emitSyntheticFill;
219
+ private onPollerBalance;
220
+ /**
221
+ * Set session start NAV once both balance AND positions have been received.
222
+ * Checks persisted file first — if today's NAV was already captured in a
223
+ * previous skill run, reuse it so Day P&L is stable across restarts.
224
+ */
225
+ private trySetSessionStartNav;
226
+ private onPollerOpenOrders;
227
+ private onPollerMarketStructure;
228
+ private onPollerCryptoMetrics;
229
+ private onPollerVolumeAnalysis;
230
+ /**
231
+ * Build and emit a trade journal entry whenever a synthetic fill is detected.
232
+ * Called from emitSyntheticFill — captures market + risk context at trade time.
233
+ */
234
+ private emitTradeJournal;
235
+ /** Simple heat score (0–100) matching webapp's HeatGauge logic */
236
+ private computeHeatScoreSimple;
237
+ /** Cache last market structure for journal entries */
238
+ private lastMarketStructure;
239
+ private fetchFreshPositions;
240
+ private fetchFreshBalance;
241
+ private fetchFreshOpenOrders;
242
+ private fetchAgentHealth;
243
+ /**
244
+ * Resolve the agent's display name + live model id directly from the gateway.
245
+ * The primary carrier is the `sessions.list` RPC (rows carry agentId + the
246
+ * live model); `health` (above) is a secondary feed. Best-effort and
247
+ * fail-open — the gateway/version may not expose either, in which case the
248
+ * header simply falls back to its placeholder. Re-emits agent_state on a hit
249
+ * so the dashboard updates without waiting for the next periodic tick.
250
+ */
251
+ private refreshAgentIdentity;
252
+ /** Merge newly-resolved identity fields into the cache. Returns true if anything changed. */
253
+ private mergeAgentIdentity;
254
+ /**
255
+ * Extract the live model id from a `sessions.list` (or `health`) gateway
256
+ * payload. We deliberately do NOT take the agent NAME from here: sessions
257
+ * rows are per-chat and their `name` is a channel/chat id (e.g. a Telegram
258
+ * id), not the agent's identity — the display name comes from
259
+ * resolveAgentNameFromIdentity(). The exact shape isn't contractually stable,
260
+ * so probe the documented paths (row → runtime → defaults → root) and accept
261
+ * either a bare string or a `{ primary }` object.
262
+ */
263
+ private extractAgentModel;
264
+ /**
265
+ * Resolve the agent's display name from the OpenClaw workspace IDENTITY.md
266
+ * (`- **Name:** <X>`), local to this deployment (per-tenant correct — each
267
+ * trader's own agent has its own IDENTITY.md). Best-effort + fail-open:
268
+ * returns undefined when the file is missing/unreadable, the field is the
269
+ * unfilled placeholder, or the value looks like an id rather than a name.
270
+ */
271
+ private resolveAgentNameFromIdentity;
272
+ /**
273
+ * Live heartbeat cadence in seconds — the agent's TRUE beat interval, read
274
+ * from the OpenClaw cron store (`~/.openclaw/cron/jobs.json` → the enabled
275
+ * heartbeat job's `schedule.everyMs`). This is the source of truth: the old
276
+ * hardcoded 1800 and `agents.defaults.heartbeat` are both stale/wrong once
277
+ * the operator edits the cron (`openclaw cron ... --every 15m`). Drives both
278
+ * the "beat every Nm" label and the unresponsive (2×) threshold. Cached 60s;
279
+ * returns undefined (caller defaults to 1800) when unreadable.
280
+ */
281
+ private resolveHeartbeatSeconds;
282
+ /** Build agent state from internal fields (used by both emitAgentState and getSnapshot). */
283
+ private buildAgentState;
284
+ private emitAgentState;
285
+ private emitRiskUpdate;
286
+ /** Compute risk metrics — delegates to pure functions in risk-calculator.ts */
287
+ private computeRiskMetrics;
288
+ /** Compute total equity — delegates to pure function in risk-calculator.ts */
289
+ private computeEquity;
290
+ /** Compute signed notional — delegates to pure function in risk-calculator.ts */
291
+ private computePositionNotional;
292
+ /**
293
+ * Report a completed trade to the Intelligence API for persistent cross-session analytics.
294
+ * Called when a position closes (detected via position polling diffs).
295
+ * Handles partial closes: updates remaining quantity in openTradeEntries instead of deleting.
296
+ *
297
+ * @param key Composite key `symbol:side` into openTradeEntries
298
+ * @param symbol CCXT symbol (e.g. "BTC/USDT")
299
+ * @param exitPrice Fill price for the close
300
+ * @param closedQty Quantity actually closed (may be less than full position for partial closes)
301
+ */
302
+ private reportTradeResult;
303
+ /**
304
+ * Seed lastTradeTs from Intelligence API on startup. One-shot, best-effort.
305
+ * Prevents "Last Trade: Never" after every restart when the agent is flat.
306
+ */
307
+ private seedLastTradeTs;
308
+ /** Symbol formatted for intelligence API (BTC/USDT → BTCUSDT). */
309
+ private get intelligenceSymbol();
310
+ /** Shared intelligence poller with circuit-breaker (exponential backoff on errors). */
311
+ private startIntelligencePoller;
312
+ /** Log first error per poller, suppress subsequent repeats. */
313
+ private trackIntelError;
314
+ private startRegimePoller;
315
+ private startSignalPoller;
316
+ private startMissionPoller;
317
+ private startAnalyticsPoller;
318
+ private startDecisionTracePoller;
319
+ private static readonly STRATEGY_LABELS;
320
+ private presentMissionToAgent;
321
+ private fire;
322
+ }