@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,338 @@
1
+ // Tool: create_order — order execution with real price data + pre-trade risk gate
2
+ // Readiness gate: BLOCKED unless adapter.readiness === 'READY'.
3
+ import { formatError } from '../logger.js';
4
+ import { getQuoteBalance, getQuoteWalletBalance } from '../balance-utils.js';
5
+ import { fetchCurrentPrice, fetchOrderBook, isError } from './helpers.js';
6
+ import { validateCreateOrder, sanitizeRealizationRule } from './assessment-validation.js';
7
+ import { preTradeRiskCheck, getDefaultPreTradeLimits, computeConsecutiveLosses } from '../risk/pre-trade-check.js';
8
+ import { bracketsEnabled, loadBracketMode, loadBracketRequirements } from '../config/brackets-config.js';
9
+ import { onCreateOrderFilled } from '../ingest/position-auto-capture.js';
10
+ /** Build a PortfolioSnapshot from current simulator state (paper mode). */
11
+ function buildPortfolioSnapshotFromSimulator(simulator) {
12
+ const state = simulator.getState();
13
+ const quoteCurrency = state.config.quoteCurrency;
14
+ const walletBal = state.wallet[quoteCurrency];
15
+ return {
16
+ positions: state.positions.map(p => {
17
+ const ticker = simulator.getLastTicker(p.symbol);
18
+ return {
19
+ symbol: p.symbol,
20
+ side: p.side,
21
+ quantity: p.quantity,
22
+ entryPrice: p.entryPrice,
23
+ markPrice: ticker?.last ?? p.entryPrice,
24
+ };
25
+ }),
26
+ walletTotal: walletBal?.total ?? 0,
27
+ walletAvailable: walletBal?.available ?? 0,
28
+ sessionStartNav: simulator.getSessionStartNav(),
29
+ equity: simulator.computeEquity(),
30
+ isLive: false,
31
+ };
32
+ }
33
+ /** Build a PortfolioSnapshot from adapter (live mode). */
34
+ async function buildPortfolioSnapshotFromAdapter(adapter) {
35
+ const [balance, positions] = await Promise.all([
36
+ adapter.getBalance(),
37
+ adapter.getPositionsOrNull(),
38
+ ]);
39
+ // null = positions fetch FAILED (429 / weight-paced / transient). If we
40
+ // collapsed that to [], existing open positions become invisible to the
41
+ // pre-trade risk check below — gross exposure / max-open-positions / net
42
+ // tilt would all be under-counted and a trade that should be blocked could
43
+ // pass. Refuse to size off a phantom-flat account; abort so the agent
44
+ // retries next heartbeat. Same null≠empty contract as the close/flatten path.
45
+ if (positions === null) {
46
+ throw new Error('Cannot build portfolio snapshot — position fetch failed (exchange data ' +
47
+ 'unavailable, likely Binance 429 or transient). Order NOT placed; retry next heartbeat.');
48
+ }
49
+ const walletTotal = getQuoteWalletBalance(balance);
50
+ // Use the session-start NAV captured once at initialization, not the current balance.
51
+ // If not available (shouldn't happen after init), fall back to current balance.
52
+ const sessionNav = adapter.getSessionStartNav?.() ?? walletTotal;
53
+ // Live Binance wallet already includes margin; equity = wallet + unrealized P&L.
54
+ let liveEquity = walletTotal;
55
+ for (const p of positions) {
56
+ const mark = p.markPrice ?? p.entryPrice;
57
+ const pnl = p.side === 'long'
58
+ ? (mark - p.entryPrice) * p.contracts
59
+ : (p.entryPrice - mark) * p.contracts;
60
+ liveEquity += pnl;
61
+ }
62
+ return {
63
+ positions: positions.map(p => ({
64
+ symbol: p.symbol,
65
+ side: p.side,
66
+ quantity: p.contracts,
67
+ entryPrice: p.entryPrice,
68
+ markPrice: p.markPrice,
69
+ })),
70
+ walletTotal,
71
+ walletAvailable: getQuoteBalance(balance.free),
72
+ sessionStartNav: sessionNav,
73
+ equity: liveEquity,
74
+ isLive: true,
75
+ };
76
+ }
77
+ export async function createOrderTool(args, deps) {
78
+ // ---- Readiness gate (live mode only) ----
79
+ if (deps.adapter.readiness !== 'READY') {
80
+ return { error: `Trading blocked: adapter is ${deps.adapter.readiness}. Wait for initialization to complete.` };
81
+ }
82
+ // ---- Assessment-schema gate (SKILL.md v2.10.0 — live mode only) ----
83
+ // In live mode the agent must include thesis + setup_type + regime +
84
+ // regime_confidence + scorecard_verdict + confluence_score. This is the
85
+ // same data the heartbeat reassessment uses to detect scorecard reversal /
86
+ // regime flip — if the agent can't produce it at entry, it can't produce
87
+ // it mid-trade either.
88
+ const gateCheck = validateCreateOrder(args, deps.adapter.isLive);
89
+ if (!gateCheck.ok) {
90
+ return { error: gateCheck.error ?? 'Invalid create_order arguments.' };
91
+ }
92
+ // Validate side
93
+ const side = args.side.toLowerCase();
94
+ if (side !== 'buy' && side !== 'sell') {
95
+ return { error: `Invalid side: ${args.side}. Must be 'buy' or 'sell'.` };
96
+ }
97
+ // Validate type
98
+ const type = args.type.toLowerCase();
99
+ if (type !== 'market' && type !== 'limit') {
100
+ return { error: `Invalid type: ${args.type}. Must be 'market' or 'limit'.` };
101
+ }
102
+ // Validate amount
103
+ if (!args.amount || args.amount <= 0) {
104
+ return { error: 'Amount must be a positive number.' };
105
+ }
106
+ // Validate price for limit orders
107
+ if (type === 'limit' && (!args.price || args.price <= 0)) {
108
+ return { error: 'Limit orders require a positive price.' };
109
+ }
110
+ // In paper mode, fetch latest ticker + order book for realistic fills
111
+ let ticker = null;
112
+ if (!deps.adapter.isLive) {
113
+ const paperDeps = { binanceApi: deps.binanceApi, simulator: deps.adapter.getSimulator() };
114
+ const [priceResult] = await Promise.all([
115
+ fetchCurrentPrice(args.symbol, paperDeps),
116
+ fetchOrderBook(args.symbol, paperDeps),
117
+ ]);
118
+ if (isError(priceResult))
119
+ return priceResult;
120
+ ticker = priceResult;
121
+ }
122
+ else {
123
+ // Live mode: fetch ticker for risk check reference price
124
+ const lastPrice = await deps.adapter.getLastPrice(args.symbol);
125
+ if (lastPrice != null) {
126
+ ticker = {
127
+ symbol: args.symbol, last: lastPrice,
128
+ bid: lastPrice, ask: lastPrice, baseVolume: 0, quoteVolume: 0,
129
+ change: 0, percentage: 0, timestamp: Date.now(), datetime: new Date().toISOString(),
130
+ };
131
+ }
132
+ }
133
+ if (!ticker) {
134
+ return { error: `Failed to fetch current price for ${args.symbol}` };
135
+ }
136
+ // ---- Pre-trade risk gate ----
137
+ const orderPrice = type === 'limit' ? args.price : ticker.last;
138
+ const proposed = {
139
+ symbol: args.symbol,
140
+ side: side,
141
+ type: type,
142
+ amount: args.amount,
143
+ price: orderPrice,
144
+ stopPrice: args.stopPrice,
145
+ targetPrice: args.target_price,
146
+ };
147
+ // Build portfolio snapshot based on mode
148
+ let portfolio;
149
+ let consecutiveLosses = 0;
150
+ if (!deps.adapter.isLive) {
151
+ const simulator = deps.adapter.getSimulator();
152
+ portfolio = buildPortfolioSnapshotFromSimulator(simulator);
153
+ consecutiveLosses = computeConsecutiveLosses(simulator.getState().tradeHistory);
154
+ }
155
+ else {
156
+ portfolio = await buildPortfolioSnapshotFromAdapter(deps.adapter);
157
+ // In live mode, consecutive losses would come from intelligence DB — use 0 for now
158
+ }
159
+ // Bracket enforcement only applies in live mode when the feature is enabled.
160
+ // Paper mode uses the stop-watcher and doesn't care about these flags.
161
+ let bracketEnforcement;
162
+ if (deps.adapter.isLive && bracketsEnabled(loadBracketMode())) {
163
+ bracketEnforcement = loadBracketRequirements();
164
+ }
165
+ const riskCheck = preTradeRiskCheck(proposed, portfolio, getDefaultPreTradeLimits(), {
166
+ volFactor: !deps.adapter.isLive ? deps.adapter.getSimulator().getVolFactor() : 1.0,
167
+ consecutiveLosses,
168
+ bracketEnforcement,
169
+ });
170
+ if (!riskCheck.allowed) {
171
+ const reasons = riskCheck.violations.map(v => v.message).join('; ');
172
+ return {
173
+ error: `Order REJECTED by risk gate [${riskCheck.drawdownZone} zone]: ${reasons}`,
174
+ };
175
+ }
176
+ // Build position metadata from optional args (only if any metadata provided)
177
+ // Sanitize numeric metadata — reject non-finite values, clamp ranges
178
+ const num = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
179
+ const clamp = (v, min, max) => {
180
+ const n = num(v);
181
+ return n != null ? Math.max(min, Math.min(max, n)) : undefined;
182
+ };
183
+ const hasMetadata = args.setup_type || args.thesis || args.target_price != null
184
+ || args.regime || args.scorecard_verdict || args.confluence_score != null
185
+ || args.invalidation_price != null || args.realization_rule != null;
186
+ const metadata = hasMetadata ? {
187
+ setupType: args.setup_type,
188
+ thesis: args.thesis?.slice(0, 500), // Cap length
189
+ stopPrice: num(args.stopPrice),
190
+ targetPrice: num(args.target_price),
191
+ regime: args.regime,
192
+ regimeConfidence: clamp(args.regime_confidence, 0, 100),
193
+ scorecardVerdict: args.scorecard_verdict,
194
+ confluenceScore: clamp(args.confluence_score, 0, 10),
195
+ invalidationPrice: num(args.invalidation_price),
196
+ realizationRule: sanitizeRealizationRule(args.realization_rule),
197
+ } : undefined;
198
+ // ---- Approval-mode branches ----
199
+ // Both shadow and per_trade need full v2.10.0 metadata so the proposal is
200
+ // operator-meaningful. Metadata is the agent's responsibility; ingest creds
201
+ // are the operator's. Separate so the error messages can be precise.
202
+ const argsMetadataComplete = !!(args.thesis &&
203
+ args.setup_type &&
204
+ args.target_price != null &&
205
+ args.stopPrice != null &&
206
+ args.regime &&
207
+ typeof args.regime_confidence === 'number' &&
208
+ args.scorecard_verdict &&
209
+ typeof args.confluence_score === 'number');
210
+ const proposalPathWired = !!(deps.proposalManager && deps.userId);
211
+ const approvalMode = deps.approvalMode ?? 'off';
212
+ // per_trade mode: propose and EARLY-RETURN. The agent's call doesn't fire;
213
+ // the operator approves and ProposalDecisionListener fires via this same
214
+ // tool with proposalManager omitted (which takes the real path below).
215
+ //
216
+ // If proposalManager isn't wired (operator set approval.mode=per_trade in
217
+ // config but ingest creds aren't present), we fall through to the normal
218
+ // fire path — the startup warning is the operator's signal. We never trap
219
+ // the agent inside an unfireable per_trade branch.
220
+ if (approvalMode === 'per_trade' && proposalPathWired) {
221
+ if (!argsMetadataComplete) {
222
+ return {
223
+ error: 'approval.mode=per_trade requires the full v2.10.0 metadata block on every create_order: ' +
224
+ 'thesis, setup_type, target_price, stopPrice, regime, regime_confidence, ' +
225
+ 'scorecard_verdict, confluence_score. The operator approves on this metadata.',
226
+ };
227
+ }
228
+ try {
229
+ const result = deps.proposalManager.propose(deps.userId, {
230
+ symbol: args.symbol,
231
+ side: side,
232
+ orderType: type,
233
+ size: args.amount,
234
+ proposedEntry: orderPrice,
235
+ stopPrice: args.stopPrice,
236
+ targetPrice: args.target_price,
237
+ riskUsd: Math.abs(orderPrice - args.stopPrice) * args.amount,
238
+ thesis: args.thesis,
239
+ setupType: args.setup_type,
240
+ regime: args.regime,
241
+ regimeConfidence: args.regime_confidence,
242
+ scorecardVerdict: args.scorecard_verdict,
243
+ confluenceScore: args.confluence_score,
244
+ ...(args.supersedes_id ? { supersedesId: args.supersedes_id } : {}),
245
+ // Real, operator-actionable. Default would also resolve to 'real' but
246
+ // we set it explicitly so the intent reads at the call site.
247
+ origin: 'real',
248
+ });
249
+ return {
250
+ status: 'pending_approval',
251
+ proposal_id: result.proposalUuid,
252
+ hard_expires_at: result.hardExpiresAt.toISOString(),
253
+ setup_bucket: result.setupBucket,
254
+ message: `Order awaits operator approval (${result.setupBucket} bucket, expires ` +
255
+ `${result.hardExpiresAt.toISOString()}). Do not call create_order again ` +
256
+ `for this setup; the listener will fire on approval. If the operator ` +
257
+ `requests modifications in chat, call create_order with supersedes_id=` +
258
+ `<id from the chat-context header>.`,
259
+ };
260
+ }
261
+ catch (err) {
262
+ return { error: `Proposal submission failed: ${formatError(err)}` };
263
+ }
264
+ }
265
+ // shadow mode: dual-write a telemetry proposal AND continue to the real
266
+ // fire below. Never blocks the hot path on a propose() failure.
267
+ if (approvalMode === 'shadow' && proposalPathWired && argsMetadataComplete) {
268
+ try {
269
+ deps.proposalManager.propose(deps.userId, {
270
+ symbol: args.symbol,
271
+ side: side,
272
+ orderType: type,
273
+ size: args.amount,
274
+ proposedEntry: orderPrice,
275
+ stopPrice: args.stopPrice,
276
+ targetPrice: args.target_price,
277
+ riskUsd: Math.abs(orderPrice - args.stopPrice) * args.amount,
278
+ thesis: args.thesis,
279
+ setupType: args.setup_type,
280
+ regime: args.regime,
281
+ regimeConfidence: args.regime_confidence,
282
+ scorecardVerdict: args.scorecard_verdict,
283
+ confluenceScore: args.confluence_score,
284
+ ...(args.supersedes_id ? { supersedesId: args.supersedes_id } : {}),
285
+ // Phase A telemetry — agent's create_order ALSO fires the real order
286
+ // synchronously below. Hidden from operator UI by origin='shadow'.
287
+ origin: 'shadow',
288
+ });
289
+ }
290
+ catch (err) {
291
+ // eslint-disable-next-line no-console
292
+ console.warn(`[approval-shadow] propose() threw: ${formatError(err)}`);
293
+ }
294
+ }
295
+ try {
296
+ const order = await deps.adapter.createOrder(args.symbol, side, type, args.amount, args.price, metadata);
297
+ // Auto-capture entry to the Position Decision Journal — fail-open, never
298
+ // block the trading hot path on a webapp ingest blip.
299
+ if (deps.autoCapture) {
300
+ // ALWAYS stash the metadata first (fixed 2026-07-05). Binance futures
301
+ // market orders routinely come back filled>0 but with NO average price
302
+ // in the immediate REST response — the synchronous capture below then
303
+ // bails ("missing fill price; skipping capture") and, before this fix,
304
+ // the metadata was dropped on the floor because the stash only ran in
305
+ // the not-filled branch. The WS-driven onWsFillObserved captured those
306
+ // entries seconds later with metadata=none → the live journal filled
307
+ // with '(no thesis recorded)' / setup_type='unknown' rows (majority of
308
+ // 2026-07 live entries) even though the agent supplied full v2.10.0
309
+ // metadata every time. When the synchronous capture DOES succeed, the
310
+ // WS dedup (openedFromExchangeTradeId === orderId) skips the duplicate
311
+ // and the unused stash entry simply expires (24h TTL, pruned).
312
+ if (metadata &&
313
+ deps.autoCapture.pendingEntries &&
314
+ typeof order.id === 'string' &&
315
+ order.id.length > 0) {
316
+ deps.autoCapture.pendingEntries.put({
317
+ orderId: order.id,
318
+ symbol: args.symbol,
319
+ side: side,
320
+ metadata,
321
+ });
322
+ }
323
+ const filledNow = typeof order.filled === 'number' && order.filled > 0;
324
+ if (filledNow) {
325
+ // Order filled at submit (market, or limit-that-crossed). Capture
326
+ // synchronously — same as the original PR1 path.
327
+ onCreateOrderFilled(deps.autoCapture, { symbol: args.symbol, side: side, metadata }, order).catch((err) => {
328
+ // eslint-disable-next-line no-console
329
+ console.warn(`[position-auto-capture] entry capture threw: ${formatError(err)}`);
330
+ });
331
+ }
332
+ }
333
+ return order;
334
+ }
335
+ catch (err) {
336
+ return { error: formatError(err) };
337
+ }
338
+ }
@@ -0,0 +1,58 @@
1
+ export type ExitGateMode = 'off' | 'shadow' | 'observe' | 'enforce';
2
+ export type ThesisStatusForGate = 'intact' | 'weakening' | 'invalidated' | 'evolving';
3
+ export type ReviewVerdictForGate = 'hold' | 'add_on' | 'close_recommended';
4
+ export interface ExitGateInputs {
5
+ /** The close_position reason the agent supplied. `operator_command` always
6
+ * passes; the other five reasons go through tier evaluation. */
7
+ reason: 'regime_flip' | 'scorecard_reversal' | 'risk_limit' | 'bracket_integrity' | 'operator_command';
8
+ /** Max favorable excursion in R-multiples ever reached on this position.
9
+ * Undefined => gate skips (PASS with code 'inputs_unavailable_mfe'). */
10
+ mfe_r_peak?: number;
11
+ /** Current R-multiple at the close price. Undefined => gate skips. */
12
+ current_r?: number;
13
+ /** Telemetry only (0a/0b) — provenance of the mfe_r_peak / current_r values
14
+ * above. Ignored by evaluateExitGate; set by buildExitGateInputs and
15
+ * persisted in gate_eval.inputs so the inputs_unavailable_* and
16
+ * computed-fallback paths are diagnosable from the journal. */
17
+ mfe_src?: 'assessment' | 'pos.mfeR' | 'computed_floor';
18
+ current_r_src?: 'assessment' | 'computed';
19
+ /** Implied or explicit vol-shock flag from the caller. The caller computes
20
+ * this from recent ATR vs latest adverse 5m bar. Default false. */
21
+ volShock?: boolean;
22
+ /** Position side — needed for the T2a stop-trail check (long stop ≥ entry,
23
+ * short stop ≤ entry). */
24
+ side?: 'long' | 'short';
25
+ /** Current bracket stop price. Undefined => T2a check is skipped (PASS with
26
+ * code 't2a_skipped_no_stop_known' rather than block). */
27
+ stopPrice?: number;
28
+ /** Entry price for the position. Used as the BE level in T2a. */
29
+ entryPrice?: number;
30
+ /** Most recent record_position_reviews entry for this position. Drives the
31
+ * contradicts-own-review block and the review-override pass. */
32
+ lastReview?: {
33
+ verdict: ReviewVerdictForGate;
34
+ thesis_status: ThesisStatusForGate;
35
+ review_at_ms: number;
36
+ };
37
+ /** Current time in ms — explicit for testability. */
38
+ now_ms: number;
39
+ }
40
+ export type GateOutcome = 'PASS' | 'BLOCK';
41
+ export interface GateVerdict {
42
+ outcome: GateOutcome;
43
+ /** Tier classification: T1 / T2a / T2b / override / skipped. */
44
+ tier: 'T1' | 'T2a' | 'T2b' | 'override' | 'skipped';
45
+ /** Stable code identifying the specific predicate that fired.
46
+ * e.g. 't1_premature_exit', 't2a_stop_not_trailed_to_BE',
47
+ * 'operator_command', 'vol_shock', 'contradicts_own_recent_hold_verdict'. */
48
+ code: string;
49
+ /** Optional recovery hint for BLOCK outcomes — surfaced back to the agent
50
+ * via the tool error so it can self-correct mid-turn. */
51
+ recovery_hint?: string;
52
+ /** Optional discipline-failure flag, currently only set on T2b passes so
53
+ * we capture "winner peaked, reversed, no stop-trail done". */
54
+ discipline_failure?: string;
55
+ }
56
+ /** Evaluate the exit gate against the supplied inputs. Pure; safe to call
57
+ * from anywhere. See module header for semantics. */
58
+ export declare function evaluateExitGate(inputs: ExitGateInputs): GateVerdict;
@@ -0,0 +1,162 @@
1
+ // Exit gate — prevents the agent from closing positions before they've
2
+ // meaningfully developed, while preserving override paths for genuine adverse
3
+ // signals. See docs/EXIT_GATE_DESIGN.md for the full spec and §3 for the
4
+ // tier table this function implements.
5
+ //
6
+ // Pure function — caller supplies all inputs, including the mode flag, and the
7
+ // function returns a structured verdict. Caller (close-position.ts) is
8
+ // responsible for actually blocking, soft-warning, or logging based on mode.
9
+ //
10
+ // Design contract:
11
+ // - In `off` mode the gate is never called at all (don't pay the input
12
+ // computation cost).
13
+ // - In `shadow` / `observe` / `enforce` the gate runs; the caller decides
14
+ // what to do with BLOCK verdicts.
15
+ // - If inputs are partial (e.g., MFE not supplied), the gate returns
16
+ // PASS('inputs_unavailable') — never BLOCK on missing data. The shadow
17
+ // soak surfaces these so we can fix plumbing before enforce.
18
+ // - The "contradicts-own-review" rule applies pre-tier (blocks T2b too).
19
+ // See §8 R4 in the design doc — this is the conservative default; we
20
+ // watch shadow telemetry and demote to T1-only if it blocks legit exits.
21
+ /** Window during which a hold-verdict review blocks a contradicting close. */
22
+ const CONTRADICTS_OWN_REVIEW_WINDOW_MS = 5 * 60 * 1000;
23
+ /** Developed-position threshold in R-multiples. */
24
+ const DEVELOPED_MFE_R = 0.5;
25
+ /** T1 escape threshold — position deteriorating fast. */
26
+ const T1_ESCAPE_CURRENT_R = -0.5;
27
+ /** 0c — floor for the 'weakening' review override. The t1_review_override pass
28
+ * was the dominant exit (~42% of agent closes) and, by construction, every one
29
+ * is a T1-premature position — the exact population the gate exists to BLOCK.
30
+ * It was fully agent-self-served: file verdict='close_recommended' +
31
+ * thesis_status='weakening' and the block lifted with zero objective
32
+ * corroboration. We now split by strength of claim: 'invalidated' (a strong,
33
+ * falsifiable thesis break — regime flip / level broken) keeps the FULL
34
+ * override even at breakeven; 'weakening' (a soft, abuse-prone signal) only
35
+ * overrides once price has ALSO moved adversely to at least this floor.
36
+ * ⚠ This threshold is a value to SOAK in shadow (compare would_have_blocked vs
37
+ * which positions later hit TP), NOT a derived optimum — tightening reclassifies
38
+ * a large population. See docs/EXIT_DISCIPLINE_BUILD_PLAN.md Step 0c. */
39
+ const T1_WEAKENING_OVERRIDE_FLOOR_R = -0.25;
40
+ /** Evaluate the exit gate against the supplied inputs. Pure; safe to call
41
+ * from anywhere. See module header for semantics. */
42
+ export function evaluateExitGate(inputs) {
43
+ // 0. Always-allow overrides — checked first, before any input-availability
44
+ // checks, so the operator can ALWAYS get out.
45
+ if (inputs.reason === 'operator_command') {
46
+ return { outcome: 'PASS', tier: 'override', code: 'operator_command' };
47
+ }
48
+ if (inputs.volShock === true) {
49
+ return { outcome: 'PASS', tier: 'override', code: 'vol_shock' };
50
+ }
51
+ // 1. Contradicts-own-review block — pre-tier.
52
+ // The "intact + hold → close" pattern from the May 12 analysis. If the
53
+ // agent's last review said hold AND was recent, refuse to immediately
54
+ // contradict it. Operator command (above) is the only override.
55
+ const lr = inputs.lastReview;
56
+ if (lr && lr.verdict === 'hold') {
57
+ const age = inputs.now_ms - lr.review_at_ms;
58
+ if (age < CONTRADICTS_OWN_REVIEW_WINDOW_MS && age >= 0) {
59
+ return {
60
+ outcome: 'BLOCK',
61
+ tier: 'override',
62
+ code: 'contradicts_own_recent_hold_verdict',
63
+ recovery_hint: `Last record_position_reviews filed verdict='hold' ${Math.round(age / 1000)}s ago. ` +
64
+ `If conditions have genuinely changed, file a fresh review with verdict='close_recommended' first. ` +
65
+ `Operator can override via reason='operator_command'.`,
66
+ };
67
+ }
68
+ }
69
+ // 2. Input availability — we need mfe_r_peak + current_r to do tier work.
70
+ // Missing => PASS with a skip code so shadow telemetry can surface the
71
+ // plumbing gap.
72
+ if (typeof inputs.mfe_r_peak !== 'number' || !Number.isFinite(inputs.mfe_r_peak)) {
73
+ return { outcome: 'PASS', tier: 'skipped', code: 'inputs_unavailable_mfe' };
74
+ }
75
+ if (typeof inputs.current_r !== 'number' || !Number.isFinite(inputs.current_r)) {
76
+ return { outcome: 'PASS', tier: 'skipped', code: 'inputs_unavailable_current_r' };
77
+ }
78
+ // 3. Review override — well-formed close_recommended on a genuine adverse
79
+ // signal. 0c split (see T1_WEAKENING_OVERRIDE_FLOOR_R): 'invalidated' (a
80
+ // strong, falsifiable break) overrides outright; 'weakening' (soft, the
81
+ // scratch-laundering vector) overrides ONLY with price confirmation
82
+ // (current_r ≤ the floor). inputs.current_r is guaranteed finite here
83
+ // (checked in step 2 above).
84
+ const reviewOverride = !!lr &&
85
+ lr.verdict === 'close_recommended' &&
86
+ (lr.thesis_status === 'invalidated' ||
87
+ (lr.thesis_status === 'weakening' && inputs.current_r <= T1_WEAKENING_OVERRIDE_FLOOR_R));
88
+ // 4. Tier classification.
89
+ const developed = inputs.mfe_r_peak >= DEVELOPED_MFE_R;
90
+ if (!developed) {
91
+ // ── Tier 1: never developed (mfe_r_peak < 0.5) ──
92
+ if (inputs.current_r <= T1_ESCAPE_CURRENT_R) {
93
+ return {
94
+ outcome: 'PASS',
95
+ tier: 'T1',
96
+ code: 't1_escape_deteriorating',
97
+ };
98
+ }
99
+ if (reviewOverride) {
100
+ return {
101
+ outcome: 'PASS',
102
+ tier: 'T1',
103
+ code: 't1_review_override',
104
+ };
105
+ }
106
+ return {
107
+ outcome: 'BLOCK',
108
+ tier: 'T1',
109
+ code: 't1_premature_exit',
110
+ recovery_hint: `Position has not developed (mfe_r_peak=${inputs.mfe_r_peak.toFixed(2)}R < 0.5R) and is not deteriorating ` +
111
+ `(current_r=${inputs.current_r.toFixed(2)}R > -0.5R). ` +
112
+ `Let the bracket be the exit. Override paths: ` +
113
+ `(a) file verdict='close_recommended' with thesis_status='invalidated' — a real, falsifiable ` +
114
+ `thesis break (regime flip, key level broken), not a soft read; ` +
115
+ `(b) thesis_status='weakening' overrides ONLY once current_r ≤ -0.25R (soft signals need price confirmation); ` +
116
+ `(c) wait for current_r ≤ -0.5R; (d) operator_command.`,
117
+ };
118
+ }
119
+ // ── Tier 2: developed (mfe_r_peak ≥ 0.5) ──
120
+ if (inputs.current_r >= 0) {
121
+ // T2a — in profit. Require stop has been trailed to BE-or-better.
122
+ // Need side + stopPrice + entryPrice to check; if any missing, pass with
123
+ // a skip code so shadow telemetry surfaces it.
124
+ if (!inputs.side ||
125
+ typeof inputs.stopPrice !== 'number' ||
126
+ !Number.isFinite(inputs.stopPrice) ||
127
+ typeof inputs.entryPrice !== 'number' ||
128
+ !Number.isFinite(inputs.entryPrice)) {
129
+ return {
130
+ outcome: 'PASS',
131
+ tier: 'T2a',
132
+ code: 't2a_skipped_no_stop_known',
133
+ };
134
+ }
135
+ const stopAtBE = inputs.side === 'long' ? inputs.stopPrice >= inputs.entryPrice : inputs.stopPrice <= inputs.entryPrice;
136
+ if (!stopAtBE) {
137
+ return {
138
+ outcome: 'BLOCK',
139
+ tier: 'T2a',
140
+ code: 't2a_stop_not_trailed_to_BE',
141
+ recovery_hint: `Position developed to mfe_r_peak=${inputs.mfe_r_peak.toFixed(2)}R and is currently in profit ` +
142
+ `(current_r=${inputs.current_r.toFixed(2)}R), but stop is still at original level ` +
143
+ `(stop=${inputs.stopPrice}, entry=${inputs.entryPrice}). ` +
144
+ `Call modify_stop to move stop to ${inputs.entryPrice} (break-even) or better before closing. ` +
145
+ `This locks in profit and is the v2.10.0 winner-protection rule.`,
146
+ };
147
+ }
148
+ return {
149
+ outcome: 'PASS',
150
+ tier: 'T2a',
151
+ code: 't2a_developed_in_profit_stop_trailed',
152
+ };
153
+ }
154
+ // T2b — developed, now in red. Horse has bolted; allow the exit but flag
155
+ // as discipline failure so we can study these later.
156
+ return {
157
+ outcome: 'PASS',
158
+ tier: 'T2b',
159
+ code: 't2b_developed_reversed',
160
+ discipline_failure: 'unmanaged_winner_reversed',
161
+ };
162
+ }
@@ -0,0 +1,5 @@
1
+ import type { IExchangeAdapter } from '../exchange-adapter.js';
2
+ import type { CcxtBalance } from '../types.js';
3
+ export declare function fetchBalanceTool(_args: Record<string, unknown>, deps: {
4
+ adapter: IExchangeAdapter;
5
+ }): Promise<CcxtBalance>;
@@ -0,0 +1,4 @@
1
+ // Tool: fetch_balance — wallet balance (paper or live)
2
+ export async function fetchBalanceTool(_args, deps) {
3
+ return deps.adapter.getBalance();
4
+ }
@@ -0,0 +1,11 @@
1
+ import type { BinancePublicApi } from '../ccxt/binance-public.js';
2
+ import type { CcxtOHLCV } from '../types.js';
3
+ export declare function fetchOhlcvTool(args: {
4
+ symbol: string;
5
+ timeframe?: string;
6
+ limit?: number;
7
+ }, deps: {
8
+ binanceApi: BinancePublicApi;
9
+ }): Promise<CcxtOHLCV[] | {
10
+ error: string;
11
+ }>;
@@ -0,0 +1,8 @@
1
+ // Tool: fetch_ohlcv — real Binance OHLCV candles via CCXT
2
+ export async function fetchOhlcvTool(args, deps) {
3
+ const candles = await deps.binanceApi.fetchOHLCV(args.symbol, args.timeframe ?? '1h', args.limit ?? 100);
4
+ if (!candles) {
5
+ return { error: `Failed to fetch OHLCV for ${args.symbol}` };
6
+ }
7
+ return candles;
8
+ }
@@ -0,0 +1,7 @@
1
+ import type { IExchangeAdapter } from '../exchange-adapter.js';
2
+ import type { CcxtOrder } from '../types.js';
3
+ export declare function fetchOpenOrdersTool(args: {
4
+ symbol?: string;
5
+ }, deps: {
6
+ adapter: IExchangeAdapter;
7
+ }): Promise<CcxtOrder[]>;
@@ -0,0 +1,4 @@
1
+ // Tool: fetch_open_orders — pending orders (paper or live)
2
+ export async function fetchOpenOrdersTool(args, deps) {
3
+ return deps.adapter.getOpenOrders(args.symbol);
4
+ }
@@ -0,0 +1,7 @@
1
+ import type { IExchangeAdapter } from '../exchange-adapter.js';
2
+ import type { CcxtPosition } from '../types.js';
3
+ export declare function fetchPositionsTool(args: {
4
+ symbol?: string;
5
+ }, deps: {
6
+ adapter: IExchangeAdapter;
7
+ }): Promise<CcxtPosition[]>;
@@ -0,0 +1,4 @@
1
+ // Tool: fetch_positions — open positions (paper or live)
2
+ export async function fetchPositionsTool(args, deps) {
3
+ return deps.adapter.getPositions(args.symbol);
4
+ }
@@ -0,0 +1,11 @@
1
+ import type { BinancePublicApi } from '../ccxt/binance-public.js';
2
+ import type { ExchangeSimulator } from '../simulator/exchange-simulator.js';
3
+ import type { CcxtTicker } from '../types.js';
4
+ export declare function fetchTickerTool(args: {
5
+ symbol: string;
6
+ }, deps: {
7
+ binanceApi: BinancePublicApi;
8
+ simulator: ExchangeSimulator;
9
+ }): Promise<CcxtTicker | {
10
+ error: string;
11
+ }>;
@@ -0,0 +1,5 @@
1
+ // Tool: fetch_ticker — real Binance market data via CCXT
2
+ import { fetchCurrentPrice } from './helpers.js';
3
+ export async function fetchTickerTool(args, deps) {
4
+ return fetchCurrentPrice(args.symbol, deps);
5
+ }
@@ -0,0 +1,4 @@
1
+ import type { IntelApiDeps } from './intel-api.js';
2
+ export declare function getAgentProfileTool(deps: IntelApiDeps): Promise<Record<string, unknown> | {
3
+ error: string;
4
+ }>;
@@ -0,0 +1,6 @@
1
+ // Tool: get_agent_profile — returns earned autonomy tier and permissions.
2
+ // The agent checks this at session start to know what it's allowed to adjust.
3
+ import { fetchIntelApi } from './intel-api.js';
4
+ export async function getAgentProfileTool(deps) {
5
+ return fetchIntelApi('/api/agent-profile', deps);
6
+ }