@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,53 @@
1
+ import type { Order, Position, Wallet, Trade, OrderBookDepth, SimulationConfig, ExecutionQuality, PositionMetadata } from './types.js';
2
+ /** Result of filling an order */
3
+ export interface FillResult {
4
+ /** The filled order (status set to 'closed') */
5
+ order: Order;
6
+ /** Updated or new position (null if position was fully closed) */
7
+ position: Position | null;
8
+ /** Closed trade record (only when closing/reducing a position) */
9
+ trade: Trade | null;
10
+ /** Wallet balance changes: { currency: delta } */
11
+ walletDeltas: Record<string, number>;
12
+ /** Execution quality metrics (Phase 9a) */
13
+ executionQuality?: ExecutionQuality;
14
+ }
15
+ /**
16
+ * Compute simulated slippage for a market order (legacy mode).
17
+ * Returns basis points: random 0-2 bps, signed based on order side.
18
+ * BUY: positive slippage (pay more), SELL: negative slippage (receive less).
19
+ */
20
+ export declare function computeSlippage(side: 'buy' | 'sell'): number;
21
+ /**
22
+ * Apply slippage to a price.
23
+ * @param price Base price
24
+ * @param slippageBps Slippage in basis points (signed)
25
+ * @returns Adjusted price
26
+ */
27
+ export declare function applySlippage(price: number, slippageBps: number): number;
28
+ /**
29
+ * Parse the base and quote currencies from a symbol like "BTC/USDT".
30
+ */
31
+ export declare function parseSymbol(symbol: string): {
32
+ base: string;
33
+ quote: string;
34
+ };
35
+ /** Options for realistic market fills (Phase 9a). */
36
+ export interface RealisticFillOptions {
37
+ orderbook?: OrderBookDepth | null;
38
+ config?: SimulationConfig;
39
+ volFactor?: number;
40
+ /** Position metadata to attach on new position creation. */
41
+ metadata?: PositionMetadata;
42
+ }
43
+ /**
44
+ * Fill a market order immediately at the given price (plus slippage).
45
+ * When realistic options are provided, uses book-aware VWAP + latency + maker/taker fees.
46
+ * Otherwise falls back to legacy random slippage + flat fee.
47
+ */
48
+ export declare function fillMarketOrder(order: Order, currentPrice: number, wallet: Wallet, existingPosition: Position | null, realistic?: RealisticFillOptions): FillResult;
49
+ /**
50
+ * Fill a limit order (when price crosses the limit price).
51
+ * Limit orders fill at the limit price. When config is provided, uses maker fee rate.
52
+ */
53
+ export declare function fillLimitOrder(order: Order, wallet: Wallet, existingPosition: Position | null, config?: SimulationConfig, decisionPrice?: number, metadata?: PositionMetadata): FillResult;
@@ -0,0 +1,276 @@
1
+ // Fill engine for simulated order execution (derivatives model).
2
+ // All accounting in quote currency (USDT). Supports long and short positions.
3
+ // Opening any direction: deduct notional + fee from USDT.
4
+ // Closing: credit margin + PnL - fee to USDT.
5
+ //
6
+ // Phase 9a: Supports realistic fills (book-aware VWAP, latency, maker/taker fees)
7
+ // when SimulationConfig and OrderBookDepth are provided. Falls back to legacy
8
+ // behavior (0-2 bps random slippage, 0.1% flat fee) when not provided.
9
+ import { FEE_RATE } from './types.js';
10
+ import { computeRealisticMarketFill, computeLimitFillQuality, getFeeRate, } from './realistic-fills.js';
11
+ /** On NEW-position creation only, freeze the at-fill stop into
12
+ * `originalStopPrice` and seed `mfePeakPrice` to the entry price. Component C
13
+ * uses these as immutable denominators / starting points for MFE tracking.
14
+ * Scale-ins and partial closes preserve whatever was set on the first fill —
15
+ * this helper is intentionally only called from the brand-new and flip code
16
+ * paths, never from the scale-in path. */
17
+ function seedMfeMetadata(metadata, fillPrice) {
18
+ const seeded = { ...(metadata ?? {}) };
19
+ if (seeded.originalEntryPrice === undefined) {
20
+ seeded.originalEntryPrice = fillPrice; // frozen R/MFE entry reference
21
+ }
22
+ if (seeded.originalStopPrice === undefined && seeded.stopPrice !== undefined) {
23
+ seeded.originalStopPrice = seeded.stopPrice;
24
+ }
25
+ if (seeded.mfePeakPrice === undefined) {
26
+ seeded.mfePeakPrice = fillPrice;
27
+ }
28
+ return seeded;
29
+ }
30
+ /**
31
+ * Compute simulated slippage for a market order (legacy mode).
32
+ * Returns basis points: random 0-2 bps, signed based on order side.
33
+ * BUY: positive slippage (pay more), SELL: negative slippage (receive less).
34
+ */
35
+ export function computeSlippage(side) {
36
+ const bps = Math.random() * 2; // 0 to 2 bps
37
+ return side === 'buy' ? bps : -bps;
38
+ }
39
+ /**
40
+ * Apply slippage to a price.
41
+ * @param price Base price
42
+ * @param slippageBps Slippage in basis points (signed)
43
+ * @returns Adjusted price
44
+ */
45
+ export function applySlippage(price, slippageBps) {
46
+ return price * (1 + slippageBps / 10_000);
47
+ }
48
+ /**
49
+ * Parse the base and quote currencies from a symbol like "BTC/USDT".
50
+ */
51
+ export function parseSymbol(symbol) {
52
+ const parts = symbol.split('/');
53
+ if (parts.length !== 2) {
54
+ throw new Error(`Invalid symbol format: ${symbol}. Expected BASE/QUOTE`);
55
+ }
56
+ return { base: parts[0], quote: parts[1] };
57
+ }
58
+ /**
59
+ * Ensure a currency exists in the wallet with at least zero balance.
60
+ */
61
+ function ensureCurrency(wallet, currency) {
62
+ if (!wallet[currency]) {
63
+ wallet[currency] = { total: 0, available: 0, locked: 0 };
64
+ }
65
+ return wallet[currency];
66
+ }
67
+ /**
68
+ * Fill a market order immediately at the given price (plus slippage).
69
+ * When realistic options are provided, uses book-aware VWAP + latency + maker/taker fees.
70
+ * Otherwise falls back to legacy random slippage + flat fee.
71
+ */
72
+ export function fillMarketOrder(order, currentPrice, wallet, existingPosition, realistic) {
73
+ if (realistic?.config) {
74
+ // Phase 9a: Realistic fill path
75
+ const { fillPrice, executionQuality } = computeRealisticMarketFill(order.side, order.amount, currentPrice, realistic.orderbook ?? null, realistic.volFactor ?? 1.0, realistic.config);
76
+ const feeRate = getFeeRate('market', realistic.config);
77
+ return executeOrderFill(order, fillPrice, wallet, existingPosition, feeRate, executionQuality, realistic.metadata);
78
+ }
79
+ // Legacy path: random 0-2 bps slippage + flat 0.1% fee
80
+ const slippageBps = computeSlippage(order.side);
81
+ const fillPrice = applySlippage(currentPrice, slippageBps);
82
+ return executeOrderFill(order, fillPrice, wallet, existingPosition);
83
+ }
84
+ /**
85
+ * Fill a limit order (when price crosses the limit price).
86
+ * Limit orders fill at the limit price. When config is provided, uses maker fee rate.
87
+ */
88
+ export function fillLimitOrder(order, wallet, existingPosition, config, decisionPrice, metadata) {
89
+ if (order.price === null) {
90
+ throw new Error('Limit order must have a price');
91
+ }
92
+ const feeRate = config ? getFeeRate('limit', config) : undefined;
93
+ const executionQuality = config && decisionPrice !== undefined
94
+ ? computeLimitFillQuality(order.side, order.amount, order.price, decisionPrice, config)
95
+ : undefined;
96
+ return executeOrderFill(order, order.price, wallet, existingPosition, feeRate, executionQuality, metadata);
97
+ }
98
+ /**
99
+ * Core fill logic (derivatives model).
100
+ * All accounting in quote currency. Both longs and shorts use USDT as collateral.
101
+ *
102
+ * Opening: deduct notional + fee from quote balance.
103
+ * Closing: credit (entry_notional + directional_PnL - fee) to quote balance.
104
+ * Flip: close existing + open opposite, fees split proportionally.
105
+ *
106
+ * Trade PnL is directional only (no fee deduction) — fees are tracked separately
107
+ * and already reflected in wallet balance changes.
108
+ *
109
+ * @param feeRateOverride When provided, overrides the legacy FEE_RATE (Phase 9a maker/taker)
110
+ * @param executionQuality When provided, attached to the Trade record (Phase 9a)
111
+ */
112
+ function executeOrderFill(order, fillPrice, wallet, existingPosition, feeRateOverride, executionQuality, metadata) {
113
+ const { quote } = parseSymbol(order.symbol);
114
+ const activeFeeRate = feeRateOverride ?? FEE_RATE;
115
+ const notional = order.amount * fillPrice;
116
+ const fee = notional * activeFeeRate;
117
+ const now = new Date().toISOString();
118
+ const quoteBalance = ensureCurrency(wallet, quote);
119
+ let position = null;
120
+ let trade = null;
121
+ let totalWalletDelta = 0;
122
+ let totalFee = fee;
123
+ // Determine relationship to existing position
124
+ const isClosing = existingPosition && ((existingPosition.side === 'long' && order.side === 'sell') ||
125
+ (existingPosition.side === 'short' && order.side === 'buy'));
126
+ if (existingPosition && isClosing) {
127
+ // --- CLOSING / REDUCING / FLIPPING ---
128
+ const closeQty = Math.min(order.amount, existingPosition.quantity);
129
+ const closeNotional = closeQty * fillPrice;
130
+ const closeFee = closeNotional * activeFeeRate;
131
+ // Directional PnL (no fee — fee tracked separately in trade record)
132
+ const pnl = existingPosition.side === 'long'
133
+ ? (fillPrice - existingPosition.entryPrice) * closeQty
134
+ : (existingPosition.entryPrice - fillPrice) * closeQty;
135
+ // Open-side fee attributable to this close (proportional for partial closes).
136
+ // Older positions without `openFee` are treated as 0 (legacy roundtrip error).
137
+ const totalOpenFee = existingPosition.openFee ?? 0;
138
+ const openFeeShare = existingPosition.quantity > 0
139
+ ? totalOpenFee * (closeQty / existingPosition.quantity)
140
+ : 0;
141
+ // Credit: release original margin + PnL - close fee
142
+ const credit = existingPosition.entryPrice * closeQty + pnl - closeFee;
143
+ quoteBalance.available += credit;
144
+ quoteBalance.total += credit;
145
+ totalWalletDelta += credit;
146
+ trade = {
147
+ id: `trade-${order.id}`,
148
+ orderId: order.id,
149
+ symbol: order.symbol,
150
+ side: order.side,
151
+ entryPrice: existingPosition.entryPrice,
152
+ exitPrice: fillPrice,
153
+ quantity: closeQty,
154
+ realizedPnl: pnl,
155
+ fee: closeFee,
156
+ openFee: openFeeShare,
157
+ openedAt: existingPosition.openedAt,
158
+ closedAt: now,
159
+ executionQuality,
160
+ metadata: existingPosition.metadata,
161
+ };
162
+ const remaining = existingPosition.quantity - closeQty;
163
+ if (remaining > 0) {
164
+ // Partial close — position reduced, metadata preserved, remaining openFee proportional
165
+ position = {
166
+ ...existingPosition,
167
+ quantity: remaining,
168
+ openFee: totalOpenFee - openFeeShare,
169
+ };
170
+ }
171
+ else if (order.amount > existingPosition.quantity) {
172
+ // Flip: close existing + open opposite (must be atomic)
173
+ const flipQty = order.amount - existingPosition.quantity;
174
+ const flipNotional = flipQty * fillPrice;
175
+ const flipFee = flipNotional * activeFeeRate;
176
+ // Affordability check for the flip leg. The close credit was already
177
+ // applied to quoteBalance.available above, so `available` IS the true
178
+ // post-close figure — do NOT add `credit` again. Double-counting the
179
+ // released margin would pass an unaffordable flip and overdraw the
180
+ // wallet negative. On failure we roll the credit back below.
181
+ const projectedAvailable = quoteBalance.available;
182
+ if (projectedAvailable < flipNotional + flipFee) {
183
+ // Rollback the close credit already applied above
184
+ quoteBalance.available -= credit;
185
+ quoteBalance.total -= credit;
186
+ totalWalletDelta -= credit;
187
+ throw new Error(`Insufficient ${quote} balance for position flip. After closing, would have ${projectedAvailable.toFixed(2)} but need ${(flipNotional + flipFee).toFixed(2)}`);
188
+ }
189
+ quoteBalance.available -= flipNotional + flipFee;
190
+ quoteBalance.total -= flipNotional + flipFee;
191
+ totalWalletDelta -= flipNotional + flipFee;
192
+ // Accounting invariant: the affordability check above guarantees a
193
+ // non-negative balance. If this ever fires, the flip math regressed.
194
+ if (quoteBalance.available < -1e-6) {
195
+ throw new Error(`Wallet overdrawn during flip (available=${quoteBalance.available.toFixed(2)}) — accounting invariant violated`);
196
+ }
197
+ position = {
198
+ symbol: order.symbol,
199
+ side: order.side === 'buy' ? 'long' : 'short',
200
+ entryPrice: fillPrice,
201
+ quantity: flipQty,
202
+ openedAt: now,
203
+ metadata: seedMfeMetadata(metadata, fillPrice),
204
+ openFee: flipFee,
205
+ };
206
+ totalFee = closeFee + flipFee;
207
+ }
208
+ // else: fully closed, position remains null, totalFee = closeFee
209
+ else {
210
+ totalFee = closeFee;
211
+ }
212
+ }
213
+ else if (existingPosition && !isClosing) {
214
+ // --- ADDING TO EXISTING POSITION (same direction) ---
215
+ if (quoteBalance.available < notional + fee) {
216
+ throw new Error(`Insufficient ${quote} balance. Need ${(notional + fee).toFixed(2)}, have ${quoteBalance.available.toFixed(2)}`);
217
+ }
218
+ quoteBalance.available -= notional + fee;
219
+ quoteBalance.total -= notional + fee;
220
+ totalWalletDelta = -(notional + fee);
221
+ // Average entry price
222
+ const totalQty = existingPosition.quantity + order.amount;
223
+ const avgEntry = (existingPosition.entryPrice * existingPosition.quantity + fillPrice * order.amount) / totalQty;
224
+ // Scale-in: preserve frozen MFE state (originalStopPrice + the running
225
+ // peak) from the first fill, while letting the agent override mutable
226
+ // entry-context fields like updated stopPrice / thesis on this fill.
227
+ const baseMeta = existingPosition.metadata ?? {};
228
+ const incomingMeta = metadata ?? {};
229
+ const mergedMeta = {
230
+ ...baseMeta,
231
+ ...incomingMeta,
232
+ originalEntryPrice: baseMeta.originalEntryPrice ?? incomingMeta.originalEntryPrice,
233
+ originalStopPrice: baseMeta.originalStopPrice ?? incomingMeta.originalStopPrice,
234
+ mfePeakPrice: baseMeta.mfePeakPrice ?? incomingMeta.mfePeakPrice,
235
+ };
236
+ position = {
237
+ ...existingPosition,
238
+ entryPrice: avgEntry,
239
+ quantity: totalQty,
240
+ metadata: mergedMeta,
241
+ openFee: (existingPosition.openFee ?? 0) + fee,
242
+ };
243
+ }
244
+ else {
245
+ // --- NEW POSITION ---
246
+ if (quoteBalance.available < notional + fee) {
247
+ throw new Error(`Insufficient ${quote} balance. Need ${(notional + fee).toFixed(2)}, have ${quoteBalance.available.toFixed(2)}`);
248
+ }
249
+ quoteBalance.available -= notional + fee;
250
+ quoteBalance.total -= notional + fee;
251
+ totalWalletDelta = -(notional + fee);
252
+ position = {
253
+ symbol: order.symbol,
254
+ side: order.side === 'buy' ? 'long' : 'short',
255
+ entryPrice: fillPrice,
256
+ quantity: order.amount,
257
+ openedAt: now,
258
+ metadata: seedMfeMetadata(metadata, fillPrice),
259
+ openFee: fee,
260
+ };
261
+ }
262
+ // Build the filled order
263
+ const filledOrder = {
264
+ ...order,
265
+ status: 'closed',
266
+ filled: order.amount,
267
+ average: fillPrice,
268
+ cost: notional,
269
+ fee: { cost: totalFee, currency: quote },
270
+ };
271
+ const walletDeltas = {};
272
+ if (totalWalletDelta !== 0) {
273
+ walletDeltas[quote] = totalWalletDelta;
274
+ }
275
+ return { order: filledOrder, position, trade, walletDeltas, executionQuality };
276
+ }
@@ -0,0 +1,26 @@
1
+ import type { CcxtTicker } from '../types.js';
2
+ import type { ExchangeSimulator } from './exchange-simulator.js';
3
+ export declare const DEFAULT_FEED_INTERVAL_MS = 5000;
4
+ export declare const MAX_SYMBOLS_PER_TICK = 30;
5
+ /** Narrow ticker source — only the method the feed needs. Lets tests inject a
6
+ * fake without constructing a real CCXT client. */
7
+ export interface TickerSource {
8
+ fetchTicker(symbol: string): Promise<CcxtTicker | null>;
9
+ }
10
+ export declare class PaperMarketFeed {
11
+ private readonly tickerSource;
12
+ private readonly simulator;
13
+ private readonly intervalMs;
14
+ private interval;
15
+ private ticking;
16
+ constructor(tickerSource: TickerSource, simulator: ExchangeSimulator, intervalMs?: number);
17
+ /** Idempotent — a second start() while running is a no-op. */
18
+ start(): void;
19
+ stop(): void;
20
+ isRunning(): boolean;
21
+ /** One refresh cycle. Exposed for tests (no setInterval). Never throws —
22
+ * a fetch failure on one symbol is logged and skipped; state self-heals on
23
+ * the next tick. Overlapping ticks are suppressed (a slow fetch round can
24
+ * outlast the interval). */
25
+ tick(): Promise<void>;
26
+ }
@@ -0,0 +1,104 @@
1
+ // Paper-mode background market-data feed.
2
+ //
3
+ // WHY: in paper mode the simulator's per-symbol lastTicker map is the SOLE
4
+ // source of mark price, unrealized PnL, NAV, MFE, the stop-watcher breach
5
+ // check, AND the resting-limit fill trigger. Nothing else refreshes it on a
6
+ // timer — updateTicker fires only when the agent happens to call a price tool.
7
+ // The skill gateway poller refreshes only the ONE dashboard symbol, so every
8
+ // other symbol the agent trades (routine — it scans/trades many pairs) had its
9
+ // mark FROZEN at entry until the next heartbeat: the stop-watcher compared a
10
+ // stale mark and never fired, NAV/uPnL went stale, and resting limits missed.
11
+ //
12
+ // This poller closes that gap: every `intervalMs` it collects the symbols with
13
+ // an open position OR an open order and refreshes each via the simulator's
14
+ // updateTicker (which also runs MFE + the pending-limit-fill check). It is
15
+ // paper-only and skips entirely when the book is flat, so it adds zero Binance
16
+ // weight when there is nothing to track.
17
+ //
18
+ // Weight: it reuses the single-symbol, ban-gated BinancePublicApi.fetchTicker
19
+ // (Binance GET /fapi/v1/ticker/24hr with a symbol = weight 1) one symbol at a
20
+ // time. For a realistic paper book (a handful of open symbols) this is lighter
21
+ // than CCXT fetchTickers(), which hits the all-symbols endpoint (weight 40).
22
+ import { logger, formatError } from '../logger.js';
23
+ const TAG = 'paper-market-feed';
24
+ // 5s: well inside the 10s stop-watcher interval (so a breach is seen with a
25
+ // fresh mark on the next watcher tick) and trivial weight for a small book.
26
+ export const DEFAULT_FEED_INTERVAL_MS = 5_000;
27
+ // Defensive upper bound on symbols refreshed per tick, so a pathological book
28
+ // can't issue an unbounded burst of reads. A real paper book is far smaller;
29
+ // if we ever hit the cap we log it (no silent truncation).
30
+ export const MAX_SYMBOLS_PER_TICK = 30;
31
+ export class PaperMarketFeed {
32
+ tickerSource;
33
+ simulator;
34
+ intervalMs;
35
+ interval = null;
36
+ ticking = false;
37
+ constructor(tickerSource, simulator, intervalMs = DEFAULT_FEED_INTERVAL_MS) {
38
+ this.tickerSource = tickerSource;
39
+ this.simulator = simulator;
40
+ this.intervalMs = intervalMs;
41
+ }
42
+ /** Idempotent — a second start() while running is a no-op. */
43
+ start() {
44
+ if (this.interval)
45
+ return;
46
+ logger.info(TAG, `Starting paper market feed (interval: ${this.intervalMs}ms)`);
47
+ this.interval = setInterval(() => {
48
+ void this.tick();
49
+ }, this.intervalMs);
50
+ }
51
+ stop() {
52
+ if (this.interval) {
53
+ clearInterval(this.interval);
54
+ this.interval = null;
55
+ }
56
+ }
57
+ isRunning() {
58
+ return this.interval !== null;
59
+ }
60
+ /** One refresh cycle. Exposed for tests (no setInterval). Never throws —
61
+ * a fetch failure on one symbol is logged and skipped; state self-heals on
62
+ * the next tick. Overlapping ticks are suppressed (a slow fetch round can
63
+ * outlast the interval). */
64
+ async tick() {
65
+ if (this.ticking)
66
+ return;
67
+ this.ticking = true;
68
+ try {
69
+ const state = this.simulator.getState();
70
+ const symbols = new Set();
71
+ for (const p of state.positions)
72
+ symbols.add(p.symbol);
73
+ for (const o of state.openOrders)
74
+ symbols.add(o.symbol);
75
+ if (symbols.size === 0)
76
+ return; // flat — no work, no Binance weight
77
+ let list = Array.from(symbols);
78
+ if (list.length > MAX_SYMBOLS_PER_TICK) {
79
+ logger.warn(TAG, `Open-symbol count ${list.length} exceeds per-tick cap ${MAX_SYMBOLS_PER_TICK} — refreshing the first ${MAX_SYMBOLS_PER_TICK}; the rest refresh next tick`);
80
+ list = list.slice(0, MAX_SYMBOLS_PER_TICK);
81
+ }
82
+ for (const symbol of list) {
83
+ let ticker;
84
+ try {
85
+ ticker = await this.tickerSource.fetchTicker(symbol);
86
+ }
87
+ catch (err) {
88
+ // fetchTicker already swallows + returns null, but guard the
89
+ // weight-pacer throw path too so one symbol never aborts the loop.
90
+ logger.warn(TAG, `fetchTicker(${symbol}) threw: ${formatError(err)}`);
91
+ continue;
92
+ }
93
+ // Reject garbage marks (a null/0 last would poison NAV / open a
94
+ // 0-priced fill — mirrors the simulator's own mark > 0 guard).
95
+ if (ticker && Number.isFinite(ticker.last) && ticker.last > 0) {
96
+ this.simulator.updateTicker(ticker);
97
+ }
98
+ }
99
+ }
100
+ finally {
101
+ this.ticking = false;
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,59 @@
1
+ import type { OrderBookDepth, SimulationConfig, ExecutionQuality } from './types.js';
2
+ /**
3
+ * Walk the order book to compute a volume-weighted average fill price.
4
+ *
5
+ * For BUY: walks asks (sellers) from best ask upward.
6
+ * For SELL: walks bids (buyers) from best bid downward.
7
+ *
8
+ * If the order exceeds available depth, the remainder fills at the worst
9
+ * available price + 10 bps penalty (simulating thin-book impact).
10
+ *
11
+ * @returns { vwap, levelsConsumed, fillRatio } where fillRatio < 1 only
12
+ * if we want to model partial fills (currently always fills completely).
13
+ */
14
+ export declare function computeBookAwareFillPrice(side: 'buy' | 'sell', amount: number, orderbook: OrderBookDepth): {
15
+ vwap: number;
16
+ levelsConsumed: number;
17
+ };
18
+ /**
19
+ * Simulate latency-induced price drift.
20
+ *
21
+ * During the latency window, the price can move against the trader.
22
+ * The drift is proportional to latency duration and current volatility.
23
+ *
24
+ * @param basePrice The VWAP price before latency adjustment
25
+ * @param side Order side (buy drift is positive, sell drift is negative)
26
+ * @param latencyMs Simulated latency in milliseconds
27
+ * @param volFactor Current volatility factor (1.0 = normal, >1 = elevated)
28
+ * @returns { adjustedPrice, latencyImpactBps }
29
+ */
30
+ export declare function applyLatencyDrift(basePrice: number, side: 'buy' | 'sell', latencyMs: number, volFactor: number): {
31
+ adjustedPrice: number;
32
+ latencyImpactBps: number;
33
+ };
34
+ /**
35
+ * Generate a random simulated latency within the configured range.
36
+ */
37
+ export declare function generateLatency(config: SimulationConfig): number;
38
+ /**
39
+ * Compute the fee rate based on order type (maker vs taker).
40
+ * Market orders = taker. Limit orders = maker.
41
+ */
42
+ export declare function getFeeRate(orderType: 'market' | 'limit', config: SimulationConfig): number;
43
+ /**
44
+ * Compute a complete realistic fill for a market order.
45
+ *
46
+ * Combines: orderbook VWAP + latency drift + appropriate fee rate.
47
+ * Falls back to simple random slippage if no orderbook is available.
48
+ *
49
+ * @returns fillPrice and ExecutionQuality metrics
50
+ */
51
+ export declare function computeRealisticMarketFill(side: 'buy' | 'sell', amount: number, decisionPrice: number, orderbook: OrderBookDepth | null, volFactor: number, config?: SimulationConfig): {
52
+ fillPrice: number;
53
+ executionQuality: ExecutionQuality;
54
+ };
55
+ /**
56
+ * Compute execution quality metrics for a limit order fill.
57
+ * Limit orders fill at the limit price (maker) with no market impact.
58
+ */
59
+ export declare function computeLimitFillQuality(side: 'buy' | 'sell', amount: number, limitPrice: number, decisionPrice: number, config?: SimulationConfig): ExecutionQuality;