@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
package/logger.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { LogLevel } from '@reefclaw/shared';
2
+ export { logger, setLogLevel, formatError } from '@reefclaw/shared';
package/logger.js ADDED
@@ -0,0 +1,2 @@
1
+ // Re-export from @reefclaw/shared (canonical source of truth).
2
+ export { logger, setLogLevel, formatError } from '@reefclaw/shared';
package/mfe.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ export interface MfeInputs {
2
+ side: 'long' | 'short';
3
+ entryPrice: number;
4
+ /** Stop at fill, frozen. If undefined, MFE-in-R is undefined. */
5
+ originalStopPrice?: number;
6
+ /** Latest mark observation. */
7
+ markPrice: number;
8
+ /** Prior peak from the last update (undefined on first call). */
9
+ priorPeakPrice?: number;
10
+ }
11
+ export interface MfeOutputs {
12
+ /** New peak price after observing this mark. */
13
+ mfePeakPrice: number;
14
+ /** Peak in R-multiples. Undefined if originalStopPrice missing. */
15
+ mfeR?: number;
16
+ /** Give-back ratio in [0, 1+]. Undefined if peak is not favourable yet. */
17
+ giveBackRatio?: number;
18
+ }
19
+ /** One-shot update: given current state + new mark, return the new peak,
20
+ * R-multiple, and give-back. Pure function — caller persists the result. */
21
+ export declare function updateMfe(input: MfeInputs): MfeOutputs;
package/mfe.js ADDED
@@ -0,0 +1,68 @@
1
+ // MFE (Max Favourable Excursion) tracking — pure math, no I/O.
2
+ //
3
+ // Component C of POSITION_MANAGEMENT_PLAN.md. The agent today has no felt
4
+ // sense of "I am leaking edge" because the heartbeat snapshot doesn't carry
5
+ // MFE or give-back. These helpers compute both from the entry-time stop and
6
+ // the running peak, and are wired into the paper simulator's mark-tick path
7
+ // and the live adapter's getPositions decoration.
8
+ //
9
+ // Conventions (confirmed with operator 2026-04-27):
10
+ // - 1R is denominated against the ORIGINAL stop at fill, frozen for the life
11
+ // of the position. Ratcheting the live stopPrice does not change R.
12
+ // - Without an originalStopPrice (e.g. paper position opened without a stop),
13
+ // `mfeR` and `giveBackRatio` are reported as `undefined` rather than
14
+ // fabricated from a default risk unit.
15
+ // - Sign is always favourable: long positions track the highest mark seen,
16
+ // short positions track the lowest. `mfeR` is non-negative.
17
+ //
18
+ // Edge cases handled:
19
+ // - Pre-entry tick (entry price not yet observed): caller should not invoke.
20
+ // - Adverse-only excursion (MFE never positive): `giveBackRatio` is left
21
+ // `undefined` because the ratio is meaningless when peak <= 0.
22
+ // - Tied peak (mark equals prior peak): treated as the peak; give-back stays 0.
23
+ /** Compute the favourable distance (always >= 0) between mark and entry. */
24
+ function favourableDelta(side, entry, mark) {
25
+ return side === 'long' ? mark - entry : entry - mark;
26
+ }
27
+ /** True if `candidate` is at least as favourable as `incumbent` for this side. */
28
+ function atLeastAsFavourable(side, candidate, incumbent) {
29
+ return side === 'long' ? candidate >= incumbent : candidate <= incumbent;
30
+ }
31
+ /** One-shot update: given current state + new mark, return the new peak,
32
+ * R-multiple, and give-back. Pure function — caller persists the result. */
33
+ export function updateMfe(input) {
34
+ const { side, entryPrice, originalStopPrice, markPrice, priorPeakPrice } = input;
35
+ if (!Number.isFinite(markPrice) || !Number.isFinite(entryPrice)) {
36
+ // Caller error — return prior peak unchanged.
37
+ return {
38
+ mfePeakPrice: priorPeakPrice ?? entryPrice,
39
+ mfeR: undefined,
40
+ giveBackRatio: undefined,
41
+ };
42
+ }
43
+ // Seed peak to entry on first observation, then ratchet only in the
44
+ // favourable direction.
45
+ const seedPeak = priorPeakPrice ?? entryPrice;
46
+ const newPeak = atLeastAsFavourable(side, markPrice, seedPeak) ? markPrice : seedPeak;
47
+ // Compute R if we have an original stop. R denominator is the at-fill
48
+ // distance from entry to stop; undefined or non-positive denominator means
49
+ // we can't report R.
50
+ const rDenominator = originalStopPrice !== undefined && Number.isFinite(originalStopPrice)
51
+ ? Math.abs(entryPrice - originalStopPrice)
52
+ : 0;
53
+ if (rDenominator <= 0) {
54
+ return { mfePeakPrice: newPeak, mfeR: undefined, giveBackRatio: undefined };
55
+ }
56
+ const peakFavourableDelta = favourableDelta(side, entryPrice, newPeak);
57
+ const currentFavourableDelta = favourableDelta(side, entryPrice, markPrice);
58
+ // mfeR is always non-negative — clamp adverse-only positions to 0.
59
+ const mfeR = Math.max(0, peakFavourableDelta / rDenominator);
60
+ // Give-back is only meaningful once peak has actually moved into profit.
61
+ // Negative peak (price went the wrong way and never recovered) → undefined.
62
+ let giveBackRatio;
63
+ if (peakFavourableDelta > 0) {
64
+ const givenBack = peakFavourableDelta - currentFavourableDelta;
65
+ giveBackRatio = Math.max(0, givenBack / peakFavourableDelta);
66
+ }
67
+ return { mfePeakPrice: newPeak, mfeR, giveBackRatio };
68
+ }
@@ -0,0 +1 @@
1
+ export { validateModeTransition, modeRequiresCredentials, type ModeTransitionResult, } from '@reefclaw/shared';
@@ -0,0 +1,3 @@
1
+ // Mode ladder — re-exported from @reefclaw/shared so plugin + skill + webapp
2
+ // all use the same source of truth. See shared/src/trading-mode.ts.
3
+ export { validateModeTransition, modeRequiresCredentials, } from '@reefclaw/shared';
@@ -0,0 +1,71 @@
1
+ import type { IExchangeAdapter } from '../exchange-adapter.js';
2
+ import type { ExchangeConfig, TradingMode } from '../types.js';
3
+ import type { ExchangeSimulator } from '../simulator/exchange-simulator.js';
4
+ import type { PaperMarketFeed } from '../simulator/paper-market-feed.js';
5
+ import { PositionWatcher } from '../live/stop-watcher.js';
6
+ export interface MicroLiveConfig {
7
+ sizeCapPercent?: number;
8
+ maxPositionUSDT?: number;
9
+ }
10
+ export interface BuildAdapterInput {
11
+ mode: TradingMode;
12
+ exchange: ExchangeConfig | null;
13
+ microLive?: MicroLiveConfig;
14
+ simulator: ExchangeSimulator;
15
+ }
16
+ /** Pure-ish factory: builds an adapter for the requested mode.
17
+ * Falls back to PaperAdapter if a live mode is requested without credentials —
18
+ * callers should pre-validate via `modeRequiresCredentials`, but this
19
+ * defense-in-depth prevents a crash if validation is bypassed. */
20
+ export declare function buildAdapter(input: BuildAdapterInput): IExchangeAdapter;
21
+ /**
22
+ * Mutable runtime holder. Held once per plugin registration.
23
+ *
24
+ * `adapterBox.adapter` is the indirection point: tool closures read it at
25
+ * execute time so a swap is visible without re-registering tools.
26
+ */
27
+ export declare class PluginRuntime {
28
+ /** Box object so existing `const adapterDeps = { adapter }` patterns still
29
+ * work via `adapterDeps.adapter = runtime.adapterBox.adapter`. In practice
30
+ * we pass the runtime itself and read `runtime.adapter` at call sites. */
31
+ private _adapter;
32
+ private _mode;
33
+ private _stopWatcher;
34
+ private readonly simulator;
35
+ /** Paper-only background market-data feed (refreshes simulator marks so
36
+ * stops fire and NAV/uPnL stay live for non-dashboard symbols). Lives for
37
+ * the runtime's lifetime; started in paper, stopped in live. */
38
+ private readonly _marketFeed;
39
+ /** Reconnect is serialized — a second caller waits for the first to finish
40
+ * so we never tear down an adapter that's mid-rebuild. */
41
+ private reconnectInFlight;
42
+ constructor(initial: {
43
+ adapter: IExchangeAdapter;
44
+ mode: TradingMode;
45
+ simulator: ExchangeSimulator;
46
+ stopWatcher?: PositionWatcher | null;
47
+ marketFeed?: PaperMarketFeed | null;
48
+ });
49
+ get adapter(): IExchangeAdapter;
50
+ get mode(): TradingMode;
51
+ get stopWatcher(): PositionWatcher | null;
52
+ get marketFeed(): PaperMarketFeed | null;
53
+ /**
54
+ * Swap the current adapter for a new one built from `next`. The old
55
+ * adapter's background loops are stopped before the new one is wired in.
56
+ *
57
+ * `deps.adapterDeps` is the tool-dependency object used by trading tools —
58
+ * its `.adapter` reference is updated in place so tools see the new adapter
59
+ * without needing re-registration.
60
+ */
61
+ reconnect(next: {
62
+ mode: TradingMode;
63
+ exchange: ExchangeConfig | null;
64
+ microLive?: MicroLiveConfig;
65
+ }, deps: {
66
+ adapterDeps: {
67
+ adapter: IExchangeAdapter;
68
+ };
69
+ }): Promise<void>;
70
+ private doReconnect;
71
+ }
@@ -0,0 +1,153 @@
1
+ // Plugin runtime holder — the mutable state that survives across a
2
+ // credential/mode swap. The rest of index.ts closes over this object
3
+ // instead of over `let` bindings, so a reconnect can atomically:
4
+ //
5
+ // 1. Stop the live adapter's background loops (order poller + reconciler)
6
+ // 2. Stop the stop-watcher
7
+ // 3. Build a new adapter from updated config
8
+ // 4. Swap `runtime.adapter` in place (closures see the new value)
9
+ // 5. Start a new stop-watcher pointed at the new adapter
10
+ //
11
+ // Doing this without dropping tool registrations or rebuilding the whole
12
+ // plugin is what makes the dashboard "Reconnect Exchange" button feel
13
+ // instant — no gateway restart required.
14
+ import { PaperAdapter } from '../paper-adapter.js';
15
+ import { LiveAdapter } from '../live/live-adapter.js';
16
+ import { PositionWatcher } from '../live/stop-watcher.js';
17
+ import { loadBracketMode } from '../config/brackets-config.js';
18
+ import { logger, formatError } from '../logger.js';
19
+ const TAG = 'plugin-runtime';
20
+ /** Pure-ish factory: builds an adapter for the requested mode.
21
+ * Falls back to PaperAdapter if a live mode is requested without credentials —
22
+ * callers should pre-validate via `modeRequiresCredentials`, but this
23
+ * defense-in-depth prevents a crash if validation is bypassed. */
24
+ export function buildAdapter(input) {
25
+ const { mode, exchange, microLive, simulator } = input;
26
+ if (mode === 'PAPER' || mode === 'SHADOW') {
27
+ // Shadow mode uses a paper adapter for execution; the separate
28
+ // ShadowTracker wraps a BinancePrivateApi for real-balance comparison.
29
+ return new PaperAdapter(simulator);
30
+ }
31
+ if (!exchange) {
32
+ logger.warn(TAG, `${mode} requested without credentials — falling back to PAPER`);
33
+ return new PaperAdapter(simulator);
34
+ }
35
+ // Read bracket-mode from plugin-config at adapter build time so a config
36
+ // flip + "Reconnect Exchange" pattern picks up the new mode on the next swap.
37
+ const bracketMode = loadBracketMode();
38
+ return new LiveAdapter(exchange, mode, microLive, bracketMode);
39
+ }
40
+ /**
41
+ * Mutable runtime holder. Held once per plugin registration.
42
+ *
43
+ * `adapterBox.adapter` is the indirection point: tool closures read it at
44
+ * execute time so a swap is visible without re-registering tools.
45
+ */
46
+ export class PluginRuntime {
47
+ /** Box object so existing `const adapterDeps = { adapter }` patterns still
48
+ * work via `adapterDeps.adapter = runtime.adapterBox.adapter`. In practice
49
+ * we pass the runtime itself and read `runtime.adapter` at call sites. */
50
+ _adapter;
51
+ _mode;
52
+ _stopWatcher = null;
53
+ simulator;
54
+ /** Paper-only background market-data feed (refreshes simulator marks so
55
+ * stops fire and NAV/uPnL stay live for non-dashboard symbols). Lives for
56
+ * the runtime's lifetime; started in paper, stopped in live. */
57
+ _marketFeed;
58
+ /** Reconnect is serialized — a second caller waits for the first to finish
59
+ * so we never tear down an adapter that's mid-rebuild. */
60
+ reconnectInFlight = null;
61
+ constructor(initial) {
62
+ this._adapter = initial.adapter;
63
+ this._mode = initial.mode;
64
+ this.simulator = initial.simulator;
65
+ this._stopWatcher = initial.stopWatcher ?? null;
66
+ this._marketFeed = initial.marketFeed ?? null;
67
+ }
68
+ get adapter() { return this._adapter; }
69
+ get mode() { return this._mode; }
70
+ get stopWatcher() { return this._stopWatcher; }
71
+ get marketFeed() { return this._marketFeed; }
72
+ /**
73
+ * Swap the current adapter for a new one built from `next`. The old
74
+ * adapter's background loops are stopped before the new one is wired in.
75
+ *
76
+ * `deps.adapterDeps` is the tool-dependency object used by trading tools —
77
+ * its `.adapter` reference is updated in place so tools see the new adapter
78
+ * without needing re-registration.
79
+ */
80
+ async reconnect(next, deps) {
81
+ // Serialize concurrent reconnects.
82
+ if (this.reconnectInFlight) {
83
+ await this.reconnectInFlight;
84
+ }
85
+ const run = this.doReconnect(next, deps);
86
+ this.reconnectInFlight = run.finally(() => {
87
+ this.reconnectInFlight = null;
88
+ });
89
+ await run;
90
+ }
91
+ async doReconnect(next, deps) {
92
+ logger.info(TAG, `Reconnect: ${this._mode} → ${next.mode}`);
93
+ // 1. Stop old stop-watcher.
94
+ if (this._stopWatcher) {
95
+ try {
96
+ this._stopWatcher.stop();
97
+ }
98
+ catch (err) {
99
+ logger.warn(TAG, `stopWatcher.stop failed: ${formatError(err)}`);
100
+ }
101
+ this._stopWatcher = null;
102
+ }
103
+ // 2. Tear down old live adapter background loops.
104
+ const old = this._adapter;
105
+ if (old instanceof LiveAdapter) {
106
+ try {
107
+ old.shutdown();
108
+ }
109
+ catch (err) {
110
+ logger.warn(TAG, `old adapter shutdown failed: ${formatError(err)}`);
111
+ }
112
+ }
113
+ // 3. Build the new adapter.
114
+ const fresh = buildAdapter({
115
+ mode: next.mode,
116
+ exchange: next.exchange,
117
+ microLive: next.microLive,
118
+ simulator: this.simulator,
119
+ });
120
+ // 4. Fire async init for live adapters (non-blocking — readiness flips
121
+ // INIT_PENDING → READY/DEGRADED/BLOCKED on its own).
122
+ if (fresh instanceof LiveAdapter) {
123
+ fresh.initialize().catch(err => {
124
+ logger.error(TAG, `New adapter init failed: ${formatError(err)}`);
125
+ });
126
+ }
127
+ // 5. Atomically publish the new adapter.
128
+ // Note: `next.mode` is the operator's intent (e.g. SHADOW), which is
129
+ // not always the same as `fresh.mode` — PaperAdapter is used for both
130
+ // PAPER and SHADOW execution, and reports its mode as 'PAPER'. The
131
+ // runtime tracks the user-facing mode separately so tools that branch
132
+ // on SHADOW (e.g. risk summary) keep working.
133
+ this._adapter = fresh;
134
+ this._mode = next.mode;
135
+ deps.adapterDeps.adapter = fresh;
136
+ // 6. Start a new stop-watcher bound to the new adapter.
137
+ const watcher = new PositionWatcher(fresh);
138
+ watcher.start();
139
+ this._stopWatcher = watcher;
140
+ // 7. Paper market feed follows the mode: run it when the new adapter is
141
+ // paper (so marks/stops/NAV stay live), stop it in live (the live
142
+ // adapter sources marks from Binance directly). Idempotent both ways.
143
+ if (this._marketFeed) {
144
+ if (fresh.isLive) {
145
+ this._marketFeed.stop();
146
+ }
147
+ else {
148
+ this._marketFeed.start();
149
+ }
150
+ }
151
+ logger.info(TAG, `Reconnect complete: now in ${this._mode} mode (readiness=${fresh.readiness})`);
152
+ }
153
+ }
@@ -0,0 +1,94 @@
1
+ {
2
+ "id": "reefclaw-paper-trading",
3
+ "name": "ReefClaw Paper Trading",
4
+ "version": "0.1.0",
5
+ "description": "Paper trading plugin with real Binance market data and simulated execution. No API keys required.",
6
+ "author": "ReefClaw",
7
+ "activation": {
8
+ "onStartup": true
9
+ },
10
+ "contracts": {
11
+ "tools": [
12
+ "fetch_ticker",
13
+ "fetch_ohlcv",
14
+ "fetch_balance",
15
+ "fetch_positions",
16
+ "fetch_open_orders",
17
+ "create_order",
18
+ "cancel_order",
19
+ "cancel_all_orders",
20
+ "close_position",
21
+ "modify_stop",
22
+ "modify_target",
23
+ "attach_brackets",
24
+ "audit_bracket_protection",
25
+ "record_position_reviews",
26
+ "get_my_recent_reviews",
27
+ "get_relevant_learnings",
28
+ "get_my_mined_patterns",
29
+ "get_my_proposed_learnings",
30
+ "propose_learning",
31
+ "query_review_outcomes",
32
+ "get_market_structure",
33
+ "get_crypto_metrics",
34
+ "get_volume_analysis",
35
+ "get_risk_summary",
36
+ "get_market_intel",
37
+ "get_regime",
38
+ "get_signals",
39
+ "get_analytics",
40
+ "get_orderbook",
41
+ "get_volume_profile",
42
+ "get_liquidation_levels",
43
+ "get_trade_feedback",
44
+ "get_trade_flow",
45
+ "score_setup",
46
+ "get_sizing",
47
+ "check_position_health",
48
+ "get_risk_scenario",
49
+ "get_session_review",
50
+ "get_backtest",
51
+ "get_pattern_scan",
52
+ "save_strategy",
53
+ "list_strategies",
54
+ "toggle_strategy",
55
+ "scan_pairs",
56
+ "get_setup_detail",
57
+ "query_trades",
58
+ "get_agent_profile",
59
+ "get_cvd",
60
+ "get_market_breadth",
61
+ "get_basis",
62
+ "get_cascade_risk",
63
+ "get_sentiment",
64
+ "get_divergences",
65
+ "get_liquidation_pulse",
66
+ "get_resting_liquidity",
67
+ "get_funding_context",
68
+ "set_exchange_credentials",
69
+ "set_trading_mode",
70
+ "test_exchange_credentials",
71
+ "clear_exchange_credentials",
72
+ "get_bracket_config",
73
+ "set_bracket_requirement"
74
+ ]
75
+ },
76
+ "configSchema": {
77
+ "type": "object",
78
+ "properties": {
79
+ "startingBalance": {
80
+ "type": "number",
81
+ "default": 10000,
82
+ "description": "Starting USDT balance for paper trading"
83
+ },
84
+ "symbol": {
85
+ "type": "string",
86
+ "default": "BTC/USDT",
87
+ "description": "Default trading symbol"
88
+ }
89
+ }
90
+ },
91
+ "skills": [
92
+ "./skills"
93
+ ]
94
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@reefclaw/openclaw-plugin",
3
+ "version": "0.1.0",
4
+ "description": "ReefClaw trading plugin for OpenClaw — paper trading with real Binance market data, plus the ReefClaw dashboard connector (supervised by OpenClaw, no service manager needed). Install: /plugins install npm:@reefclaw/openclaw-plugin",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "openclaw": {
8
+ "extensions": ["./index.js"]
9
+ },
10
+ "files": [
11
+ "**/*"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "dependencies": {
17
+ "@reefclaw/shared": "0.1.0",
18
+ "ccxt": "4.5.37",
19
+ "json5": "2.2.3",
20
+ "ws": "8.19.0"
21
+ },
22
+ "scripts": {
23
+ "build": "node scripts/assemble.mjs"
24
+ },
25
+ "license": "MIT",
26
+ "homepage": "https://reefclaw.com"
27
+ }
@@ -0,0 +1,24 @@
1
+ import type { IExchangeAdapter, AdapterReadiness, OrderOptions } from './exchange-adapter.js';
2
+ import type { ExchangeSimulator } from './simulator/exchange-simulator.js';
3
+ import type { CcxtOrder, CcxtBalance, CcxtPosition } from './types.js';
4
+ import type { PositionMetadata, CloseReason } from './simulator/types.js';
5
+ export declare class PaperAdapter implements IExchangeAdapter {
6
+ private simulator;
7
+ readonly mode: "PAPER";
8
+ readonly isLive = false;
9
+ readonly readiness: AdapterReadiness;
10
+ constructor(simulator: ExchangeSimulator);
11
+ /** Get the underlying simulator (for paper-only operations like ATR, orderbook, state). */
12
+ getSimulator(): ExchangeSimulator;
13
+ createOrder(symbol: string, side: 'buy' | 'sell', type: 'market' | 'limit', amount: number, price?: number, metadata?: PositionMetadata, _options?: OrderOptions): Promise<CcxtOrder>;
14
+ cancelOrder(orderId: string, _symbol?: string): Promise<CcxtOrder>;
15
+ cancelAllOrders(symbol?: string): Promise<CcxtOrder[]>;
16
+ closePosition(symbol: string, closeReason?: CloseReason): Promise<CcxtOrder>;
17
+ getBalance(): Promise<CcxtBalance>;
18
+ getPositions(symbol?: string): Promise<CcxtPosition[]>;
19
+ /** Paper has no exchange fetch to fail — position state is always known. */
20
+ getPositionsOrNull(symbol?: string): Promise<CcxtPosition[] | null>;
21
+ getOpenOrders(symbol?: string): Promise<CcxtOrder[]>;
22
+ fetchOrder(orderId: string, _symbol?: string): Promise<CcxtOrder | null>;
23
+ getLastPrice(symbol: string): Promise<number | null>;
24
+ }
@@ -0,0 +1,91 @@
1
+ // PaperAdapter — wraps ExchangeSimulator to implement IExchangeAdapter.
2
+ // No behavioral changes — just a thin async wrapper for uniform interface.
3
+ export class PaperAdapter {
4
+ simulator;
5
+ mode = 'PAPER';
6
+ isLive = false;
7
+ readiness = 'READY'; // Paper is always ready
8
+ constructor(simulator) {
9
+ this.simulator = simulator;
10
+ }
11
+ /** Get the underlying simulator (for paper-only operations like ATR, orderbook, state). */
12
+ getSimulator() {
13
+ return this.simulator;
14
+ }
15
+ async createOrder(symbol, side, type, amount, price, metadata, _options) {
16
+ // Paper mode ignores OrderOptions (clientOrderId, reduceOnly, positionSide)
17
+ return this.simulator.createOrder(symbol, side, type, amount, price, metadata);
18
+ }
19
+ async cancelOrder(orderId, _symbol) {
20
+ return this.simulator.cancelOrder(orderId);
21
+ }
22
+ async cancelAllOrders(symbol) {
23
+ return this.simulator.cancelAllOrders(symbol);
24
+ }
25
+ async closePosition(symbol, closeReason) {
26
+ return this.simulator.closePosition(symbol, closeReason);
27
+ }
28
+ async getBalance() {
29
+ return this.simulator.getBalance();
30
+ }
31
+ async getPositions(symbol) {
32
+ return this.simulator.getPositions(symbol);
33
+ }
34
+ /** Paper has no exchange fetch to fail — position state is always known. */
35
+ async getPositionsOrNull(symbol) {
36
+ return this.simulator.getPositions(symbol);
37
+ }
38
+ async getOpenOrders(symbol) {
39
+ return this.simulator.getOpenOrders(symbol);
40
+ }
41
+ async fetchOrder(orderId, _symbol) {
42
+ // Search open orders, then trade history for filled/canceled orders
43
+ const state = this.simulator.getState();
44
+ const openOrder = state.openOrders.find(o => o.id === orderId);
45
+ if (openOrder) {
46
+ return {
47
+ id: openOrder.id,
48
+ symbol: openOrder.symbol,
49
+ side: openOrder.side,
50
+ type: openOrder.type,
51
+ status: openOrder.status,
52
+ amount: openOrder.amount,
53
+ filled: openOrder.filled,
54
+ remaining: openOrder.amount - openOrder.filled,
55
+ average: openOrder.average,
56
+ price: openOrder.price,
57
+ cost: openOrder.cost,
58
+ fee: openOrder.fee,
59
+ timestamp: new Date(openOrder.createdAt).getTime(),
60
+ datetime: openOrder.createdAt,
61
+ timeInForce: 'GTC',
62
+ };
63
+ }
64
+ // Check trade history for filled orders
65
+ const trade = state.tradeHistory.find(t => t.orderId === orderId);
66
+ if (trade) {
67
+ return {
68
+ id: trade.orderId,
69
+ symbol: trade.symbol,
70
+ side: trade.side,
71
+ type: 'market', // Trades don't store order type; market is the common close path
72
+ status: 'closed',
73
+ amount: trade.quantity,
74
+ filled: trade.quantity,
75
+ remaining: 0,
76
+ average: trade.exitPrice,
77
+ price: trade.exitPrice,
78
+ cost: trade.exitPrice * trade.quantity,
79
+ fee: { cost: trade.fee, currency: 'USDT' },
80
+ timestamp: new Date(trade.closedAt).getTime(),
81
+ datetime: trade.closedAt,
82
+ timeInForce: 'GTC',
83
+ };
84
+ }
85
+ return null;
86
+ }
87
+ async getLastPrice(symbol) {
88
+ const ticker = this.simulator.getLastTicker(symbol);
89
+ return ticker?.last ?? null;
90
+ }
91
+ }
@@ -0,0 +1,42 @@
1
+ import type { SimulatorState } from '../simulator/types.js';
2
+ export declare class StateManager {
3
+ private readonly statePath;
4
+ private saveTimer;
5
+ private pendingState;
6
+ constructor(pluginId: string, baseDir?: string);
7
+ getStatePath(): string;
8
+ /** Quarantine a state file that exists but won't parse, instead of letting
9
+ * the caller silently overwrite it with a fresh default (which would wipe
10
+ * the positions/orders/realized-PnL ledger). The corrupt bytes are renamed
11
+ * to `<state>.corrupt-<ts>` so an operator can recover them. Returns true if
12
+ * a corrupt file was quarantined. */
13
+ private quarantineCorruptSync;
14
+ /** Load state from disk. Returns null when the file is MISSING (fresh start)
15
+ * OR when it exists-but-is-unreadable (in which case the corrupt bytes are
16
+ * quarantined first — never silently discarded). The two cases are
17
+ * distinguished in the log; both yield null so the caller seeds a default,
18
+ * but a corrupt file is preserved as `<state>.corrupt-<ts>`. */
19
+ load(): Promise<SimulatorState | null>;
20
+ /** Save state atomically (write to a UNIQUE temp, rename to final). A unique
21
+ * temp name (pid + uuid) is essential under the two-process model: both the
22
+ * agent and gateway processes wire stateChanged→save, and a shared
23
+ * `<state>.tmp` let their concurrent writes interleave into a torn file that
24
+ * then parses as garbage on next boot. With a per-write temp, the worst case
25
+ * degrades to a harmless last-writer-wins rename of two COMPLETE files. */
26
+ save(state: SimulatorState): Promise<void>;
27
+ /**
28
+ * Debounced save — coalesces rapid mutations into one write per second.
29
+ * Call this on every state change; it will save at most once per second.
30
+ */
31
+ debouncedSave(state: SimulatorState): void;
32
+ /** Flush any pending debounced save immediately. */
33
+ flush(): Promise<void>;
34
+ /** Load state or create default if not found. */
35
+ loadOrDefault(startingBalance: number, quoteCurrency: string): Promise<SimulatorState>;
36
+ /** Synchronous load — for use in synchronous plugin register(). Same
37
+ * missing-vs-corrupt distinction as load(): a corrupt file is quarantined
38
+ * (preserved), never silently overwritten by the default the caller seeds. */
39
+ loadSync(): SimulatorState | null;
40
+ /** Synchronous load-or-create-default — for use in synchronous plugin register(). */
41
+ loadOrDefaultSync(startingBalance: number, quoteCurrency: string): SimulatorState;
42
+ }