@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,62 @@
1
+ import type { MarketContext, StrategyConfig, SignalCondition, Direction, SignalEvent, StrategyState } from '@reefclaw/shared';
2
+ /** A locally-evaluated signal — the same shape the agent gets from the central engine. */
3
+ export interface LocalSignal {
4
+ symbol: string;
5
+ strategy: string;
6
+ direction: Direction;
7
+ regime: string;
8
+ regimeConfidence: number;
9
+ /** Fraction of conditions met, 0..1. */
10
+ confluenceScore: number;
11
+ conditions: SignalCondition[];
12
+ entryZone: {
13
+ low: number;
14
+ high: number;
15
+ };
16
+ stopLevel: number;
17
+ targets: number[];
18
+ /** ms epoch the signal fired. */
19
+ firedAt: number;
20
+ }
21
+ export interface EvaluatorParams {
22
+ /** Min fraction of conditions met to fire (intel default 1.0). */
23
+ confluenceThreshold: number;
24
+ /** Floor applied to each strategy's minConfidence (intel: per-user trading param). */
25
+ regimeConfidenceFloor: number;
26
+ /** Min ms between identical (strategy, symbol, direction) signals. */
27
+ cooldownMs: number;
28
+ }
29
+ export declare const DEFAULT_EVALUATOR_PARAMS: EvaluatorParams;
30
+ /**
31
+ * Evaluates the user's strategies locally against incoming fact-contexts.
32
+ *
33
+ * Single-tenant (one plugin == one user), so cooldown state is keyed
34
+ * `strategy:symbol:direction` — there is no user dimension, unlike the central
35
+ * engine. Higher-timeframe tick gating lives inside the shared strategy-adapter
36
+ * (module-level), so a strategy only re-evaluates once per closed primary-tf bar.
37
+ */
38
+ export declare class LocalStrategyEvaluator {
39
+ /** `strategy:symbol:direction` → last fired ms. */
40
+ private readonly cooldowns;
41
+ /**
42
+ * Full per-symbol snapshot — every strategy's state (whether or not it fired)
43
+ * plus the signals that fired this evaluation. A faithful mirror of
44
+ * intelligence/src/signals/engine.ts:evaluateSymbol (regime gate → state;
45
+ * evaluate → confluence → state; confluence + cooldown → SignalEvent), minus
46
+ * the intel-only side effects (DB persist, mission create, degradation gate).
47
+ *
48
+ * `now` is injected for determinism/testability — pass `Date.now()` live.
49
+ */
50
+ evaluateSnapshot(ctx: MarketContext, strategies: StrategyConfig[], now: number, params?: EvaluatorParams): Promise<{
51
+ states: StrategyState[];
52
+ signals: SignalEvent[];
53
+ }>;
54
+ /**
55
+ * Convenience wrapper that returns only the fired signals as LocalSignal[].
56
+ * Thin layer over evaluateSnapshot so there is a single source of evaluation
57
+ * logic.
58
+ */
59
+ evaluate(ctx: MarketContext, strategies: StrategyConfig[], now: number, params?: EvaluatorParams): Promise<LocalSignal[]>;
60
+ /** Clear cooldown state (e.g. when the strategy set changes). */
61
+ reset(): void;
62
+ }
@@ -0,0 +1,127 @@
1
+ // Plugin-side strategy evaluator — facts-out (docs/FACTS_OUT_ARCHITECTURE.md) step 3b.
2
+ //
3
+ // Runs strategy evaluation LOCALLY in the plugin instead of calling the central
4
+ // intel signal engine. It runs the SAME shared evaluation core (adaptStrategy →
5
+ // conditions + direction/entry/stop rules; PRs #42–44) that intel runs, then
6
+ // applies the engine's thin orchestration — regime gate → confluence → cooldown
7
+ // — to produce the same signal shape the agent receives today. This is a
8
+ // faithful mirror of intelligence/src/signals/engine.ts:evaluateSymbol, minus
9
+ // the intel-only concerns (DB persistence, mission creation, per-user passes,
10
+ // degradation gate).
11
+ //
12
+ // NOT wired into the heartbeat yet — the SSE fact stream (step 4) feeds it.
13
+ // Built + tested standalone; rollout will be shadow (compare vs the central
14
+ // engine) → enforce.
15
+ import { adaptStrategy } from '../signals/strategy-adapter.js';
16
+ export const DEFAULT_EVALUATOR_PARAMS = {
17
+ confluenceThreshold: 1.0,
18
+ regimeConfidenceFloor: 0,
19
+ cooldownMs: 300_000,
20
+ };
21
+ /**
22
+ * Evaluates the user's strategies locally against incoming fact-contexts.
23
+ *
24
+ * Single-tenant (one plugin == one user), so cooldown state is keyed
25
+ * `strategy:symbol:direction` — there is no user dimension, unlike the central
26
+ * engine. Higher-timeframe tick gating lives inside the shared strategy-adapter
27
+ * (module-level), so a strategy only re-evaluates once per closed primary-tf bar.
28
+ */
29
+ export class LocalStrategyEvaluator {
30
+ /** `strategy:symbol:direction` → last fired ms. */
31
+ cooldowns = new Map();
32
+ /**
33
+ * Full per-symbol snapshot — every strategy's state (whether or not it fired)
34
+ * plus the signals that fired this evaluation. A faithful mirror of
35
+ * intelligence/src/signals/engine.ts:evaluateSymbol (regime gate → state;
36
+ * evaluate → confluence → state; confluence + cooldown → SignalEvent), minus
37
+ * the intel-only side effects (DB persist, mission create, degradation gate).
38
+ *
39
+ * `now` is injected for determinism/testability — pass `Date.now()` live.
40
+ */
41
+ async evaluateSnapshot(ctx, strategies, now, params = DEFAULT_EVALUATOR_PARAMS) {
42
+ const states = [];
43
+ const signals = [];
44
+ for (const config of strategies) {
45
+ const def = adaptStrategy(config);
46
+ // Regime gate (engine.ts): effective floor is the LOWER of the strategy's
47
+ // own minConfidence and the user's regimeConfidenceFloor.
48
+ const effectiveMinConf = Math.min(def.minConfidence, params.regimeConfidenceFloor || def.minConfidence);
49
+ if (!def.regimeWhitelist.includes(ctx.regime) || ctx.regimeConfidence < effectiveMinConf) {
50
+ states.push({
51
+ strategy: config.name,
52
+ direction: null,
53
+ regimeGated: true,
54
+ regimeRequired: def.regimeWhitelist,
55
+ currentRegime: ctx.regime,
56
+ regimeConfidence: ctx.regimeConfidence,
57
+ confluenceScore: 0,
58
+ conditions: [],
59
+ });
60
+ continue;
61
+ }
62
+ const evaluation = await def.evaluate(ctx);
63
+ const metCount = evaluation.conditions.filter((c) => c.met).length;
64
+ const confluenceScore = evaluation.conditions.length > 0
65
+ ? metCount / evaluation.conditions.length
66
+ : 0;
67
+ states.push({
68
+ strategy: config.name,
69
+ direction: evaluation.direction,
70
+ regimeGated: false,
71
+ regimeRequired: def.regimeWhitelist,
72
+ currentRegime: ctx.regime,
73
+ regimeConfidence: ctx.regimeConfidence,
74
+ confluenceScore,
75
+ conditions: evaluation.conditions,
76
+ });
77
+ // Confluence gate (engine.ts:339) + cooldown (engine.ts:342-346).
78
+ if (confluenceScore >= params.confluenceThreshold && evaluation.direction && evaluation.trade) {
79
+ const cooldownKey = `${config.name}:${ctx.symbol}:${evaluation.direction}`;
80
+ const lastFired = this.cooldowns.get(cooldownKey) ?? 0;
81
+ if (now - lastFired > params.cooldownMs) {
82
+ this.cooldowns.set(cooldownKey, now);
83
+ signals.push({
84
+ signalId: `local-${ctx.symbol}-${config.name}-${now}`,
85
+ timestamp: new Date(now).toISOString(),
86
+ symbol: ctx.symbol,
87
+ strategy: config.name,
88
+ direction: evaluation.direction,
89
+ regime: ctx.regime,
90
+ regimeConfidence: ctx.regimeConfidence,
91
+ confluenceScore,
92
+ conditions: evaluation.conditions,
93
+ entryZone: evaluation.trade.entryZone,
94
+ stopLevel: evaluation.trade.stopLevel,
95
+ targets: evaluation.trade.targets,
96
+ });
97
+ }
98
+ }
99
+ }
100
+ return { states, signals };
101
+ }
102
+ /**
103
+ * Convenience wrapper that returns only the fired signals as LocalSignal[].
104
+ * Thin layer over evaluateSnapshot so there is a single source of evaluation
105
+ * logic.
106
+ */
107
+ async evaluate(ctx, strategies, now, params = DEFAULT_EVALUATOR_PARAMS) {
108
+ const { signals } = await this.evaluateSnapshot(ctx, strategies, now, params);
109
+ return signals.map((s) => ({
110
+ symbol: s.symbol,
111
+ strategy: s.strategy,
112
+ direction: s.direction,
113
+ regime: s.regime,
114
+ regimeConfidence: s.regimeConfidence,
115
+ confluenceScore: s.confluenceScore,
116
+ conditions: s.conditions,
117
+ entryZone: s.entryZone,
118
+ stopLevel: s.stopLevel,
119
+ targets: s.targets,
120
+ firedAt: now,
121
+ }));
122
+ }
123
+ /** Clear cooldown state (e.g. when the strategy set changes). */
124
+ reset() {
125
+ this.cooldowns.clear();
126
+ }
127
+ }
@@ -0,0 +1,54 @@
1
+ import { type IntelApiDeps } from '../tools/intel-api.js';
2
+ export interface MicrostructureBlock {
3
+ symbol: string;
4
+ /** Smoothed (not yet — placeholder for v2) band imbalance at ±0.5 %.
5
+ * Computed as bid_usd / (bid_usd + ask_usd) ∈ [0, 1]. 0.5 = balanced;
6
+ * > 0.5 leans bid (more support); < 0.5 leans ask (more resistance). */
7
+ restingBandImbalance05?: number;
8
+ /** Reserved for v2 — true when imbalance held its sign for ≥ 3 samples.
9
+ * Always false until the EMA layer lands. */
10
+ persisted?: boolean;
11
+ /** Rolling-60 s cross-symbol cascade classification. */
12
+ cascadePulse60s?: 'quiet' | 'elevated' | 'active_cascade';
13
+ /** Coverage in basis-points-of-percent ×100 (e.g. 150 = 1.5 %).
14
+ * The webapp persists `coveragePctAtReview` as percent units, so the
15
+ * ingest layer divides this by 100. */
16
+ coverageBpc?: number;
17
+ /** Net dominant-side reading from the pulse stream. */
18
+ dominantSide?: 'long' | 'short' | 'balanced';
19
+ /** Epoch ms of the source intel reading. */
20
+ updatedAt?: number;
21
+ }
22
+ export interface MicrostructureAssembler {
23
+ /** Returns null when the heartbeatPush flag is off or both upstream calls
24
+ * failed. Partial results (one endpoint succeeded) yield a partial block. */
25
+ forSymbol(symbol: string): Promise<MicrostructureBlock | null>;
26
+ /** Returns a Map<symbol, block> for a set of symbols. Null entries are
27
+ * omitted from the map. */
28
+ forSymbols(symbols: string[]): Promise<Map<string, MicrostructureBlock>>;
29
+ }
30
+ /** No-op fallback used when no intel deps are wired. Kept for tests + as a
31
+ * safety net during the activation ladder. */
32
+ export declare class StubMicrostructureAssembler implements MicrostructureAssembler {
33
+ forSymbol(_symbol: string): Promise<MicrostructureBlock | null>;
34
+ forSymbols(_symbols: string[]): Promise<Map<string, MicrostructureBlock>>;
35
+ }
36
+ export interface IntelMicrostructureAssemblerOptions {
37
+ intelDeps: IntelApiDeps;
38
+ /** Override the cache TTL — tests use 0 to disable. */
39
+ cacheTtlMs?: number;
40
+ /** Test seam — the heartbeatPush flag reader. Defaults to the live config. */
41
+ flagReader?: () => 'off' | 'on';
42
+ /** Test seam — Date.now(). */
43
+ now?: () => number;
44
+ }
45
+ export declare class IntelMicrostructureAssembler implements MicrostructureAssembler {
46
+ private readonly intelDeps;
47
+ private readonly cacheTtlMs;
48
+ private readonly flagReader;
49
+ private readonly now;
50
+ private readonly cache;
51
+ constructor(options: IntelMicrostructureAssemblerOptions);
52
+ forSymbol(symbol: string): Promise<MicrostructureBlock | null>;
53
+ forSymbols(symbols: string[]): Promise<Map<string, MicrostructureBlock>>;
54
+ }
@@ -0,0 +1,148 @@
1
+ // Microstructure heartbeat-push assembler.
2
+ //
3
+ // Builds a per-symbol microstructure context block (resting band imbalance,
4
+ // cascade pulse, banded coverage) that the `record_position_reviews` tool
5
+ // auto-attaches to each review row when `microstructure.heartbeatPush='on'`.
6
+ // Without this, the agent must explicitly pass the orderbook fields (or the
7
+ // review row's restingBandImbalance05 / cascadePulseAtReview / coveragePctAtReview
8
+ // columns stay NULL).
9
+ //
10
+ // Sources both feeds from the intelligence VPS in parallel:
11
+ // - GET /api/resting-liquidity/<symbol> → bands + coverage_pct
12
+ // - GET /api/liquidation-pulse?symbol=<sym> → classification + dominant side
13
+ //
14
+ // Returns `null` for a symbol when:
15
+ // - the heartbeatPush flag is off (legacy behaviour)
16
+ // - intel responds with `enabled: false` on BOTH endpoints (no upstream data)
17
+ // - both fetches fail (network error / timeout)
18
+ // A partial result (one endpoint succeeded) yields a partial block — fields
19
+ // from the failed call stay `undefined` rather than dropping the whole entry.
20
+ //
21
+ // Caching: per-symbol 30 s TTL. Heartbeats fire ~30 min apart, so the cache
22
+ // almost never serves a hit during normal operation; its purpose is to dedupe
23
+ // within-heartbeat repeat calls (e.g. an operator triggering a manual review
24
+ // while a scheduled heartbeat is mid-flight).
25
+ //
26
+ // See docs/MARKET_MICROSTRUCTURE_STREAMS.md (intel-side) and
27
+ // docs/POSITION_DECISION_JOURNAL_PLAN.md §11 (PR1 deferral #3).
28
+ import { logger, formatError } from '../logger.js';
29
+ import { loadMicrostructureHeartbeatPush } from '../config/position-review-config.js';
30
+ import { fetchIntelApi, enc } from '../tools/intel-api.js';
31
+ const TAG = 'microstructure-assembler';
32
+ const DEFAULT_CACHE_TTL_MS = 30_000;
33
+ /** Per-symbol concurrency cap to keep heartbeat round-trips bounded even on
34
+ * many-symbol portfolios. 8 in-flight at once is well under the intel HTTP
35
+ * pool budget; remaining symbols queue. */
36
+ const MAX_PARALLEL = 8;
37
+ /** No-op fallback used when no intel deps are wired. Kept for tests + as a
38
+ * safety net during the activation ladder. */
39
+ export class StubMicrostructureAssembler {
40
+ async forSymbol(_symbol) {
41
+ return null;
42
+ }
43
+ async forSymbols(_symbols) {
44
+ return new Map();
45
+ }
46
+ }
47
+ export class IntelMicrostructureAssembler {
48
+ intelDeps;
49
+ cacheTtlMs;
50
+ flagReader;
51
+ now;
52
+ cache = new Map();
53
+ constructor(options) {
54
+ this.intelDeps = options.intelDeps;
55
+ this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
56
+ this.flagReader = options.flagReader ?? loadMicrostructureHeartbeatPush;
57
+ this.now = options.now ?? (() => Date.now());
58
+ }
59
+ async forSymbol(symbol) {
60
+ if (this.flagReader() === 'off')
61
+ return null;
62
+ const ts = this.now();
63
+ const canonical = canonicalSymbol(symbol);
64
+ const cached = this.cache.get(canonical);
65
+ if (cached && cached.expiresAt > ts)
66
+ return cached.block;
67
+ // Map canonical "BTC/USDT" → intel "BTCUSDT".
68
+ const intelSymbol = canonical.replace('/', '');
69
+ const [restingResult, pulseResult] = await Promise.allSettled([
70
+ fetchIntelApi(`/api/resting-liquidity/${enc(intelSymbol)}`, this.intelDeps),
71
+ fetchIntelApi(`/api/liquidation-pulse?symbol=${enc(intelSymbol)}&window_seconds=60`, this.intelDeps),
72
+ ]);
73
+ const block = { symbol: canonical, updatedAt: ts };
74
+ let anySignal = false;
75
+ if (restingResult.status === 'fulfilled') {
76
+ const r = restingResult.value;
77
+ if (!isErr(r) && r.enabled === true) {
78
+ const bands = r.bands;
79
+ const band05 = bands?.['0.5'];
80
+ if (band05) {
81
+ const total = (Number(band05.bid_usd) || 0) + (Number(band05.ask_usd) || 0);
82
+ if (total > 0) {
83
+ block.restingBandImbalance05 = (Number(band05.bid_usd) || 0) / total;
84
+ anySignal = true;
85
+ }
86
+ }
87
+ if (typeof r.coverage_pct === 'number' && Number.isFinite(r.coverage_pct)) {
88
+ // Intel returns percent units (e.g. 1.5 for 1.5 %); we store as
89
+ // basis-points-of-percent ×100 to preserve the assembler convention.
90
+ block.coverageBpc = r.coverage_pct * 100;
91
+ anySignal = true;
92
+ }
93
+ }
94
+ }
95
+ else {
96
+ logger.warn(TAG, `resting-liquidity fetch failed for ${canonical}: ${formatError(restingResult.reason)}`);
97
+ }
98
+ if (pulseResult.status === 'fulfilled') {
99
+ const p = pulseResult.value;
100
+ if (!isErr(p) && p.enabled === true) {
101
+ const cls = p.classification;
102
+ if (cls === 'quiet' || cls === 'elevated' || cls === 'active_cascade') {
103
+ block.cascadePulse60s = cls;
104
+ anySignal = true;
105
+ }
106
+ const dom = p.dominantSide;
107
+ if (dom === 'long' || dom === 'short' || dom === 'balanced') {
108
+ block.dominantSide = dom;
109
+ anySignal = true;
110
+ }
111
+ }
112
+ }
113
+ else {
114
+ logger.warn(TAG, `liquidation-pulse fetch failed for ${canonical}: ${formatError(pulseResult.reason)}`);
115
+ }
116
+ block.persisted = false;
117
+ if (!anySignal)
118
+ return null;
119
+ if (this.cacheTtlMs > 0) {
120
+ this.cache.set(canonical, { block, expiresAt: ts + this.cacheTtlMs });
121
+ }
122
+ return block;
123
+ }
124
+ async forSymbols(symbols) {
125
+ if (this.flagReader() === 'off' || symbols.length === 0) {
126
+ return new Map();
127
+ }
128
+ const out = new Map();
129
+ // Bounded parallelism — chunked Promise.all with cap MAX_PARALLEL.
130
+ for (let i = 0; i < symbols.length; i += MAX_PARALLEL) {
131
+ const chunk = symbols.slice(i, i + MAX_PARALLEL);
132
+ const results = await Promise.all(chunk.map((s) => this.forSymbol(s)));
133
+ for (let j = 0; j < chunk.length; j++) {
134
+ const block = results[j];
135
+ if (block)
136
+ out.set(canonicalSymbol(chunk[j]), block);
137
+ }
138
+ }
139
+ return out;
140
+ }
141
+ }
142
+ function canonicalSymbol(s) {
143
+ // Strip the Binance settle suffix (`BTC/USDT:USDT` → `BTC/USDT`).
144
+ return s.replace(/:[A-Z]+$/, '');
145
+ }
146
+ function isErr(v) {
147
+ return typeof v.error === 'string';
148
+ }
@@ -0,0 +1,29 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { IExchangeAdapter } from '../exchange-adapter.js';
3
+ import type { CcxtOrder } from '../types.js';
4
+ export interface OrderPollEvent {
5
+ orderId: string;
6
+ symbol: string;
7
+ type: 'filled' | 'partial_fill' | 'canceled' | 'expired';
8
+ order: CcxtOrder;
9
+ previousFilled?: number;
10
+ }
11
+ export declare class OrderPoller extends EventEmitter {
12
+ private polling;
13
+ private safetyTimeouts;
14
+ private adapter;
15
+ private pollIntervalMs;
16
+ private lastKnownFilled;
17
+ constructor(adapter: IExchangeAdapter, pollIntervalMs?: number);
18
+ /** Start polling for an order. */
19
+ startPolling(orderId: string, symbol: string): void;
20
+ /** Stop polling for an order. */
21
+ stopPolling(orderId: string): void;
22
+ /** Stop all polling. */
23
+ stopAll(): void;
24
+ /** Get all actively polled order IDs. */
25
+ getPollingOrders(): string[];
26
+ /** Number of orders being polled. */
27
+ get activeCount(): number;
28
+ private processOrderUpdate;
29
+ }
@@ -0,0 +1,125 @@
1
+ // Order poller — polls exchange for order status on live limit orders.
2
+ // Detects fills, partial fills, and cancellations.
3
+ import { EventEmitter } from 'node:events';
4
+ import { logger, formatError } from '../logger.js';
5
+ const TAG = 'order-poller';
6
+ const DEFAULT_POLL_INTERVAL_MS = 5_000; // 5 seconds
7
+ const MAX_POLL_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours safety timeout
8
+ export class OrderPoller extends EventEmitter {
9
+ polling = new Map();
10
+ safetyTimeouts = new Map();
11
+ adapter;
12
+ pollIntervalMs;
13
+ // Track previous fill amounts for partial fill detection
14
+ lastKnownFilled = new Map();
15
+ constructor(adapter, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS) {
16
+ super();
17
+ this.adapter = adapter;
18
+ this.pollIntervalMs = pollIntervalMs;
19
+ }
20
+ /** Start polling for an order. */
21
+ startPolling(orderId, symbol) {
22
+ if (this.polling.has(orderId)) {
23
+ logger.warn(TAG, `Already polling order ${orderId}`);
24
+ return;
25
+ }
26
+ logger.info(TAG, `Start polling order ${orderId} (${symbol})`);
27
+ this.lastKnownFilled.set(orderId, 0);
28
+ const interval = setInterval(async () => {
29
+ try {
30
+ const order = await this.adapter.fetchOrder(orderId, symbol);
31
+ if (!order)
32
+ return; // API error — skip this cycle
33
+ this.processOrderUpdate(orderId, order);
34
+ }
35
+ catch (err) {
36
+ logger.warn(TAG, `Poll error for ${orderId}: ${formatError(err)}`);
37
+ }
38
+ }, this.pollIntervalMs);
39
+ this.polling.set(orderId, interval);
40
+ // Safety timeout: stop polling after 24h
41
+ const safetyTimeout = setTimeout(() => {
42
+ logger.warn(TAG, `Safety timeout reached for order ${orderId} — stopping poll`);
43
+ this.stopPolling(orderId);
44
+ }, MAX_POLL_DURATION_MS);
45
+ this.safetyTimeouts.set(orderId, safetyTimeout);
46
+ }
47
+ /** Stop polling for an order. */
48
+ stopPolling(orderId) {
49
+ const interval = this.polling.get(orderId);
50
+ if (interval) {
51
+ clearInterval(interval);
52
+ this.polling.delete(orderId);
53
+ }
54
+ const timeout = this.safetyTimeouts.get(orderId);
55
+ if (timeout) {
56
+ clearTimeout(timeout);
57
+ this.safetyTimeouts.delete(orderId);
58
+ }
59
+ this.lastKnownFilled.delete(orderId);
60
+ }
61
+ /** Stop all polling. */
62
+ stopAll() {
63
+ for (const orderId of this.polling.keys()) {
64
+ this.stopPolling(orderId);
65
+ }
66
+ }
67
+ /** Get all actively polled order IDs. */
68
+ getPollingOrders() {
69
+ return Array.from(this.polling.keys());
70
+ }
71
+ /** Number of orders being polled. */
72
+ get activeCount() {
73
+ return this.polling.size;
74
+ }
75
+ // ---- Internal ----
76
+ processOrderUpdate(orderId, order) {
77
+ const previousFilled = this.lastKnownFilled.get(orderId) ?? 0;
78
+ // CCXT's runtime status set is wider than CcxtOrder's compile-time union:
79
+ // Binance EXPIRED / EXPIRED_IN_MATCH surface as 'expired' (and 'rejected'
80
+ // exists on other venues). Compare on the runtime string so an unmodelled
81
+ // terminal status can never leave the poller running for the full 24h
82
+ // safety timeout with no terminal event ever emitted.
83
+ const status = String(order.status);
84
+ if (status === 'closed') {
85
+ // Fully filled
86
+ this.emit('order_event', {
87
+ orderId,
88
+ symbol: order.symbol,
89
+ type: 'filled',
90
+ order,
91
+ previousFilled,
92
+ });
93
+ this.stopPolling(orderId);
94
+ logger.info(TAG, `Order ${orderId} FILLED: ${order.filled} @ ${order.average}`);
95
+ }
96
+ else if (status !== 'open') {
97
+ // canceled / expired / rejected / any future non-open status — the
98
+ // order can never fill further; emit the matching terminal event and
99
+ // stop. Only a literal 'open' keeps the poll alive.
100
+ const type = status === 'expired' ? 'expired' : 'canceled';
101
+ this.emit('order_event', {
102
+ orderId,
103
+ symbol: order.symbol,
104
+ type,
105
+ order,
106
+ previousFilled,
107
+ });
108
+ this.stopPolling(orderId);
109
+ logger.info(TAG, `Order ${orderId} ${status.toUpperCase()} (filled: ${order.filled})`);
110
+ }
111
+ else if (order.filled > previousFilled) {
112
+ // Partial fill — still open but more filled
113
+ this.lastKnownFilled.set(orderId, order.filled);
114
+ this.emit('order_event', {
115
+ orderId,
116
+ symbol: order.symbol,
117
+ type: 'partial_fill',
118
+ order,
119
+ previousFilled,
120
+ });
121
+ logger.info(TAG, `Order ${orderId} PARTIAL FILL: ${previousFilled} → ${order.filled}`);
122
+ }
123
+ // else: no change, keep polling
124
+ }
125
+ }
@@ -0,0 +1,83 @@
1
+ export interface PositionStateEntry {
2
+ /** Canonical symbol form (no settle suffix). */
3
+ symbol: string;
4
+ /** Authoritative position-open timestamp (epoch ms). Used as the webapp's
5
+ * natural-key column alongside (userId, symbol). */
6
+ openedAt: number;
7
+ side: 'long' | 'short';
8
+ /** UUID returned from POST /api/internal/positions. May be undefined briefly
9
+ * between local detection and the first successful upsert. */
10
+ webappPositionId?: string;
11
+ /** exchangeTradeId of the opening fill (audit trail). */
12
+ openedFromExchangeTradeId?: string;
13
+ /** Last record_position_reviews call timestamp (epoch ms). Drives stale-gate. */
14
+ lastReviewAt?: number;
15
+ lastVerdict?: 'hold' | 'add_on' | 'close_recommended';
16
+ /** Last thesis_status from the most recent review. Drives the exit-gate's
17
+ * review-override check (see docs/EXIT_GATE_DESIGN.md §3). */
18
+ lastThesisStatus?: 'intact' | 'weakening' | 'invalidated' | 'evolving';
19
+ /** Remaining open contracts, tracked from opening fills (+) and reduce-only
20
+ * exit fills (−). Lets the auto-capture pipeline detect when a position goes
21
+ * flat via a bracket SL/TP or other reduce-only fill — the close-bypass path
22
+ * that otherwise never reaches close_position — and journal an exact close.
23
+ * Optional: entries opened before this field existed (or before a restart)
24
+ * are seeded from exchange truth at boot (seedRemainingContracts). When
25
+ * undefined we can't decide flatness from fills alone and defer to the
26
+ * reconciler/ALGO close-bypass backstop. */
27
+ remainingContracts?: number;
28
+ /** Cumulative Binance-exact realised PnL summed across the reduce-only exit
29
+ * fills since open, so the auto-journaled close carries the real PnL rather
30
+ * than a server-side estimate. */
31
+ realizedPnlAccum?: number;
32
+ createdAt: number;
33
+ updatedAt: number;
34
+ }
35
+ /** Test seam — lets tests override the clock for deterministic timestamps. */
36
+ export type NowFn = () => number;
37
+ export declare class PositionStateStore {
38
+ private readonly filePath;
39
+ private readonly dir;
40
+ private readonly now;
41
+ private entries;
42
+ constructor(pluginId?: string, opts?: {
43
+ now?: NowFn;
44
+ basePath?: string;
45
+ });
46
+ /** Insert or update a position. Caller is responsible for deciding when
47
+ * to call this — typically on first observation of a new position. */
48
+ upsert(entry: Omit<PositionStateEntry, 'createdAt' | 'updatedAt'> & {
49
+ createdAt?: number;
50
+ updatedAt?: number;
51
+ }): PositionStateEntry;
52
+ /** Set the webapp positionId after the upsert returns. Idempotent. */
53
+ setWebappId(symbol: string, webappPositionId: string): void;
54
+ /** Update review tracking after a successful record_position_reviews call.
55
+ * thesisStatus is optional for backwards compatibility — older callers that
56
+ * predate the exit-gate work pass only verdict; the gate falls back to
57
+ * PASS('inputs_unavailable_thesis_status') when the field isn't cached. */
58
+ recordReview(symbol: string, verdict: 'hold' | 'add_on' | 'close_recommended', reviewAt?: number, thesisStatus?: 'intact' | 'weakening' | 'invalidated' | 'evolving'): void;
59
+ /** Add opening contracts to the running size (first opening fill or a
60
+ * scale-in). Initialises the realised-PnL accumulator on first open. No-op
61
+ * if the entry doesn't exist yet (caller upserts the entry first). */
62
+ addOpenContracts(symbol: string, qty: number): void;
63
+ /** Seed remaining contracts from exchange truth at startup. Only sets when
64
+ * the size is currently unknown — never clobbers live fill-tracked state. */
65
+ seedRemainingContracts(symbol: string, qty: number): void;
66
+ /** Apply a reduce-only exit fill: decrement remaining contracts and sum the
67
+ * fill's realised PnL. Returns the updated entry so the caller can journal a
68
+ * close when the position is flat, or null when there's no tracked entry or
69
+ * the size is unknown (can't decide flatness — defer to the backstop). */
70
+ applyExitContracts(symbol: string, qty: number, realizedPnl: number): PositionStateEntry | null;
71
+ get(symbol: string): PositionStateEntry | undefined;
72
+ /** Returns all entries — used by the stale-gate to enumerate open positions. */
73
+ getAll(): PositionStateEntry[];
74
+ /** Drop an entry — called when the auto-capture pipeline observes the
75
+ * position has closed (size dropped to zero on the exchange). */
76
+ remove(symbol: string): boolean;
77
+ size(): number;
78
+ getFilePath(): string;
79
+ private loadOrInit;
80
+ private persist;
81
+ private validateFile;
82
+ private validateEntry;
83
+ }