@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,169 @@
1
+ // ListenKeyManager — owns the Binance Futures user-data listenKey lifecycle.
2
+ //
3
+ // Flow:
4
+ // 1. `start()` — POST /fapi/v1/listenKey → emit 'keyMinted' with the listenKey.
5
+ // 2. Schedule PUT /fapi/v1/listenKey every `refreshIntervalMs` (default 25 min).
6
+ // Binance expires idle keys at 60 min; 25 min gives a 2x safety margin.
7
+ // 3. On failed keep-alive, re-mint (POST) a fresh key and emit 'keyRotated'.
8
+ // Binance returns the SAME listenKey for repeat POSTs from the same API
9
+ // key, so "re-mint" is effectively a probe that the key is still valid.
10
+ // If the probe succeeds, we accept its answer; if it fails, we emit
11
+ // 'keyKeepAliveFailed' and stop — the parent UserDataStream schedules
12
+ // a reconnect that re-enters `start()`.
13
+ // 4. `stop()` — clear timer + DELETE /fapi/v1/listenKey (best-effort).
14
+ //
15
+ // Why this is a separate component:
16
+ // - Testable in isolation with fake timers + a mock API.
17
+ // - UserDataStream can stay focused on frame parsing + reconnect logic.
18
+ // - Graduated alert thresholds live here (3 failed refreshes = WARN,
19
+ // 10 = ERROR) rather than being spread through the WS state machine.
20
+ import { EventEmitter } from 'node:events';
21
+ import { logger, formatError } from '../logger.js';
22
+ const TAG = 'listen-key-manager';
23
+ const REAL_TIMER_DEPS = {
24
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
25
+ clearTimeout: (h) => clearTimeout(h),
26
+ now: () => Date.now(),
27
+ };
28
+ /** Lightweight typed emitter — EventEmitter with a phantom type. */
29
+ export class ListenKeyManager extends EventEmitter {
30
+ api;
31
+ refreshIntervalMs;
32
+ maxConsecutiveFailures;
33
+ timers;
34
+ currentKey = null;
35
+ keyMintedAt = null;
36
+ refreshTimer = null;
37
+ consecutiveFailures = 0;
38
+ running = false;
39
+ constructor(api, options = {}) {
40
+ super();
41
+ this.api = api;
42
+ this.refreshIntervalMs = options.refreshIntervalMs ?? 25 * 60_000;
43
+ this.maxConsecutiveFailures = options.maxConsecutiveFailures ?? 3;
44
+ this.timers = {
45
+ setTimeout: options.timers?.setTimeout ?? REAL_TIMER_DEPS.setTimeout,
46
+ clearTimeout: options.timers?.clearTimeout ?? REAL_TIMER_DEPS.clearTimeout,
47
+ now: options.timers?.now ?? REAL_TIMER_DEPS.now,
48
+ };
49
+ }
50
+ /** Mint a listenKey and start the refresh loop. Returns the minted key.
51
+ * Throws if the first POST fails — caller decides how to back off. */
52
+ async start() {
53
+ if (this.running) {
54
+ throw new Error('ListenKeyManager already started');
55
+ }
56
+ const key = await this.api.createListenKey();
57
+ this.currentKey = key;
58
+ this.keyMintedAt = this.timers.now();
59
+ this.consecutiveFailures = 0;
60
+ this.running = true;
61
+ this.emit('keyMinted', key);
62
+ this.scheduleRefresh();
63
+ logger.info(TAG, `listenKey minted (hash=${hashKey(key)}), refresh every ${this.refreshIntervalMs / 1000}s`);
64
+ return key;
65
+ }
66
+ /** Stop the refresh loop and best-effort close the key. Idempotent.
67
+ * For GRACEFUL shutdown only — a reconnect-driven teardown must use
68
+ * `stopKeepAlive()` instead (see below). */
69
+ async stop() {
70
+ if (!this.running)
71
+ return;
72
+ this.running = false;
73
+ if (this.refreshTimer) {
74
+ this.timers.clearTimeout(this.refreshTimer);
75
+ this.refreshTimer = null;
76
+ }
77
+ if (this.currentKey) {
78
+ try {
79
+ await this.api.closeListenKey();
80
+ }
81
+ catch (err) {
82
+ // Not worth retrying on shutdown — Binance expires idle keys anyway.
83
+ logger.warn(TAG, `closeListenKey failed on stop (non-fatal): ${formatError(err)}`);
84
+ }
85
+ this.currentKey = null;
86
+ this.keyMintedAt = null;
87
+ }
88
+ }
89
+ /** Stop the refresh loop WITHOUT the DELETE — for reconnect-driven closes.
90
+ * Binance returns the SAME active listenKey on a re-POST and merely
91
+ * extends its validity (doc-verified 2026-07-02: "Doing a POST on an
92
+ * account with an active listenKey will return the currently active
93
+ * listenKey and extend its validity for 60 minutes"), so an in-flight
94
+ * DELETE racing the reconnect's POST can land AFTER it and invalidate the
95
+ * key the fresh socket just authenticated with — a silent dead stream.
96
+ * Skipping the DELETE is safe: an unused key expires on its own after
97
+ * 60 min of no keep-alive. Synchronous + idempotent. */
98
+ stopKeepAlive() {
99
+ if (!this.running)
100
+ return;
101
+ this.running = false;
102
+ if (this.refreshTimer) {
103
+ this.timers.clearTimeout(this.refreshTimer);
104
+ this.refreshTimer = null;
105
+ }
106
+ this.currentKey = null;
107
+ this.keyMintedAt = null;
108
+ }
109
+ /** Current listenKey, or null if stopped. */
110
+ getKey() {
111
+ return this.currentKey;
112
+ }
113
+ /** Age in ms since the key was last minted. Null if stopped. Useful for
114
+ * observability dashboards — a key far beyond refreshIntervalMs but still
115
+ * alive means Binance is tolerating us past the nominal window. */
116
+ getKeyAge() {
117
+ if (this.keyMintedAt == null)
118
+ return null;
119
+ return this.timers.now() - this.keyMintedAt;
120
+ }
121
+ /** Number of consecutive failed keep-alive probes. Exposed for diagnostics. */
122
+ getConsecutiveFailures() {
123
+ return this.consecutiveFailures;
124
+ }
125
+ // ---- Internal ----
126
+ scheduleRefresh() {
127
+ if (!this.running)
128
+ return;
129
+ this.refreshTimer = this.timers.setTimeout(() => {
130
+ this.refreshTimer = null;
131
+ void this.refresh();
132
+ }, this.refreshIntervalMs);
133
+ }
134
+ async refresh() {
135
+ if (!this.running)
136
+ return;
137
+ try {
138
+ await this.api.keepAliveListenKey();
139
+ this.consecutiveFailures = 0;
140
+ this.emit('keyRefreshed');
141
+ logger.info(TAG, `listenKey refreshed (age=${Math.round((this.getKeyAge() ?? 0) / 1000)}s)`);
142
+ this.scheduleRefresh();
143
+ }
144
+ catch (err) {
145
+ this.consecutiveFailures += 1;
146
+ const reason = formatError(err);
147
+ if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
148
+ // Stream is dead. Don't reschedule — parent will restart us.
149
+ logger.error(TAG, `listenKey keep-alive failed ${this.consecutiveFailures} times (last: ${reason}) — giving up`);
150
+ this.running = false;
151
+ if (this.refreshTimer) {
152
+ this.timers.clearTimeout(this.refreshTimer);
153
+ this.refreshTimer = null;
154
+ }
155
+ this.emit('keyKeepAliveFailed', reason);
156
+ return;
157
+ }
158
+ // Transient — log and retry on the normal cadence. We deliberately do
159
+ // NOT shrink the interval on failure; the keep-alive window is 60 min
160
+ // so we can afford up to 2 more tries before the key expires.
161
+ logger.warn(TAG, `listenKey keep-alive failed (${this.consecutiveFailures}/${this.maxConsecutiveFailures}): ${reason}`);
162
+ this.scheduleRefresh();
163
+ }
164
+ }
165
+ }
166
+ /** First 8 chars of a listenKey for log correlation without leaking the key. */
167
+ function hashKey(key) {
168
+ return key.length > 8 ? key.slice(0, 8) : '(short)';
169
+ }
@@ -0,0 +1,264 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { IExchangeAdapter, AdapterReadiness, OrderOptions } from '../exchange-adapter.js';
3
+ import { BinancePrivateApi } from '../ccxt/binance-private.js';
4
+ import type { CcxtOrder, CcxtBalance, CcxtPosition, TradingMode, ExchangeConfig } from '../types.js';
5
+ import type { PositionMetadata, CloseReason } from '../simulator/types.js';
6
+ import { ExchangeInfoCache } from './exchange-info-cache.js';
7
+ import { BinanceRateLimiter } from './rate-limiter.js';
8
+ import { IntentJournal } from './intent-journal.js';
9
+ import { OrderPoller } from './order-poller.js';
10
+ import { SlippageTracker } from './slippage-tracker.js';
11
+ import { PositionReconciler } from './reconciler.js';
12
+ import { LiveBalanceEnricher } from './live-balance-enricher.js';
13
+ import { DepositTracker } from './deposit-tracker.js';
14
+ import { BracketLedger } from './bracket-ledger.js';
15
+ import { BracketManager } from './bracket-manager.js';
16
+ import { type BracketMode } from '../config/brackets-config.js';
17
+ import { type UserDataStreamMode, type UserDataStreamTunables } from '../config/user-data-stream-config.js';
18
+ import { UserDataStreamController } from './user-data-stream-controller.js';
19
+ import type { AutoCaptureContext } from '../ingest/position-auto-capture.js';
20
+ import type { TradeStoreClient } from '../ingest/trade-store-client.js';
21
+ /** Optional audit-trail wiring (TRADE_AUDIT_TRAIL_PLAN Phase 1). Caller
22
+ * passes this only when userDataStream.dbWrite='on' AND the WEBAPP_INGEST_TOKEN
23
+ * + REEFCLAW_USER_ID env vars are present. Forwarded into the controller
24
+ * which spins up a WsIngest alongside the existing read path. */
25
+ export interface TradeIngestWiring {
26
+ client: TradeStoreClient;
27
+ userId: string;
28
+ exchange?: string;
29
+ }
30
+ /**
31
+ * Backfill `balance.free[cur] = total[cur]` for currencies where Binance's CCXT
32
+ * response left the per-currency `free` and `used` at zero despite a non-zero
33
+ * total. Only safe when `used` is zero — otherwise the exchange really is
34
+ * reserving margin and we must not inflate availability.
35
+ */
36
+ export declare function repairFreeFromTotal(balance: CcxtBalance): void;
37
+ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapter {
38
+ readonly mode: TradingMode;
39
+ readonly isLive = true;
40
+ private api;
41
+ private sizeCapMultiplier;
42
+ private maxPositionUSDT;
43
+ private exchangeInfo;
44
+ private isHedgeMode;
45
+ private getOpenOrdersUnavailableUntil;
46
+ private static readonly OPEN_ORDERS_RL_COOLDOWN_MS;
47
+ private rateLimiter;
48
+ private intentJournal;
49
+ private orderPoller;
50
+ private slippageTracker;
51
+ private reconciler;
52
+ private enricher;
53
+ private depositTracker;
54
+ private readonly bracketMode;
55
+ private bracketLedger;
56
+ private bracketManager;
57
+ private bracketReconciler;
58
+ private readonly userDataStreamMode;
59
+ private userDataStream;
60
+ private readonly wsAlgoStatusByCid;
61
+ private static readonly WS_ALGO_STATUS_CAP;
62
+ private static readonly WS_LIVE_LEG_MAX_AGE_MS;
63
+ private _sessionStartNav;
64
+ private lastIncomeRefreshMs;
65
+ private incomeAnchorUtcDay;
66
+ private liveMetadata;
67
+ private _readiness;
68
+ get readiness(): AdapterReadiness;
69
+ /** Session-start NAV, captured once at initialization. Used for drawdown calculation. */
70
+ getSessionStartNav(): number | null;
71
+ constructor(config: ExchangeConfig, mode: 'MICRO_LIVE' | 'LIVE', microLiveConfig?: {
72
+ sizeCapPercent?: number;
73
+ maxPositionUSDT?: number;
74
+ }, bracketMode?: BracketMode, userDataStreamMode?: UserDataStreamMode, userDataStreamTunables?: UserDataStreamTunables,
75
+ /** TRADE_AUDIT_TRAIL_PLAN Phase 1 — optional audit-trail wiring. Wired
76
+ * through to UserDataStreamController. Absent today on every existing
77
+ * call site (default off); operator-driven once activation is approved. */
78
+ tradeIngest?: TradeIngestWiring,
79
+ /** Position-decision auto-capture wiring. When provided alongside
80
+ * tradeIngest, ws-ingest also routes every TRADE event through
81
+ * `onWsFillObserved` to capture limit-order entries + scale-ins
82
+ * (PR1 deferral resolution, 2026-05-03). Optional for back-compat. */
83
+ autoCapture?: AutoCaptureContext);
84
+ /** Get the underlying BinancePrivateApi (for advanced queries like fetchTicker). */
85
+ getApi(): BinancePrivateApi;
86
+ /** Get the exchange info cache (for symbol rules). */
87
+ getExchangeInfo(): ExchangeInfoCache;
88
+ /** Whether the account is in hedge mode. */
89
+ getIsHedgeMode(): boolean;
90
+ /** Get safety modules for external access (dashboard, tools, etc.). */
91
+ getRateLimiter(): BinanceRateLimiter;
92
+ getIntentJournal(): IntentJournal;
93
+ getOrderPoller(): OrderPoller;
94
+ getSlippageTracker(): SlippageTracker;
95
+ getReconciler(): PositionReconciler;
96
+ getEnricher(): LiveBalanceEnricher;
97
+ getDepositTracker(): DepositTracker;
98
+ /** Active user-data-stream mode. `'off'` when the feature flag is disabled. */
99
+ getUserDataStreamMode(): UserDataStreamMode;
100
+ /** User-data stream controller, or null if mode='off'. Used by dashboards
101
+ * and diagnostic tools to read the live-state store + health. */
102
+ getUserDataStream(): UserDataStreamController | null;
103
+ /**
104
+ * Post-registration async initialization.
105
+ * Must complete before create_order is allowed (readiness gate).
106
+ * Emergency controls (cancel, close) work regardless of readiness.
107
+ */
108
+ initialize(): Promise<void>;
109
+ /**
110
+ * Acknowledge existing positions on startup.
111
+ * Transitions DEGRADED → READY. Exchange info + position mode are already loaded
112
+ * (they're loaded before the position check in initialize()).
113
+ */
114
+ acknowledgePositions(): Promise<void>;
115
+ /**
116
+ * Tear down background loops so the adapter can be replaced at runtime
117
+ * (e.g. when the operator changes credentials or trading mode from the
118
+ * dashboard). Must be called before discarding the LiveAdapter reference
119
+ * or the poller + reconciler timers will keep firing against stale state.
120
+ *
121
+ * Idempotent — safe to call multiple times.
122
+ */
123
+ shutdown(): void;
124
+ createOrder(symbol: string, side: 'buy' | 'sell', type: 'market' | 'limit', amount: number, price?: number, metadata?: PositionMetadata, options?: OrderOptions): Promise<CcxtOrder>;
125
+ /**
126
+ * Attach brackets in the background. Auto-flattens on failure per locked
127
+ * decision #2. Never throws — errors are surfaced via emitted events and
128
+ * the subsequent flatten.
129
+ */
130
+ private attachBracketsAsync;
131
+ /**
132
+ * AUTHORITATIVE bracket-lifecycle handler — Binance's ALGO_UPDATE push.
133
+ *
134
+ * Why this exists (2026-05-15 RCA, doc-verified): pre-fix the plugin
135
+ * learned bracket state by REST-polling `audit_bracket_protection` /
136
+ * `fetchOpenOrders` and *guessing* — under a 429 storm that produced the
137
+ * audit→re-attach→cancel spiral that flushed live positions. Binance
138
+ * already PUSHES authoritative conditional-order lifecycle. Doc-exact
139
+ * status semantics (developers.binance.com → Event Algo Order Update):
140
+ *
141
+ * EXPIRED = SYSTEM cancel. The canonical trigger is "GTE_GTC
142
+ * conditional + position closed → Binance auto-cancels the
143
+ * surviving sibling". So EXPIRED on our bracket cid ⇒ the
144
+ * position just closed by a non-close_position path (bracket
145
+ * fill, manual, opposite-side). EXPECTED — not a fault.
146
+ * TRIGGERED / FINISHED = the bracket fired (SL/TP hit) → position closed.
147
+ * CANCELED = MANUAL cancel (an explicit cancelOrder from us). Post the
148
+ * bracket-reconciler fix we should NOT be doing this while a
149
+ * position is open; if we see it, it's the canary that some
150
+ * *other* path still strips protection — WARN loudly.
151
+ *
152
+ * For EXPIRED/TRIGGERED/FINISHED we (a) mark the ledger terminal so the
153
+ * attach-brackets re-attach loop STOPS chasing a bracket Binance already
154
+ * removed, and (b) emit the SAME `drift_detected` shape the position
155
+ * reconciler emits, so the existing, tested close-bypass cleanup
156
+ * (`onReconcilerObservedClose` in index.ts) runs immediately instead of
157
+ * up to 60 s later — which is what clears the stale state-store entry and
158
+ * stops the NEXT entry on the symbol being mis-classified as a scale-in
159
+ * (the bug that left fresh positions naked). No auto-flatten here: that
160
+ * stays deferred until this signal has soaked.
161
+ */
162
+ private onAlgoUpdate;
163
+ /** Expose the bracket manager for agent-facing tools (modify_stop, etc.).
164
+ * Returns null when bracket mode is 'off'. */
165
+ getBracketManager(): BracketManager | null;
166
+ /** Expose the bracket ledger for reconciliation + admin queries. */
167
+ getBracketLedger(): BracketLedger | null;
168
+ /** Is the user-data WS store currently trusted (seeded + fresh + no gap)?
169
+ * The Tier-1 gate for resolveBracketLegLiveness — when false the WS
170
+ * per-cid signal is NOT consulted (a stale/gapped stream that missed a
171
+ * CANCELED would otherwise yield a false 'live' on a naked leg — the exact
172
+ * flaw the layered design exists to prevent). Null stream (mode=off) ⇒
173
+ * never trusted ⇒ always the REST authority. */
174
+ isUserDataStoreTrusted(): boolean;
175
+ /** Authoritative per-bracket-leg liveness — the Phase 2 disambiguator that
176
+ * replaces inferring presence/absence from a broad openOrders snapshot
177
+ * (the SOL/INJ spiral root: a *successful* empty /fapi/v1/openAlgoOrders
178
+ * is NOT proof a leg is gone).
179
+ *
180
+ * Precedence (the agreed layering):
181
+ * 1. WS fast-path — ONLY behind isUserDataStoreTrusted(): the real
182
+ * per-cid ALGO_UPDATE status (incl. CANCELED, which the ledger does
183
+ * NOT record). Zero weight, sub-second. No record for the cid ⇒ no
184
+ * WS opinion ⇒ fall through (never infer from absence).
185
+ * 2. REST authority — queryAlgoOrderStatus(cid): a DIRECT weight-1
186
+ * question about that exact order id. Used when WS is untrusted or
187
+ * silent on the cid.
188
+ * 3. 'unknown' — both unavailable. The d59e51b safe floor: callers MUST
189
+ * refuse destructive action and retry; NEVER treat 'unknown' as
190
+ * 'terminal'/'stale'.
191
+ *
192
+ * 'live' = leg is on the exchange (do NOT reattach/clobber).
193
+ * 'terminal' = positively confirmed gone (CANCELED/EXPIRED/REJECTED) or
194
+ * fired (TRIGGERED/FINISHED) — only this licenses repair.
195
+ * 'unknown' = could not get an answer — safe-wait. */
196
+ resolveBracketLegLiveness(cid: string): Promise<'live' | 'terminal' | 'unknown'>;
197
+ /** Symbol-scoped generalization of the trusted-WS fast-path — the
198
+ * authoritative action-layer guard for the 2026-05-16 INJ bracket spiral.
199
+ *
200
+ * `resolveBracketLegLiveness(cid)` answers about ONE cid. The spiral is:
201
+ * a prior `attach_brackets` reattach placed a FRESH bracket that is live
202
+ * on the exchange under a NEW cid, while the ledger row still holds the
203
+ * PREVIOUS, genuinely-terminal cids. Classifying only the ledger's cids
204
+ * yields a false 'stale' → clear+reattach cancels the live fresh bracket
205
+ * → naked → audit unprotected → attach → spiral (prod: INJ/USDT cancelled
206
+ * every ~15-40s for hours). This asks the question the ledger cids can't:
207
+ * "is ANY rc-* bracket leg for this symbol live RIGHT NOW, under any cid?"
208
+ *
209
+ * Trust model (identical to resolveBracketLegLiveness Tier-1): the WS map
210
+ * is consulted ONLY when `isUserDataStoreTrusted()` (seeded + fresh +
211
+ * no-gap — the property that guarantees no missed CANCELED, so a record
212
+ * reading 'live' really is live). When the store is NOT trusted this
213
+ * returns `{ trusted:false, liveCids:[] }` and the caller must NOT infer
214
+ * "no live leg" from it — it falls back to the per-leg REST authority and,
215
+ * failing that, refuses the destructive action (the d59e51b safe floor).
216
+ * A record older than WS_LIVE_LEG_MAX_AGE_MS is ignored (caller falls back
217
+ * too) so a stale 'live' can never suppress a genuine repair forever. */
218
+ findTrustedLiveBracketCidsForSymbol(symbol: string): {
219
+ trusted: boolean;
220
+ liveCids: string[];
221
+ };
222
+ /** Cancel every bracket-tagged (SL or TP) open order for a symbol. Used by
223
+ * the `attach_brackets` recovery tool to clear stale bracket orders before
224
+ * re-attaching a fresh pair. Does not touch non-bracket orders. Returns
225
+ * the number successfully cancelled. Errors are logged + swallowed —
226
+ * "already gone" is treated as success by the underlying cancel path. */
227
+ cancelSymbolBracketOrders(symbol: string): Promise<number>;
228
+ cancelOrder(orderId: string, symbol?: string): Promise<CcxtOrder>;
229
+ cancelAllOrders(symbol?: string): Promise<CcxtOrder[]>;
230
+ closePosition(symbol: string, closeReason?: CloseReason): Promise<CcxtOrder>;
231
+ /** Re-arm protection on a position whose closePosition flow cancelled the
232
+ * brackets and then failed every market-close attempt. Reuses the normal
233
+ * attach machinery: re-register the ledger row (terminal row → fresh
234
+ * pending_entry with the SAME stop/target levels) and run the standard
235
+ * 3-retry attach. Never throws — this runs on the already-failing close
236
+ * path; on any failure it emits the reconciler's `position_unprotected`
237
+ * drift shape (re-emitted upstream as `bracket_drift`) plus a loud log so
238
+ * the operator knows re-protection is required. */
239
+ private reattachBracketsAfterFailedClose;
240
+ getBalance(): Promise<CcxtBalance>;
241
+ /** Re-anchor the realized-today seed + sessionStartNav from /fapi/v1/income.
242
+ * Throttled to {@link INCOME_REFRESH_MIN_INTERVAL_MS}; UTC date rollover
243
+ * forces an immediate refresh. Best-effort — failures are logged and
244
+ * swallowed so a transient income-endpoint blip never blocks a balance
245
+ * read. Called from getBalance() with the fresh quote wallet so we don't
246
+ * fetch balance twice.
247
+ */
248
+ private refreshIncomeAnchor;
249
+ getPositions(symbol?: string): Promise<CcxtPosition[]>;
250
+ getPositionsOrNull(symbol?: string): Promise<CcxtPosition[] | null>;
251
+ /** Merge agent-supplied entry metadata + ratchet MFE state into the raw
252
+ * CCXT position list. Side effect: drops metadata for symbols no longer
253
+ * in the open-positions snapshot (only when called without a symbol filter
254
+ * — symbol-scoped calls skip cleanup so we don't lose context for other
255
+ * open positions). */
256
+ private decoratePositionsWithMetadata;
257
+ getOpenOrders(symbol?: string): Promise<CcxtOrder[]>;
258
+ fetchOrder(orderId: string, symbol?: string): Promise<CcxtOrder | null>;
259
+ getLastPrice(symbol: string): Promise<number | null>;
260
+ /** Sync rate limiter from exchange response headers after every private API call. */
261
+ private syncRateLimits;
262
+ private loadExchangeInfo;
263
+ private detectPositionMode;
264
+ }