@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,102 @@
1
+ /** Role of a clientOrderId within a bracket. */
2
+ export type BracketRole = 'entry' | 'stop' | 'target';
3
+ /** Short tag stored inside clientOrderId suffix. */
4
+ export type BracketRoleTag = 'e' | 's' | 't';
5
+ /** Immutable opaque identifier tying entry + SL + TP together. */
6
+ export type BracketId = string;
7
+ /** Lifecycle state of a bracket attached to an open position. */
8
+ export type BracketState = 'pending_entry' | 'attaching' | 'active' | 'partial' | 'triggered_sl' | 'triggered_tp' | 'cancelled' | 'failed';
9
+ /** Operator-provided bracket parameters for a new entry. */
10
+ export interface BracketRequest {
11
+ symbol: string;
12
+ side: 'buy' | 'sell';
13
+ stopPrice?: number;
14
+ targetPrice?: number;
15
+ }
16
+ /** A single ledger entry representing one bracket. Authoritative on disk;
17
+ * mirrored to intelligence DB for durability. */
18
+ export interface BracketLedgerEntry {
19
+ bracketId: BracketId;
20
+ symbol: string;
21
+ entrySide: 'buy' | 'sell';
22
+ entryCid: string;
23
+ slCid?: string;
24
+ tpCid?: string;
25
+ stopPrice?: number;
26
+ targetPrice?: number;
27
+ /** Filled entry quantity the bracket legs were sized to (reduceOnly +
28
+ * explicit amount, NOT closePosition). modifyStop/modifyTarget MUST reuse
29
+ * this — rebuilding a leg with a placeholder size silently under-protects
30
+ * the position to that size. Set at attach time. */
31
+ qty?: number;
32
+ state: BracketState;
33
+ attachStartedAt?: string;
34
+ attachCompletedAt?: string;
35
+ attachLatencyMs?: number;
36
+ closedAt?: string;
37
+ closeReason?: 'triggered_sl' | 'triggered_tp' | 'cancelled_manual' | 'cancelled_auto' | 'attach_failed';
38
+ lastError?: string;
39
+ createdAt: string;
40
+ updatedAt: string;
41
+ }
42
+ /** Event emitted by BracketManager for telemetry + logging. */
43
+ export type BracketEvent = {
44
+ type: 'bracket.entry_submitted';
45
+ bracketId: BracketId;
46
+ symbol: string;
47
+ ts: string;
48
+ } | {
49
+ type: 'bracket.entry_filled';
50
+ bracketId: BracketId;
51
+ symbol: string;
52
+ ts: string;
53
+ } | {
54
+ type: 'bracket.attached';
55
+ bracketId: BracketId;
56
+ symbol: string;
57
+ latencyMs: number;
58
+ ts: string;
59
+ } | {
60
+ type: 'bracket.attach_failed';
61
+ bracketId: BracketId;
62
+ symbol: string;
63
+ error: string;
64
+ retryCount: number;
65
+ ts: string;
66
+ } | {
67
+ type: 'bracket.sla_breach';
68
+ bracketId: BracketId;
69
+ symbol: string;
70
+ latencyMs: number;
71
+ ts: string;
72
+ } | {
73
+ type: 'bracket.triggered';
74
+ bracketId: BracketId;
75
+ symbol: string;
76
+ side: 'sl' | 'tp';
77
+ ts: string;
78
+ } | {
79
+ type: 'bracket.cancelled';
80
+ bracketId: BracketId;
81
+ symbol: string;
82
+ reason: string;
83
+ ts: string;
84
+ } | {
85
+ type: 'bracket.orphan_detected';
86
+ bracketId: BracketId;
87
+ symbol: string;
88
+ ts: string;
89
+ } | {
90
+ type: 'bracket.orphan_cancelled';
91
+ bracketId: BracketId;
92
+ symbol: string;
93
+ ts: string;
94
+ } | {
95
+ type: 'bracket.modified';
96
+ bracketId: BracketId;
97
+ symbol: string;
98
+ field: 'stop' | 'target';
99
+ oldValue: number;
100
+ newValue: number;
101
+ ts: string;
102
+ };
@@ -0,0 +1,8 @@
1
+ // Bracket order types — shared across BracketManager, BracketLedger,
2
+ // BracketReconciler, and the telemetry pusher.
3
+ //
4
+ // A "bracket" is the pair of exchange-side protective orders attached to a
5
+ // live position: a STOP_MARKET (stop-loss) and a TAKE_PROFIT_MARKET
6
+ // (take-profit), both reduceOnly + closePosition. One entry → one bracketId
7
+ // → three clientOrderIds (entry/stop/target).
8
+ export {};
@@ -0,0 +1,62 @@
1
+ import type { BinancePrivateApi } from '../ccxt/binance-private.js';
2
+ interface TransferFetcher {
3
+ fetchTransfers(startMs: number, endMs: number): Promise<Array<{
4
+ tranId: string;
5
+ time: number;
6
+ amount: number;
7
+ asset: string;
8
+ }>>;
9
+ }
10
+ export interface DepositTrackerOptions {
11
+ /** Earliest transfer time to consider (ms). Defaults to `Date.now()`. */
12
+ sessionStartMs?: number;
13
+ /** Minimum gap between polls (ms). Transfers (deposits/withdrawals/spot↔
14
+ * futures moves) are RARE and only affect the cosmetic Day-P&L anchor —
15
+ * never a safety path. `fetchTransfers` is 30 Binance weight; at the old
16
+ * 30s default that was ~60 weight/min PERMANENT on an IP budget already
17
+ * pinned at the pacer ceiling 24/7 (2026-05-16 RCA). 5 min cuts it to
18
+ * ~6 weight/min; the only cost is a capital flow taking ≤5 min to be
19
+ * subtracted from Day P&L. Default: 5 min. */
20
+ minPollIntervalMs?: number;
21
+ /** Currencies to sum as quote (1:1 with USD assumed). Anything else is
22
+ * logged and ignored. */
23
+ quoteCurrencies?: readonly string[];
24
+ /** Injectable clock for tests. */
25
+ now?: () => number;
26
+ }
27
+ export declare class DepositTracker {
28
+ private api;
29
+ private seenTranIds;
30
+ /** Last SUCCESSFUL poll — anchors the fetch window so transfers that land
31
+ * between a success and a run of failures are still covered by the next
32
+ * successful poll. */
33
+ private lastPollMs;
34
+ /** Last poll ATTEMPT (success or failure) — drives the throttle. Advancing
35
+ * this on failure too is what stops a sustained income-endpoint outage
36
+ * from retrying the 30-weight call on every balance cycle (~seconds
37
+ * apart) instead of once per minPollIntervalMs. */
38
+ private lastAttemptMs;
39
+ private sessionStartMs;
40
+ private netSinceSessionStart;
41
+ private readonly minPollIntervalMs;
42
+ private readonly quoteCurrencies;
43
+ private readonly now;
44
+ constructor(api: TransferFetcher | BinancePrivateApi, opts?: DepositTrackerOptions);
45
+ /** Reset the tracker — used when the agent re-anchors the session NAV for
46
+ * any reason (date rollover, operator-triggered reset). */
47
+ reset(sessionStartMs?: number): void;
48
+ getSessionStartMs(): number;
49
+ /** Net quote-currency transfers into (positive) or out of (negative) the
50
+ * futures wallet since the tracker was armed. */
51
+ getNetTransfers(): number;
52
+ /**
53
+ * Check the exchange for new transfers. Returns the delta (signed) that
54
+ * happened since the last poll — callers should add this to their session
55
+ * NAV anchor so the new wallet level doesn't register as trading P&L.
56
+ *
57
+ * The fetch is throttled by `minPollIntervalMs`; rapid calls return 0 without
58
+ * hitting the API.
59
+ */
60
+ refresh(): Promise<number>;
61
+ }
62
+ export {};
@@ -0,0 +1,97 @@
1
+ // DepositTracker — detects wallet-transfer deltas (spot↔futures moves,
2
+ // deposits, withdrawals) and feeds them to the enricher so capital flows
3
+ // are subtracted from Day P&L instead of being mistaken for trading gains.
4
+ import { logger } from '../logger.js';
5
+ const TAG = 'deposit-tracker';
6
+ export class DepositTracker {
7
+ api;
8
+ seenTranIds = new Set();
9
+ /** Last SUCCESSFUL poll — anchors the fetch window so transfers that land
10
+ * between a success and a run of failures are still covered by the next
11
+ * successful poll. */
12
+ lastPollMs;
13
+ /** Last poll ATTEMPT (success or failure) — drives the throttle. Advancing
14
+ * this on failure too is what stops a sustained income-endpoint outage
15
+ * from retrying the 30-weight call on every balance cycle (~seconds
16
+ * apart) instead of once per minPollIntervalMs. */
17
+ lastAttemptMs;
18
+ sessionStartMs;
19
+ netSinceSessionStart = 0;
20
+ minPollIntervalMs;
21
+ quoteCurrencies;
22
+ now;
23
+ constructor(api, opts = {}) {
24
+ this.api = api;
25
+ this.now = opts.now ?? (() => Date.now());
26
+ this.sessionStartMs = opts.sessionStartMs ?? this.now();
27
+ this.lastPollMs = this.sessionStartMs;
28
+ this.lastAttemptMs = this.sessionStartMs;
29
+ this.minPollIntervalMs = opts.minPollIntervalMs ?? 300_000;
30
+ this.quoteCurrencies = opts.quoteCurrencies ?? ['USDT', 'USDC'];
31
+ }
32
+ /** Reset the tracker — used when the agent re-anchors the session NAV for
33
+ * any reason (date rollover, operator-triggered reset). */
34
+ reset(sessionStartMs) {
35
+ this.seenTranIds.clear();
36
+ this.sessionStartMs = sessionStartMs ?? this.now();
37
+ this.lastPollMs = this.sessionStartMs;
38
+ this.lastAttemptMs = this.sessionStartMs;
39
+ this.netSinceSessionStart = 0;
40
+ }
41
+ getSessionStartMs() {
42
+ return this.sessionStartMs;
43
+ }
44
+ /** Net quote-currency transfers into (positive) or out of (negative) the
45
+ * futures wallet since the tracker was armed. */
46
+ getNetTransfers() {
47
+ return this.netSinceSessionStart;
48
+ }
49
+ /**
50
+ * Check the exchange for new transfers. Returns the delta (signed) that
51
+ * happened since the last poll — callers should add this to their session
52
+ * NAV anchor so the new wallet level doesn't register as trading P&L.
53
+ *
54
+ * The fetch is throttled by `minPollIntervalMs`; rapid calls return 0 without
55
+ * hitting the API.
56
+ */
57
+ async refresh() {
58
+ const now = this.now();
59
+ // Throttle on the last ATTEMPT (not the last success) — otherwise a
60
+ // sustained fetch failure bypasses the throttle entirely and every
61
+ // balance cycle re-fires the 30-weight income call into the outage.
62
+ if (now - this.lastAttemptMs < this.minPollIntervalMs)
63
+ return 0;
64
+ this.lastAttemptMs = now;
65
+ // A small lookback overlap guards against clock skew between our host and
66
+ // Binance's edge — duplicate entries get filtered via seenTranIds. The
67
+ // window anchors on the last SUCCESS so transfers that landed during a
68
+ // run of failed polls are still picked up.
69
+ const start = Math.max(this.sessionStartMs, this.lastPollMs - 5_000);
70
+ let entries;
71
+ try {
72
+ entries = await this.api.fetchTransfers(start, now);
73
+ }
74
+ catch (err) {
75
+ logger.warn(TAG, `Transfer poll failed: ${err instanceof Error ? err.message : String(err)}`);
76
+ return 0;
77
+ }
78
+ this.lastPollMs = now;
79
+ let delta = 0;
80
+ for (const e of entries) {
81
+ if (!e.tranId || this.seenTranIds.has(e.tranId))
82
+ continue;
83
+ this.seenTranIds.add(e.tranId);
84
+ if (this.quoteCurrencies.includes(e.asset)) {
85
+ delta += e.amount;
86
+ }
87
+ else if (e.amount !== 0) {
88
+ logger.warn(TAG, `Non-quote transfer ignored: ${e.amount} ${e.asset} (tranId=${e.tranId})`);
89
+ }
90
+ }
91
+ if (delta !== 0) {
92
+ this.netSinceSessionStart += delta;
93
+ logger.info(TAG, `Capital flow detected: ${delta > 0 ? '+' : ''}${delta.toFixed(4)} (session total: ${this.netSinceSessionStart.toFixed(4)})`);
94
+ }
95
+ return delta;
96
+ }
97
+ }
@@ -0,0 +1,32 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { IExchangeAdapter } from '../exchange-adapter.js';
3
+ export interface EmergencyStep {
4
+ description: string;
5
+ status: 'pending' | 'done' | 'failed';
6
+ error?: string;
7
+ }
8
+ export interface EmergencyProgress {
9
+ action: 'kill' | 'flatten';
10
+ status: 'in_progress' | 'completed' | 'failed' | 'partial';
11
+ steps: EmergencyStep[];
12
+ startedAt: string;
13
+ completedAt?: string;
14
+ }
15
+ export declare class EmergencyControls extends EventEmitter {
16
+ private adapter;
17
+ private isHedgeMode;
18
+ constructor(adapter: IExchangeAdapter, isHedgeMode?: boolean);
19
+ /**
20
+ * Kill switch: cancel all open orders on exchange.
21
+ * Retries up to 3 times on transient failure.
22
+ */
23
+ kill(): Promise<EmergencyProgress>;
24
+ /**
25
+ * Flatten: close all positions at market.
26
+ * Loop-verifies: re-fetches positions after close, retries remainder.
27
+ * Uses reduceOnly to prevent accidental position reversal.
28
+ */
29
+ flatten(): Promise<EmergencyProgress>;
30
+ private closePositionWithRetry;
31
+ private emitProgress;
32
+ }
@@ -0,0 +1,226 @@
1
+ // Emergency controls for live trading — kill switch and flatten with retry logic.
2
+ // These NEVER have a readiness gate — they must work in ALL adapter states.
3
+ import { EventEmitter } from 'node:events';
4
+ import { logger, formatError } from '../logger.js';
5
+ const TAG = 'emergency';
6
+ const MAX_RETRIES = 3;
7
+ const RETRY_DELAY_MS = 1_000;
8
+ const MAX_FLATTEN_ATTEMPTS = 3;
9
+ export class EmergencyControls extends EventEmitter {
10
+ adapter;
11
+ isHedgeMode;
12
+ constructor(adapter, isHedgeMode = false) {
13
+ super();
14
+ this.adapter = adapter;
15
+ this.isHedgeMode = isHedgeMode;
16
+ }
17
+ /**
18
+ * Kill switch: cancel all open orders on exchange.
19
+ * Retries up to 3 times on transient failure.
20
+ */
21
+ async kill() {
22
+ const progress = {
23
+ action: 'kill',
24
+ status: 'in_progress',
25
+ steps: [],
26
+ startedAt: new Date().toISOString(),
27
+ };
28
+ this.emitProgress(progress);
29
+ logger.warn(TAG, '🔴 KILL SWITCH ACTIVATED');
30
+ try {
31
+ // Fetch current open orders
32
+ const openOrders = await this.adapter.getOpenOrders();
33
+ if (openOrders.length === 0) {
34
+ progress.steps.push({ description: 'No open orders to cancel', status: 'done' });
35
+ progress.status = 'completed';
36
+ progress.completedAt = new Date().toISOString();
37
+ this.emitProgress(progress);
38
+ logger.info(TAG, 'Kill: no open orders');
39
+ return progress;
40
+ }
41
+ progress.steps.push({ description: `Cancelling ${openOrders.length} orders...`, status: 'pending' });
42
+ this.emitProgress(progress);
43
+ // Cancel all with retry
44
+ let success = false;
45
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
46
+ try {
47
+ await this.adapter.cancelAllOrders();
48
+ success = true;
49
+ break;
50
+ }
51
+ catch (err) {
52
+ logger.warn(TAG, `Kill attempt ${attempt}/${MAX_RETRIES} failed: ${formatError(err)}`);
53
+ if (attempt < MAX_RETRIES) {
54
+ await sleep(RETRY_DELAY_MS);
55
+ }
56
+ }
57
+ }
58
+ if (success) {
59
+ progress.steps[progress.steps.length - 1].status = 'done';
60
+ progress.steps[progress.steps.length - 1].description = `${openOrders.length} orders cancelled`;
61
+ progress.status = 'completed';
62
+ }
63
+ else {
64
+ progress.steps[progress.steps.length - 1].status = 'failed';
65
+ progress.steps[progress.steps.length - 1].error = 'Failed after 3 retries';
66
+ progress.steps.push({
67
+ description: 'MANUAL INTERVENTION REQUIRED — cancel orders manually on Binance',
68
+ status: 'failed',
69
+ });
70
+ progress.status = 'failed';
71
+ }
72
+ progress.completedAt = new Date().toISOString();
73
+ this.emitProgress(progress);
74
+ return progress;
75
+ }
76
+ catch (err) {
77
+ logger.error(TAG, `Kill switch error: ${formatError(err)}`);
78
+ progress.status = 'failed';
79
+ progress.steps.push({
80
+ description: `MANUAL INTERVENTION REQUIRED: ${formatError(err)}`,
81
+ status: 'failed',
82
+ error: formatError(err),
83
+ });
84
+ progress.completedAt = new Date().toISOString();
85
+ this.emitProgress(progress);
86
+ return progress;
87
+ }
88
+ }
89
+ /**
90
+ * Flatten: close all positions at market.
91
+ * Loop-verifies: re-fetches positions after close, retries remainder.
92
+ * Uses reduceOnly to prevent accidental position reversal.
93
+ */
94
+ async flatten() {
95
+ const progress = {
96
+ action: 'flatten',
97
+ status: 'in_progress',
98
+ steps: [],
99
+ startedAt: new Date().toISOString(),
100
+ };
101
+ this.emitProgress(progress);
102
+ logger.warn(TAG, '🟠 FLATTEN ALL ACTIVATED');
103
+ try {
104
+ for (let attempt = 1; attempt <= MAX_FLATTEN_ATTEMPTS; attempt++) {
105
+ // Fresh snapshot of positions from exchange. getPositionsOrNull()
106
+ // returns null when the fetch FAILED (429 / weight-paced / transient)
107
+ // — position state is UNKNOWN, NOT "flat". Treating a failed fetch as
108
+ // "all closed" would make the single most safety-critical control
109
+ // report a green "Flatten complete" to the operator mid-crisis while
110
+ // positions are still live on Binance. Never declare success on null;
111
+ // skip this attempt's close work and retry (final verification below
112
+ // delivers an honest partial/MANUAL-INTERVENTION verdict if it
113
+ // persists). Same null≠empty contract as closePosition/reconciler.
114
+ const positions = await this.adapter.getPositionsOrNull();
115
+ if (positions === null) {
116
+ logger.warn(TAG, `Flatten attempt ${attempt}/${MAX_FLATTEN_ATTEMPTS}: position fetch failed (exchange data unavailable) — cannot confirm flat, retrying`);
117
+ continue;
118
+ }
119
+ if (positions.length === 0) {
120
+ progress.steps.push({ description: 'All positions closed', status: 'done' });
121
+ progress.status = 'completed';
122
+ progress.completedAt = new Date().toISOString();
123
+ this.emitProgress(progress);
124
+ logger.info(TAG, 'Flatten complete — no positions remaining');
125
+ return progress;
126
+ }
127
+ if (attempt > 1) {
128
+ logger.warn(TAG, `Flatten retry ${attempt}/${MAX_FLATTEN_ATTEMPTS}: ${positions.length} positions still open`);
129
+ }
130
+ // Close each position
131
+ for (const position of positions) {
132
+ const step = {
133
+ description: `Closing ${position.symbol} ${position.side} ${position.contracts}`,
134
+ status: 'pending',
135
+ };
136
+ progress.steps.push(step);
137
+ this.emitProgress(progress);
138
+ try {
139
+ await this.closePositionWithRetry(position);
140
+ step.status = 'done';
141
+ step.description = `Closed ${position.symbol} ${position.side} ${position.contracts}`;
142
+ }
143
+ catch (err) {
144
+ step.status = 'failed';
145
+ step.error = formatError(err);
146
+ logger.error(TAG, `Failed to close ${position.symbol}: ${formatError(err)}`);
147
+ }
148
+ this.emitProgress(progress);
149
+ }
150
+ }
151
+ // Final verification. A null here means we exhausted all attempts
152
+ // without ever getting a trustworthy snapshot — we CANNOT confirm the
153
+ // account is flat, so we must NOT report `completed`. Surface it as
154
+ // partial + MANUAL INTERVENTION (operator must check Binance directly)
155
+ // rather than the false-green that motivated this fix.
156
+ const remaining = await this.adapter.getPositionsOrNull();
157
+ if (remaining === null) {
158
+ progress.status = 'partial';
159
+ progress.steps.push({
160
+ description: 'MANUAL INTERVENTION REQUIRED — could not verify positions are closed (exchange data unavailable); check Binance directly',
161
+ status: 'failed',
162
+ });
163
+ logger.error(TAG, 'Flatten verification failed: position fetch unavailable — flat state UNCONFIRMED');
164
+ }
165
+ else if (remaining.length > 0) {
166
+ progress.status = 'partial';
167
+ progress.steps.push({
168
+ description: `MANUAL INTERVENTION REQUIRED — ${remaining.length} positions still open on Binance`,
169
+ status: 'failed',
170
+ });
171
+ logger.error(TAG, `Flatten incomplete: ${remaining.length} positions remain`);
172
+ }
173
+ else {
174
+ progress.status = 'completed';
175
+ }
176
+ progress.completedAt = new Date().toISOString();
177
+ this.emitProgress(progress);
178
+ return progress;
179
+ }
180
+ catch (err) {
181
+ logger.error(TAG, `Flatten error: ${formatError(err)}`);
182
+ progress.status = 'failed';
183
+ progress.steps.push({
184
+ description: `MANUAL INTERVENTION REQUIRED: ${formatError(err)}`,
185
+ status: 'failed',
186
+ error: formatError(err),
187
+ });
188
+ progress.completedAt = new Date().toISOString();
189
+ this.emitProgress(progress);
190
+ return progress;
191
+ }
192
+ }
193
+ // ---- Internal ----
194
+ async closePositionWithRetry(position) {
195
+ const closeSide = position.side === 'long' ? 'sell' : 'buy';
196
+ const options = { emergency: true };
197
+ if (this.isHedgeMode) {
198
+ options.positionSide = position.side === 'long' ? 'LONG' : 'SHORT';
199
+ }
200
+ else {
201
+ options.reduceOnly = true;
202
+ }
203
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
204
+ try {
205
+ return await this.adapter.createOrder(position.symbol, closeSide, 'market', position.contracts, undefined, undefined, options);
206
+ }
207
+ catch (err) {
208
+ logger.warn(TAG, `Close ${position.symbol} attempt ${attempt}/${MAX_RETRIES}: ${formatError(err)}`);
209
+ if (attempt < MAX_RETRIES) {
210
+ await sleep(RETRY_DELAY_MS);
211
+ }
212
+ else {
213
+ throw err;
214
+ }
215
+ }
216
+ }
217
+ // TypeScript: unreachable, but satisfies return type
218
+ throw new Error(`Failed to close ${position.symbol} after ${MAX_RETRIES} attempts`);
219
+ }
220
+ emitProgress(progress) {
221
+ this.emit('progress', progress);
222
+ }
223
+ }
224
+ function sleep(ms) {
225
+ return new Promise(resolve => setTimeout(resolve, ms));
226
+ }
@@ -0,0 +1,12 @@
1
+ export type ErrorAction = 'report' | 'wait_and_retry' | 'check_and_retry' | 'fix_precision' | 'sync_time';
2
+ export interface ParsedExchangeError {
3
+ code: number | null;
4
+ message: string;
5
+ action: ErrorAction;
6
+ retryAfterMs: number;
7
+ isRetryable: boolean;
8
+ isNetworkError: boolean;
9
+ humanMessage: string;
10
+ }
11
+ /** Parse a CCXT/Binance error into a structured response. */
12
+ export declare function parseExchangeError(err: unknown): ParsedExchangeError;
@@ -0,0 +1,130 @@
1
+ // Exchange error handler — maps Binance error codes to human-readable messages
2
+ // and determines appropriate recovery action.
3
+ import { logger } from '../logger.js';
4
+ const TAG = 'exchange-error';
5
+ /** Known Binance Futures error codes and their human-readable meanings. */
6
+ const BINANCE_ERRORS = {
7
+ // Order errors
8
+ [-2019]: { message: 'Insufficient margin', action: 'report' },
9
+ [-1111]: { message: 'Precision error (lot size or tick size mismatch)', action: 'fix_precision' },
10
+ [-4131]: { message: 'Order below minimum notional value', action: 'report' },
11
+ [-1015]: { message: 'Too many orders — exchange rate limit hit', action: 'wait_and_retry' },
12
+ [-2022]: { message: 'Stop price invalid (must be above/below current price depending on side)', action: 'report' },
13
+ [-1021]: { message: 'Timestamp sync error — clock drift', action: 'sync_time' },
14
+ [-1003]: { message: 'IP rate limit exceeded', action: 'wait_and_retry' },
15
+ [-1010]: { message: 'Order not found', action: 'report' },
16
+ [-2021]: { message: 'Order would immediately trigger', action: 'report' },
17
+ [-4164]: { message: 'Order price exceeds MAX_PRICE filter', action: 'report' },
18
+ [-4165]: { message: 'Order price below MIN_PRICE filter', action: 'report' },
19
+ [-4028]: { message: 'Reduce only rejected — no open position', action: 'report' },
20
+ [-4005]: { message: 'Order quantity too small', action: 'report' },
21
+ [-4003]: { message: 'Order quantity exceeds MAX_QTY', action: 'report' },
22
+ [-4014]: { message: 'Too many open orders', action: 'report' },
23
+ // System errors
24
+ [-1000]: { message: 'Unknown exchange error', action: 'report' },
25
+ [-1001]: { message: 'Exchange internal error — try again later', action: 'wait_and_retry' },
26
+ [-1002]: { message: 'Unauthorized — API key invalid or insufficient permissions', action: 'report' },
27
+ [-1006]: { message: 'Unexpected response from exchange', action: 'wait_and_retry' },
28
+ [-1007]: { message: 'Exchange request timeout', action: 'check_and_retry' },
29
+ [-1014]: { message: 'Unsupported order combination', action: 'report' },
30
+ [-1016]: { message: 'Service shutting down', action: 'report' },
31
+ [-1020]: { message: 'Unsupported operation', action: 'report' },
32
+ };
33
+ /** Parse a CCXT/Binance error into a structured response. */
34
+ export function parseExchangeError(err) {
35
+ const error = err;
36
+ // Extract error code (CCXT wraps Binance errors)
37
+ let code = null;
38
+ let rawMessage = '';
39
+ if (typeof error === 'object' && error !== null) {
40
+ // CCXT errors often have a message like "binance {...}" or structured fields
41
+ rawMessage = String(error.message ?? error);
42
+ // Try to extract Binance error code from message
43
+ const codeMatch = rawMessage.match(/"code"\s*:\s*(-?\d+)/);
44
+ if (codeMatch) {
45
+ code = parseInt(codeMatch[1], 10);
46
+ }
47
+ // Also check if CCXT provides it directly
48
+ if (typeof error.code === 'number') {
49
+ code = error.code;
50
+ }
51
+ }
52
+ else {
53
+ rawMessage = String(err);
54
+ }
55
+ // Check for network errors
56
+ const isNetworkError = isNetworkRelated(rawMessage);
57
+ if (isNetworkError) {
58
+ return {
59
+ code: null,
60
+ message: rawMessage,
61
+ action: 'check_and_retry',
62
+ retryAfterMs: 1000,
63
+ isRetryable: true,
64
+ isNetworkError: true,
65
+ humanMessage: 'Network error communicating with exchange. Checking if order went through...',
66
+ };
67
+ }
68
+ // Look up known error code
69
+ if (code !== null && BINANCE_ERRORS[code]) {
70
+ const known = BINANCE_ERRORS[code];
71
+ const retryAfterMs = known.action === 'wait_and_retry' ? (code === -1015 || code === -1003 ? 60_000 : 5_000) : 0;
72
+ return {
73
+ code,
74
+ message: rawMessage,
75
+ action: known.action,
76
+ retryAfterMs,
77
+ isRetryable: known.action !== 'report',
78
+ isNetworkError: false,
79
+ humanMessage: buildHumanMessage(code, known.message, rawMessage),
80
+ };
81
+ }
82
+ // Unknown error
83
+ logger.warn(TAG, `Unknown exchange error (code=${code}): ${rawMessage}`);
84
+ return {
85
+ code,
86
+ message: rawMessage,
87
+ action: 'report',
88
+ retryAfterMs: 0,
89
+ isRetryable: false,
90
+ isNetworkError: false,
91
+ humanMessage: `Exchange error: ${rawMessage}`,
92
+ };
93
+ }
94
+ /** Check if an error is network-related (timeout, connection reset, etc.). */
95
+ function isNetworkRelated(message) {
96
+ const patterns = [
97
+ 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND',
98
+ 'network', 'timeout', 'socket hang up', 'EPIPE',
99
+ 'EAI_AGAIN', 'EHOSTUNREACH', 'fetch failed',
100
+ ];
101
+ const lower = message.toLowerCase();
102
+ return patterns.some(p => lower.includes(p.toLowerCase()));
103
+ }
104
+ /** Build a human-friendly error message. */
105
+ function buildHumanMessage(code, knownMessage, rawMessage) {
106
+ switch (code) {
107
+ case -2019: {
108
+ // Try to extract balance info from raw message
109
+ const match = rawMessage.match(/(\d+\.?\d*)\s*USDT/);
110
+ if (match)
111
+ return `Insufficient margin: available ${match[1]} USDT`;
112
+ return 'Insufficient margin — check your balance';
113
+ }
114
+ case -4131:
115
+ return `Order too small: below this symbol's minimum notional value. Check exchange info for the exact minimum.`;
116
+ case -1111:
117
+ return `Precision error: amount or price has too many decimal places. Order will be auto-corrected on retry.`;
118
+ case -1015:
119
+ case -1003:
120
+ return `Exchange rate limit hit. Wait 60 seconds before retrying.`;
121
+ case -2022:
122
+ return `Invalid stop price. For buy stops: stop must be above current price. For sell stops: stop must be below.`;
123
+ case -1021:
124
+ return `Clock out of sync with exchange. Syncing time automatically...`;
125
+ case -4028:
126
+ return `Cannot reduce position — no open position found. The position may have already been closed.`;
127
+ default:
128
+ return `Exchange error ${code}: ${knownMessage}`;
129
+ }
130
+ }