@reefclaw/connect 0.1.10 → 0.1.12

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 (396) hide show
  1. package/assets/bridge/gateway/event-parser.d.ts +6 -1
  2. package/assets/bridge/gateway/event-parser.js +19 -2
  3. package/assets/bridge/gateway/heartbeat-cron.d.ts +27 -0
  4. package/assets/bridge/gateway/heartbeat-cron.js +85 -0
  5. package/assets/bridge/gateway/poller.d.ts +1 -0
  6. package/assets/bridge/gateway/poller.js +14 -2
  7. package/assets/bridge/providers/gateway.d.ts +24 -2
  8. package/assets/bridge/providers/gateway.js +124 -25
  9. package/assets/bridge/setup.js +6 -51
  10. package/assets/shared/signals/conditions/registry.js +11 -2
  11. package/assets/shared/signals/strategy-adapter.js +17 -7
  12. package/assets/skill/SKILL.md +2 -0
  13. package/dist/deps.js +2 -2
  14. package/dist/plugin.js +35 -7
  15. package/package.json +1 -1
  16. package/assets/plugin/audit/mode-transition-audit.d.ts +0 -11
  17. package/assets/plugin/audit/mode-transition-audit.js +0 -29
  18. package/assets/plugin/balance-utils.d.ts +0 -36
  19. package/assets/plugin/balance-utils.js +0 -98
  20. package/assets/plugin/ccxt/binance-ban-gate.d.ts +0 -47
  21. package/assets/plugin/ccxt/binance-ban-gate.js +0 -420
  22. package/assets/plugin/ccxt/binance-private.d.ts +0 -346
  23. package/assets/plugin/ccxt/binance-private.js +0 -1525
  24. package/assets/plugin/ccxt/binance-public.d.ts +0 -34
  25. package/assets/plugin/ccxt/binance-public.js +0 -180
  26. package/assets/plugin/ccxt/intel-public.d.ts +0 -25
  27. package/assets/plugin/ccxt/intel-public.js +0 -80
  28. package/assets/plugin/ccxt/public-market-data-api.d.ts +0 -12
  29. package/assets/plugin/ccxt/public-market-data-api.js +0 -9
  30. package/assets/plugin/config/agent-config-client.d.ts +0 -77
  31. package/assets/plugin/config/agent-config-client.js +0 -188
  32. package/assets/plugin/config/agent-config-poller.d.ts +0 -32
  33. package/assets/plugin/config/agent-config-poller.js +0 -101
  34. package/assets/plugin/config/brackets-config.d.ts +0 -22
  35. package/assets/plugin/config/brackets-config.js +0 -58
  36. package/assets/plugin/config/entitlement-gate.d.ts +0 -51
  37. package/assets/plugin/config/entitlement-gate.js +0 -137
  38. package/assets/plugin/config/gate-store.d.ts +0 -18
  39. package/assets/plugin/config/gate-store.js +0 -61
  40. package/assets/plugin/config/plugin-config-io.d.ts +0 -215
  41. package/assets/plugin/config/plugin-config-io.js +0 -111
  42. package/assets/plugin/config/position-review-config.d.ts +0 -35
  43. package/assets/plugin/config/position-review-config.js +0 -105
  44. package/assets/plugin/config/tool-gate.d.ts +0 -53
  45. package/assets/plugin/config/tool-gate.js +0 -128
  46. package/assets/plugin/config/user-data-stream-config.d.ts +0 -89
  47. package/assets/plugin/config/user-data-stream-config.js +0 -239
  48. package/assets/plugin/connector-supervisor.d.ts +0 -36
  49. package/assets/plugin/connector-supervisor.js +0 -149
  50. package/assets/plugin/exchange-adapter.d.ts +0 -65
  51. package/assets/plugin/exchange-adapter.js +0 -4
  52. package/assets/plugin/index.d.ts +0 -30
  53. package/assets/plugin/index.js +0 -2678
  54. package/assets/plugin/ingest/pending-entry-metadata.d.ts +0 -74
  55. package/assets/plugin/ingest/pending-entry-metadata.js +0 -236
  56. package/assets/plugin/ingest/position-auto-capture.d.ts +0 -103
  57. package/assets/plugin/ingest/position-auto-capture.js +0 -411
  58. package/assets/plugin/ingest/position-decisions-client.d.ts +0 -322
  59. package/assets/plugin/ingest/position-decisions-client.js +0 -296
  60. package/assets/plugin/ingest/readiness-reporter.d.ts +0 -42
  61. package/assets/plugin/ingest/readiness-reporter.js +0 -163
  62. package/assets/plugin/ingest/reconcile-db-vs-exchange.d.ts +0 -13
  63. package/assets/plugin/ingest/reconcile-db-vs-exchange.js +0 -114
  64. package/assets/plugin/ingest/reconciler-cleanup.d.ts +0 -37
  65. package/assets/plugin/ingest/reconciler-cleanup.js +0 -147
  66. package/assets/plugin/ingest/rest-gap-filler.d.ts +0 -191
  67. package/assets/plugin/ingest/rest-gap-filler.js +0 -565
  68. package/assets/plugin/ingest/touched-symbols-store.d.ts +0 -25
  69. package/assets/plugin/ingest/touched-symbols-store.js +0 -96
  70. package/assets/plugin/ingest/trade-store-client.d.ts +0 -40
  71. package/assets/plugin/ingest/trade-store-client.js +0 -125
  72. package/assets/plugin/ingest/ws-ingest.d.ts +0 -43
  73. package/assets/plugin/ingest/ws-ingest.js +0 -126
  74. package/assets/plugin/learning/setup-family.d.ts +0 -21
  75. package/assets/plugin/learning/setup-family.js +0 -103
  76. package/assets/plugin/lifecycle/install-signal-handlers.d.ts +0 -33
  77. package/assets/plugin/lifecycle/install-signal-handlers.js +0 -112
  78. package/assets/plugin/lifecycle/shutdown-coordinator.d.ts +0 -43
  79. package/assets/plugin/lifecycle/shutdown-coordinator.js +0 -131
  80. package/assets/plugin/lifecycle/trading-operation-lock.d.ts +0 -17
  81. package/assets/plugin/lifecycle/trading-operation-lock.js +0 -14
  82. package/assets/plugin/live/bracket-id.d.ts +0 -26
  83. package/assets/plugin/live/bracket-id.js +0 -112
  84. package/assets/plugin/live/bracket-ledger.d.ts +0 -54
  85. package/assets/plugin/live/bracket-ledger.js +0 -267
  86. package/assets/plugin/live/bracket-manager.d.ts +0 -82
  87. package/assets/plugin/live/bracket-manager.js +0 -478
  88. package/assets/plugin/live/bracket-params.d.ts +0 -22
  89. package/assets/plugin/live/bracket-params.js +0 -124
  90. package/assets/plugin/live/bracket-reconciler.d.ts +0 -95
  91. package/assets/plugin/live/bracket-reconciler.js +0 -573
  92. package/assets/plugin/live/bracket-types.d.ts +0 -102
  93. package/assets/plugin/live/bracket-types.js +0 -8
  94. package/assets/plugin/live/deposit-tracker.d.ts +0 -62
  95. package/assets/plugin/live/deposit-tracker.js +0 -97
  96. package/assets/plugin/live/emergency-controls.d.ts +0 -32
  97. package/assets/plugin/live/emergency-controls.js +0 -226
  98. package/assets/plugin/live/exchange-errors.d.ts +0 -12
  99. package/assets/plugin/live/exchange-errors.js +0 -130
  100. package/assets/plugin/live/exchange-info-cache.d.ts +0 -37
  101. package/assets/plugin/live/exchange-info-cache.js +0 -134
  102. package/assets/plugin/live/fact-subscriber.d.ts +0 -78
  103. package/assets/plugin/live/fact-subscriber.js +0 -182
  104. package/assets/plugin/live/intent-journal.d.ts +0 -42
  105. package/assets/plugin/live/intent-journal.js +0 -122
  106. package/assets/plugin/live/listen-key-manager.d.ts +0 -70
  107. package/assets/plugin/live/listen-key-manager.js +0 -169
  108. package/assets/plugin/live/live-adapter.d.ts +0 -287
  109. package/assets/plugin/live/live-adapter.js +0 -1777
  110. package/assets/plugin/live/live-balance-enricher.d.ts +0 -32
  111. package/assets/plugin/live/live-balance-enricher.js +0 -104
  112. package/assets/plugin/live/live-bracket-api.d.ts +0 -13
  113. package/assets/plugin/live/live-bracket-api.js +0 -20
  114. package/assets/plugin/live/live-state-store.d.ts +0 -194
  115. package/assets/plugin/live/live-state-store.js +0 -450
  116. package/assets/plugin/live/local-signal-service.d.ts +0 -57
  117. package/assets/plugin/live/local-signal-service.js +0 -151
  118. package/assets/plugin/live/local-strategy-evaluator.d.ts +0 -62
  119. package/assets/plugin/live/local-strategy-evaluator.js +0 -131
  120. package/assets/plugin/live/microstructure-assembler.d.ts +0 -54
  121. package/assets/plugin/live/microstructure-assembler.js +0 -148
  122. package/assets/plugin/live/order-poller.d.ts +0 -29
  123. package/assets/plugin/live/order-poller.js +0 -125
  124. package/assets/plugin/live/position-state-store.d.ts +0 -83
  125. package/assets/plugin/live/position-state-store.js +0 -237
  126. package/assets/plugin/live/proposal-decision-listener.d.ts +0 -70
  127. package/assets/plugin/live/proposal-decision-listener.js +0 -292
  128. package/assets/plugin/live/proposal-manager.d.ts +0 -76
  129. package/assets/plugin/live/proposal-manager.js +0 -140
  130. package/assets/plugin/live/rate-limiter.d.ts +0 -47
  131. package/assets/plugin/live/rate-limiter.js +0 -159
  132. package/assets/plugin/live/reconciler.d.ts +0 -39
  133. package/assets/plugin/live/reconciler.js +0 -175
  134. package/assets/plugin/live/setup-buckets.d.ts +0 -7
  135. package/assets/plugin/live/setup-buckets.js +0 -33
  136. package/assets/plugin/live/slippage-tracker.d.ts +0 -45
  137. package/assets/plugin/live/slippage-tracker.js +0 -78
  138. package/assets/plugin/live/stop-watcher.d.ts +0 -60
  139. package/assets/plugin/live/stop-watcher.js +0 -215
  140. package/assets/plugin/live/user-data-active-probe.d.ts +0 -54
  141. package/assets/plugin/live/user-data-active-probe.js +0 -180
  142. package/assets/plugin/live/user-data-stream-controller.d.ts +0 -200
  143. package/assets/plugin/live/user-data-stream-controller.js +0 -579
  144. package/assets/plugin/live/user-data-stream-ws.d.ts +0 -22
  145. package/assets/plugin/live/user-data-stream-ws.js +0 -63
  146. package/assets/plugin/live/user-data-stream.d.ts +0 -243
  147. package/assets/plugin/live/user-data-stream.js +0 -704
  148. package/assets/plugin/logger.d.ts +0 -2
  149. package/assets/plugin/logger.js +0 -2
  150. package/assets/plugin/mfe.d.ts +0 -21
  151. package/assets/plugin/mfe.js +0 -68
  152. package/assets/plugin/onboarding/mode-ladder.d.ts +0 -1
  153. package/assets/plugin/onboarding/mode-ladder.js +0 -3
  154. package/assets/plugin/onboarding/runtime.d.ts +0 -84
  155. package/assets/plugin/onboarding/runtime.js +0 -173
  156. package/assets/plugin/openclaw.plugin.json +0 -92
  157. package/assets/plugin/paper-adapter.d.ts +0 -24
  158. package/assets/plugin/paper-adapter.js +0 -91
  159. package/assets/plugin/persistence/state-manager.d.ts +0 -66
  160. package/assets/plugin/persistence/state-manager.js +0 -222
  161. package/assets/plugin/pinned-plan.d.ts +0 -9
  162. package/assets/plugin/pinned-plan.js +0 -23
  163. package/assets/plugin/portfolio/wave9-admission.d.ts +0 -67
  164. package/assets/plugin/portfolio/wave9-admission.js +0 -262
  165. package/assets/plugin/portfolio/wave9-policy.d.ts +0 -36
  166. package/assets/plugin/portfolio/wave9-policy.js +0 -183
  167. package/assets/plugin/risk/pre-trade-check.d.ts +0 -38
  168. package/assets/plugin/risk/pre-trade-check.js +0 -345
  169. package/assets/plugin/risk/pre-trade-types.d.ts +0 -60
  170. package/assets/plugin/risk/pre-trade-types.js +0 -3
  171. package/assets/plugin/shadow/shadow-tracker.d.ts +0 -36
  172. package/assets/plugin/shadow/shadow-tracker.js +0 -151
  173. package/assets/plugin/shadow/types.d.ts +0 -42
  174. package/assets/plugin/shadow/types.js +0 -20
  175. package/assets/plugin/shared/indicators-extended.d.ts +0 -52
  176. package/assets/plugin/shared/indicators-extended.js +0 -291
  177. package/assets/plugin/shared/indicators.d.ts +0 -15
  178. package/assets/plugin/shared/indicators.js +0 -114
  179. package/assets/plugin/signals/conditions/registry.d.ts +0 -16
  180. package/assets/plugin/signals/conditions/registry.js +0 -1324
  181. package/assets/plugin/signals/conditions/types.d.ts +0 -1
  182. package/assets/plugin/signals/conditions/types.js +0 -4
  183. package/assets/plugin/signals/direction-rules.d.ts +0 -3
  184. package/assets/plugin/signals/direction-rules.js +0 -24
  185. package/assets/plugin/signals/entry-rules.d.ts +0 -6
  186. package/assets/plugin/signals/entry-rules.js +0 -33
  187. package/assets/plugin/signals/serialize-context.d.ts +0 -4
  188. package/assets/plugin/signals/serialize-context.js +0 -39
  189. package/assets/plugin/signals/stop-rules.d.ts +0 -3
  190. package/assets/plugin/signals/stop-rules.js +0 -48
  191. package/assets/plugin/signals/strategy-adapter.d.ts +0 -47
  192. package/assets/plugin/signals/strategy-adapter.js +0 -199
  193. package/assets/plugin/signals/types.d.ts +0 -1
  194. package/assets/plugin/signals/types.js +0 -8
  195. package/assets/plugin/simulator/exchange-simulator.d.ts +0 -93
  196. package/assets/plugin/simulator/exchange-simulator.js +0 -689
  197. package/assets/plugin/simulator/fill-engine.d.ts +0 -53
  198. package/assets/plugin/simulator/fill-engine.js +0 -280
  199. package/assets/plugin/simulator/paper-market-feed.d.ts +0 -26
  200. package/assets/plugin/simulator/paper-market-feed.js +0 -104
  201. package/assets/plugin/simulator/realistic-fills.d.ts +0 -59
  202. package/assets/plugin/simulator/realistic-fills.js +0 -175
  203. package/assets/plugin/simulator/types.d.ts +0 -224
  204. package/assets/plugin/simulator/types.js +0 -43
  205. package/assets/plugin/strategy/builtin-strategies.d.ts +0 -2
  206. package/assets/plugin/strategy/builtin-strategies.js +0 -113
  207. package/assets/plugin/strategy/condition-registry.d.ts +0 -3
  208. package/assets/plugin/strategy/condition-registry.js +0 -153
  209. package/assets/plugin/strategy/evaluator.d.ts +0 -70
  210. package/assets/plugin/strategy/evaluator.js +0 -98
  211. package/assets/plugin/tools/assessment-validation.d.ts +0 -120
  212. package/assets/plugin/tools/assessment-validation.js +0 -415
  213. package/assets/plugin/tools/attach-brackets.d.ts +0 -39
  214. package/assets/plugin/tools/attach-brackets.js +0 -399
  215. package/assets/plugin/tools/audit-bracket-protection.d.ts +0 -49
  216. package/assets/plugin/tools/audit-bracket-protection.js +0 -527
  217. package/assets/plugin/tools/cancel-all-orders.d.ts +0 -9
  218. package/assets/plugin/tools/cancel-all-orders.js +0 -8
  219. package/assets/plugin/tools/cancel-order.d.ts +0 -14
  220. package/assets/plugin/tools/cancel-order.js +0 -60
  221. package/assets/plugin/tools/check-position-health.d.ts +0 -46
  222. package/assets/plugin/tools/check-position-health.js +0 -194
  223. package/assets/plugin/tools/clear-exchange-credentials.d.ts +0 -24
  224. package/assets/plugin/tools/clear-exchange-credentials.js +0 -70
  225. package/assets/plugin/tools/close-position.d.ts +0 -45
  226. package/assets/plugin/tools/close-position.js +0 -722
  227. package/assets/plugin/tools/create-order.d.ts +0 -82
  228. package/assets/plugin/tools/create-order.js +0 -1516
  229. package/assets/plugin/tools/exit-gate.d.ts +0 -58
  230. package/assets/plugin/tools/exit-gate.js +0 -162
  231. package/assets/plugin/tools/fetch-balance.d.ts +0 -5
  232. package/assets/plugin/tools/fetch-balance.js +0 -4
  233. package/assets/plugin/tools/fetch-ohlcv.d.ts +0 -11
  234. package/assets/plugin/tools/fetch-ohlcv.js +0 -8
  235. package/assets/plugin/tools/fetch-open-orders.d.ts +0 -7
  236. package/assets/plugin/tools/fetch-open-orders.js +0 -4
  237. package/assets/plugin/tools/fetch-positions.d.ts +0 -7
  238. package/assets/plugin/tools/fetch-positions.js +0 -4
  239. package/assets/plugin/tools/fetch-ticker.d.ts +0 -11
  240. package/assets/plugin/tools/fetch-ticker.js +0 -5
  241. package/assets/plugin/tools/get-agent-profile.d.ts +0 -4
  242. package/assets/plugin/tools/get-agent-profile.js +0 -6
  243. package/assets/plugin/tools/get-analytics.d.ts +0 -6
  244. package/assets/plugin/tools/get-analytics.js +0 -7
  245. package/assets/plugin/tools/get-backtest.d.ts +0 -12
  246. package/assets/plugin/tools/get-backtest.js +0 -91
  247. package/assets/plugin/tools/get-basis.d.ts +0 -7
  248. package/assets/plugin/tools/get-basis.js +0 -7
  249. package/assets/plugin/tools/get-bracket-config.d.ts +0 -11
  250. package/assets/plugin/tools/get-bracket-config.js +0 -24
  251. package/assets/plugin/tools/get-cascade-risk.d.ts +0 -7
  252. package/assets/plugin/tools/get-cascade-risk.js +0 -8
  253. package/assets/plugin/tools/get-crypto-metrics.d.ts +0 -18
  254. package/assets/plugin/tools/get-crypto-metrics.js +0 -62
  255. package/assets/plugin/tools/get-cvd.d.ts +0 -6
  256. package/assets/plugin/tools/get-cvd.js +0 -6
  257. package/assets/plugin/tools/get-divergences.d.ts +0 -6
  258. package/assets/plugin/tools/get-divergences.js +0 -6
  259. package/assets/plugin/tools/get-funding-context.d.ts +0 -6
  260. package/assets/plugin/tools/get-funding-context.js +0 -16
  261. package/assets/plugin/tools/get-liquidation-levels.d.ts +0 -7
  262. package/assets/plugin/tools/get-liquidation-levels.js +0 -7
  263. package/assets/plugin/tools/get-liquidation-pulse.d.ts +0 -9
  264. package/assets/plugin/tools/get-liquidation-pulse.js +0 -22
  265. package/assets/plugin/tools/get-market-breadth.d.ts +0 -6
  266. package/assets/plugin/tools/get-market-breadth.js +0 -8
  267. package/assets/plugin/tools/get-market-intel.d.ts +0 -19
  268. package/assets/plugin/tools/get-market-intel.js +0 -116
  269. package/assets/plugin/tools/get-market-structure.d.ts +0 -47
  270. package/assets/plugin/tools/get-market-structure.js +0 -198
  271. package/assets/plugin/tools/get-my-mined-patterns.d.ts +0 -20
  272. package/assets/plugin/tools/get-my-mined-patterns.js +0 -61
  273. package/assets/plugin/tools/get-my-proposed-learnings.d.ts +0 -20
  274. package/assets/plugin/tools/get-my-proposed-learnings.js +0 -55
  275. package/assets/plugin/tools/get-my-recent-reviews.d.ts +0 -22
  276. package/assets/plugin/tools/get-my-recent-reviews.js +0 -66
  277. package/assets/plugin/tools/get-orderbook.d.ts +0 -21
  278. package/assets/plugin/tools/get-orderbook.js +0 -32
  279. package/assets/plugin/tools/get-pattern-scan.d.ts +0 -7
  280. package/assets/plugin/tools/get-pattern-scan.js +0 -8
  281. package/assets/plugin/tools/get-regime.d.ts +0 -6
  282. package/assets/plugin/tools/get-regime.js +0 -7
  283. package/assets/plugin/tools/get-relevant-learnings.d.ts +0 -21
  284. package/assets/plugin/tools/get-relevant-learnings.js +0 -65
  285. package/assets/plugin/tools/get-resting-liquidity.d.ts +0 -6
  286. package/assets/plugin/tools/get-resting-liquidity.js +0 -11
  287. package/assets/plugin/tools/get-risk-scenario.d.ts +0 -29
  288. package/assets/plugin/tools/get-risk-scenario.js +0 -47
  289. package/assets/plugin/tools/get-risk-summary.d.ts +0 -51
  290. package/assets/plugin/tools/get-risk-summary.js +0 -118
  291. package/assets/plugin/tools/get-sentiment.d.ts +0 -4
  292. package/assets/plugin/tools/get-sentiment.js +0 -6
  293. package/assets/plugin/tools/get-session-review.d.ts +0 -7
  294. package/assets/plugin/tools/get-session-review.js +0 -8
  295. package/assets/plugin/tools/get-setup-detail.d.ts +0 -7
  296. package/assets/plugin/tools/get-setup-detail.js +0 -311
  297. package/assets/plugin/tools/get-signals.d.ts +0 -15
  298. package/assets/plugin/tools/get-signals.js +0 -54
  299. package/assets/plugin/tools/get-sizing.d.ts +0 -6
  300. package/assets/plugin/tools/get-sizing.js +0 -6
  301. package/assets/plugin/tools/get-trade-feedback.d.ts +0 -7
  302. package/assets/plugin/tools/get-trade-feedback.js +0 -8
  303. package/assets/plugin/tools/get-trade-flow.d.ts +0 -7
  304. package/assets/plugin/tools/get-trade-flow.js +0 -7
  305. package/assets/plugin/tools/get-volume-analysis.d.ts +0 -21
  306. package/assets/plugin/tools/get-volume-analysis.js +0 -74
  307. package/assets/plugin/tools/get-volume-profile.d.ts +0 -7
  308. package/assets/plugin/tools/get-volume-profile.js +0 -7
  309. package/assets/plugin/tools/get-wave9-status.d.ts +0 -127
  310. package/assets/plugin/tools/get-wave9-status.js +0 -796
  311. package/assets/plugin/tools/helpers.d.ts +0 -26
  312. package/assets/plugin/tools/helpers.js +0 -39
  313. package/assets/plugin/tools/intel-api.d.ts +0 -34
  314. package/assets/plugin/tools/intel-api.js +0 -119
  315. package/assets/plugin/tools/intel-cache.d.ts +0 -25
  316. package/assets/plugin/tools/intel-cache.js +0 -148
  317. package/assets/plugin/tools/list-strategies.d.ts +0 -17
  318. package/assets/plugin/tools/list-strategies.js +0 -23
  319. package/assets/plugin/tools/modify-stop.d.ts +0 -21
  320. package/assets/plugin/tools/modify-stop.js +0 -120
  321. package/assets/plugin/tools/modify-target.d.ts +0 -21
  322. package/assets/plugin/tools/modify-target.js +0 -110
  323. package/assets/plugin/tools/propose-learning.d.ts +0 -22
  324. package/assets/plugin/tools/propose-learning.js +0 -65
  325. package/assets/plugin/tools/query-review-outcomes.d.ts +0 -30
  326. package/assets/plugin/tools/query-review-outcomes.js +0 -64
  327. package/assets/plugin/tools/query-trades.d.ts +0 -21
  328. package/assets/plugin/tools/query-trades.js +0 -37
  329. package/assets/plugin/tools/record-position-reviews.d.ts +0 -38
  330. package/assets/plugin/tools/record-position-reviews.js +0 -147
  331. package/assets/plugin/tools/save-strategy.d.ts +0 -16
  332. package/assets/plugin/tools/save-strategy.js +0 -46
  333. package/assets/plugin/tools/scan-pairs.d.ts +0 -18
  334. package/assets/plugin/tools/scan-pairs.js +0 -231
  335. package/assets/plugin/tools/score-setup.d.ts +0 -31
  336. package/assets/plugin/tools/score-setup.js +0 -268
  337. package/assets/plugin/tools/set-bracket-requirement.d.ts +0 -18
  338. package/assets/plugin/tools/set-bracket-requirement.js +0 -81
  339. package/assets/plugin/tools/set-exchange-credentials.d.ts +0 -25
  340. package/assets/plugin/tools/set-exchange-credentials.js +0 -80
  341. package/assets/plugin/tools/set-trading-mode.d.ts +0 -26
  342. package/assets/plugin/tools/set-trading-mode.js +0 -135
  343. package/assets/plugin/tools/test-exchange-credentials.d.ts +0 -16
  344. package/assets/plugin/tools/test-exchange-credentials.js +0 -100
  345. package/assets/plugin/tools/toggle-strategy.d.ts +0 -8
  346. package/assets/plugin/tools/toggle-strategy.js +0 -15
  347. package/assets/plugin/trading-params-cache.d.ts +0 -26
  348. package/assets/plugin/trading-params-cache.js +0 -52
  349. package/assets/plugin/types.d.ts +0 -134
  350. package/assets/plugin/types.js +0 -7
  351. package/assets/plugin/util/plugin-paths.d.ts +0 -3
  352. package/assets/plugin/util/plugin-paths.js +0 -15
  353. package/assets/plugin/venues/hyperliquid/hl-balance.d.ts +0 -116
  354. package/assets/plugin/venues/hyperliquid/hl-balance.js +0 -145
  355. package/assets/plugin/venues/hyperliquid/hl-brackets.d.ts +0 -102
  356. package/assets/plugin/venues/hyperliquid/hl-brackets.js +0 -172
  357. package/assets/plugin/venues/hyperliquid/hl-cloid.d.ts +0 -22
  358. package/assets/plugin/venues/hyperliquid/hl-cloid.js +0 -82
  359. package/assets/plugin/venues/hyperliquid/hl-info-cache.d.ts +0 -46
  360. package/assets/plugin/venues/hyperliquid/hl-info-cache.js +0 -125
  361. package/assets/plugin/venues/hyperliquid/hl-live-adapter.d.ts +0 -88
  362. package/assets/plugin/venues/hyperliquid/hl-live-adapter.js +0 -353
  363. package/assets/plugin/venues/hyperliquid/hl-precision.d.ts +0 -61
  364. package/assets/plugin/venues/hyperliquid/hl-precision.js +0 -176
  365. package/assets/plugin/venues/hyperliquid/hl-private.d.ts +0 -88
  366. package/assets/plugin/venues/hyperliquid/hl-private.js +0 -357
  367. package/assets/plugin/venues/hyperliquid/hl-public.d.ts +0 -79
  368. package/assets/plugin/venues/hyperliquid/hl-public.js +0 -429
  369. package/assets/plugin/venues/hyperliquid/hl-rate-gate.d.ts +0 -57
  370. package/assets/plugin/venues/hyperliquid/hl-rate-gate.js +0 -220
  371. package/assets/plugin/venues/hyperliquid/hl-user-stream.d.ts +0 -90
  372. package/assets/plugin/venues/hyperliquid/hl-user-stream.js +0 -220
  373. package/assets/plugin/venues/registry.d.ts +0 -38
  374. package/assets/plugin/venues/registry.js +0 -46
  375. package/assets/plugin/venues/symbols.d.ts +0 -43
  376. package/assets/plugin/venues/symbols.js +0 -107
  377. package/assets/plugin/wave9/live-account-capture.d.ts +0 -67
  378. package/assets/plugin/wave9/live-account-capture.js +0 -435
  379. package/assets/plugin/wave9/live-autonomous-protection.d.ts +0 -39
  380. package/assets/plugin/wave9/live-autonomous-protection.js +0 -112
  381. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.d.ts +0 -33
  382. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.js +0 -115
  383. package/assets/plugin/wave9/live-execution-ledger.d.ts +0 -107
  384. package/assets/plugin/wave9/live-execution-ledger.js +0 -498
  385. package/assets/plugin/wave9/live-position-confirmation.d.ts +0 -18
  386. package/assets/plugin/wave9/live-position-confirmation.js +0 -111
  387. package/assets/plugin/wave9/live-residual-protection.d.ts +0 -18
  388. package/assets/plugin/wave9/live-residual-protection.js +0 -250
  389. package/assets/plugin/wave9/live-startup-reconciliation.d.ts +0 -38
  390. package/assets/plugin/wave9/live-startup-reconciliation.js +0 -454
  391. package/assets/plugin/wave9/live-symbol-ownership.d.ts +0 -20
  392. package/assets/plugin/wave9/live-symbol-ownership.js +0 -132
  393. package/assets/plugin/wave9/paper-admission-guard.d.ts +0 -199
  394. package/assets/plugin/wave9/paper-admission-guard.js +0 -650
  395. package/assets/plugin/wave9/usdm-evidence-provider.d.ts +0 -42
  396. package/assets/plugin/wave9/usdm-evidence-provider.js +0 -133
@@ -1,1324 +0,0 @@
1
- // ⚠️ GENERATED FILE — DO NOT EDIT.
2
- // Canonical source of truth: shared/src/signals/conditions/registry.ts
3
- // Regenerate: node scripts/sync-shared-code.mjs (enforced by shared-code-sync.test.ts)
4
- //
5
- // This copy exists because this package builds with tsc and deploys as a
6
- // self-contained tree that strips workspace deps, so it cannot import
7
- // @reefclaw/shared runtime code across the deploy boundary.
8
- // Condition registry — all reusable condition functions for declarative strategies.
9
- // Each condition is a pure function: (ctx, params, direction, condCtx) => ConditionResult.
10
- import { computeEMA, computeRSI, linearSlope, findSwingPoints, mean } from '../../shared/indicators.js';
11
- import { computeMACD, computeBollingerBands, computeVWAP, computeStochRSI, computeADX, computeIchimoku, computeSupertrend, } from '../../shared/indicators-extended.js';
12
- // ─── Condition Registry ────────────────────────────────────────────────
13
- const REGISTRY = new Map();
14
- function register(type, fn) {
15
- REGISTRY.set(type, fn);
16
- }
17
- export function getConditionFn(type) {
18
- return REGISTRY.get(type);
19
- }
20
- export function hasCondition(type) {
21
- return REGISTRY.has(type);
22
- }
23
- export function listConditionTypes() {
24
- return Array.from(REGISTRY.keys());
25
- }
26
- /** Evaluate an array of condition configs. Returns SignalCondition[] + shared context. */
27
- export function evaluateConditions(configs, ctx, direction) {
28
- const condCtx = {};
29
- const conditions = configs.map(cfg => {
30
- const fn = REGISTRY.get(cfg.type);
31
- if (!fn) {
32
- return { name: cfg.type, met: false, currentValue: 0, threshold: 0, description: `Unknown condition: ${cfg.type}` };
33
- }
34
- const result = fn(ctx, cfg.params, direction, condCtx);
35
- return { name: cfg.type, met: result.met, currentValue: result.value, threshold: result.threshold, description: result.description };
36
- });
37
- return { conditions, condCtx };
38
- }
39
- // ─── 1. ema_proximity ──────────────────────────────────────────────────
40
- // Price within X% of EMA(N)
41
- register('ema_proximity', (ctx, params, _dir, condCtx) => {
42
- const period = params.period ?? 21;
43
- const maxDistPct = params.maxDistPct ?? 0.003;
44
- const closes = ctx.ohlcv1h.map(b => b.close);
45
- const ema = computeEMA(closes, period);
46
- const dist = Math.abs(ctx.currentPrice - ema) / ema;
47
- const met = dist <= maxDistPct;
48
- // Store EMA for entry rule
49
- condCtx.ema21 = period === 21 ? ema : condCtx.ema21;
50
- return {
51
- met,
52
- value: Math.round(dist * 10000) / 100,
53
- threshold: maxDistPct * 100,
54
- description: met
55
- ? `Price within ${(dist * 100).toFixed(2)}% of EMA(${period})`
56
- : `Price ${(dist * 100).toFixed(2)}% from EMA(${period}) (need ≤${maxDistPct * 100}%)`,
57
- };
58
- });
59
- // ─── 2. orderbook_imbalance ────────────────────────────────────────────
60
- // OB imbalance favors direction; auto-passes in backtest mode
61
- register('orderbook_imbalance', (ctx, params, direction) => {
62
- const threshold = params.threshold ?? 0.6;
63
- if (ctx.backtestMode) {
64
- return { met: true, value: 50, threshold: threshold * 100, description: 'Skipped (no historical orderbook)' };
65
- }
66
- const met = direction === 'LONG'
67
- ? ctx.obImbalance > threshold
68
- : direction === 'SHORT'
69
- ? ctx.obImbalance < 1 - threshold
70
- : Math.abs(ctx.obImbalance - 0.5) > (threshold - 0.5);
71
- return {
72
- met,
73
- value: Math.round(ctx.obImbalance * 100),
74
- threshold: threshold * 100,
75
- description: met
76
- ? `Orderbook ${direction === 'LONG' ? 'bid' : 'ask'} imbalance ${(ctx.obImbalance * 100).toFixed(0)}%`
77
- : `Orderbook imbalance ${(ctx.obImbalance * 100).toFixed(0)}% (need ${direction === 'LONG' ? '>' : '<'}${threshold * 100}%)`,
78
- };
79
- });
80
- // ─── 3. funding_not_crowded ────────────────────────────────────────────
81
- // Funding z-score within bounds (not overcrowded)
82
- register('funding_not_crowded', (ctx, params) => {
83
- const maxZScore = params.maxZScore ?? 2;
84
- const zScore = ctx.fundingStd > 0
85
- ? Math.abs((ctx.fundingRates[0] ?? 0) - ctx.fundingMean) / ctx.fundingStd
86
- : 0;
87
- const met = zScore < maxZScore;
88
- return {
89
- met,
90
- value: Math.round(zScore * 100) / 100,
91
- threshold: maxZScore,
92
- description: met
93
- ? `Funding z-score ${zScore.toFixed(2)} (not crowded)`
94
- : `Funding z-score ${zScore.toFixed(2)} (crowded, need <${maxZScore}σ)`,
95
- };
96
- });
97
- // ─── 4. oi_slope ───────────────────────────────────────────────────────
98
- // OI slope over N slots is >= minSlope (steady or rising)
99
- register('oi_slope', (ctx, params) => {
100
- const lookbackSlots = params.lookbackSlots ?? 16;
101
- const minSlope = params.minSlope ?? 0;
102
- const oiRecent = ctx.oiValues.slice(0, lookbackSlots).reverse();
103
- const slope = oiRecent.length >= 4 ? linearSlope(oiRecent) : 0;
104
- const met = slope >= minSlope;
105
- return {
106
- met,
107
- value: Math.round(slope * 100) / 100,
108
- threshold: minSlope,
109
- description: met
110
- ? `OI slope ${slope >= 0 ? '+' : ''}${slope.toFixed(2)} (steady/rising)`
111
- : `OI slope ${slope.toFixed(2)} (declining)`,
112
- };
113
- });
114
- // ─── 5. no_liquidation_cluster ─────────────────────────────────────────
115
- // No major liq cluster near entry; auto-passes in backtest mode
116
- register('no_liquidation_cluster', (ctx, params) => {
117
- const maxUsd = params.maxUsd ?? 500_000;
118
- if (ctx.backtestMode) {
119
- return { met: true, value: 0, threshold: maxUsd, description: 'Skipped (no historical liquidation data)' };
120
- }
121
- const met = ctx.liqNearEntry < maxUsd;
122
- return {
123
- met,
124
- value: Math.round(ctx.liqNearEntry),
125
- threshold: maxUsd,
126
- description: met
127
- ? `Liquidations near entry $${(ctx.liqNearEntry / 1000).toFixed(0)}k (below threshold)`
128
- : `Liquidation cluster $${(ctx.liqNearEntry / 1000).toFixed(0)}k near entry (threshold $${(maxUsd / 1000).toFixed(0)}k)`,
129
- };
130
- });
131
- // ─── 6. price_sweep ────────────────────────────────────────────────────
132
- // Price swept a swing high/low on 5m, then reversed
133
- register('price_sweep', (ctx, params, _dir, condCtx) => {
134
- const swingLookback = params.swingLookback ?? 5;
135
- const recentBarCount = params.recentBars ?? 10;
136
- const bars5m = ctx.ohlcv5m;
137
- if (bars5m.length < 50) {
138
- return { met: false, value: 0, threshold: 0, description: 'Insufficient 5m data' };
139
- }
140
- const lookbackBars = bars5m.slice(0, -recentBarCount);
141
- const recentBars = bars5m.slice(-recentBarCount);
142
- const swings = findSwingPoints(lookbackBars, swingLookback);
143
- const latestClose = recentBars[recentBars.length - 1].close;
144
- const recentLow = Math.min(...recentBars.map(b => b.low));
145
- const recentHigh = Math.max(...recentBars.map(b => b.high));
146
- let sweptLow = false;
147
- let sweptHigh = false;
148
- let sweepLevel = 0;
149
- if (swings.lows.length > 0) {
150
- const nearestLow = Math.min(...swings.lows.slice(-5));
151
- if (recentLow < nearestLow && latestClose > nearestLow) {
152
- sweptLow = true;
153
- sweepLevel = nearestLow;
154
- }
155
- }
156
- if (!sweptLow && swings.highs.length > 0) {
157
- const nearestHigh = Math.max(...swings.highs.slice(-5));
158
- if (recentHigh > nearestHigh && latestClose < nearestHigh) {
159
- sweptHigh = true;
160
- sweepLevel = nearestHigh;
161
- }
162
- }
163
- // Store in shared context for entry/stop rules and direction
164
- condCtx.sweepLevel = sweepLevel;
165
- condCtx.recentLow = recentLow;
166
- condCtx.recentHigh = recentHigh;
167
- condCtx.sweptDirection = sweptLow ? 'LONG' : sweptHigh ? 'SHORT' : null;
168
- const met = sweptLow || sweptHigh;
169
- return {
170
- met,
171
- value: sweepLevel,
172
- threshold: 0,
173
- description: sweptLow
174
- ? `Swept swing low at ${sweepLevel.toFixed(2)}, reversed above`
175
- : sweptHigh
176
- ? `Swept swing high at ${sweepLevel.toFixed(2)}, reversed below`
177
- : 'No sweep detected',
178
- };
179
- });
180
- // ─── 7. liquidations_at_sweep ──────────────────────────────────────────
181
- // Liquidation volume at sweep level above minimum; auto-passes in backtest
182
- register('liquidations_at_sweep', (ctx, params) => {
183
- const minUsd = params.minUsd ?? 200_000;
184
- if (ctx.backtestMode) {
185
- return { met: true, value: 0, threshold: minUsd, description: 'Skipped (no historical liquidation data)' };
186
- }
187
- const liq = ctx.liqNearEntry;
188
- const met = liq > minUsd;
189
- return {
190
- met,
191
- value: Math.round(liq),
192
- threshold: minUsd,
193
- description: met
194
- ? `$${(liq / 1000).toFixed(0)}k liquidations at sweep level`
195
- : `Only $${(liq / 1000).toFixed(0)}k liquidations (need >${(minUsd / 1000).toFixed(0)}k)`,
196
- };
197
- });
198
- // ─── 8. order_flow_absorption ──────────────────────────────────────────
199
- // High volume but small net delta = absorption
200
- register('order_flow_absorption', (ctx, params) => {
201
- const maxRatio = params.maxRatio ?? 0.3;
202
- const totalBuy = ctx.tradeFlow.reduce((s, b) => s + b.buyVolume, 0);
203
- const totalSell = ctx.tradeFlow.reduce((s, b) => s + b.sellVolume, 0);
204
- const totalVolume = totalBuy + totalSell;
205
- const netDelta = Math.abs(totalBuy - totalSell);
206
- const ratio = totalVolume > 0 ? netDelta / totalVolume : 1;
207
- const met = ratio < maxRatio;
208
- return {
209
- met,
210
- value: Math.round(ratio * 100),
211
- threshold: maxRatio * 100,
212
- description: met
213
- ? `Absorption detected: delta/volume ratio ${(ratio * 100).toFixed(0)}%`
214
- : `No absorption: delta/volume ratio ${(ratio * 100).toFixed(0)}% (need <${(maxRatio * 100).toFixed(0)}%)`,
215
- };
216
- });
217
- // ─── 9. funding_contrarian ─────────────────────────────────────────────
218
- // Funding not extreme in the direction of trade (direction-aware)
219
- register('funding_contrarian', (ctx, params, direction) => {
220
- const maxZScore = params.maxZScore ?? 1.5;
221
- const currentFunding = ctx.fundingRates[0] ?? 0;
222
- const zScore = ctx.fundingStd > 0 ? (currentFunding - ctx.fundingMean) / ctx.fundingStd : 0;
223
- const met = direction === 'LONG'
224
- ? zScore > -maxZScore
225
- : direction === 'SHORT'
226
- ? zScore < maxZScore
227
- : Math.abs(zScore) < maxZScore;
228
- return {
229
- met,
230
- value: Math.round(zScore * 100) / 100,
231
- threshold: maxZScore,
232
- description: met
233
- ? `Funding z-score ${zScore.toFixed(2)} (neutral/contrarian)`
234
- : `Funding z-score ${zScore.toFixed(2)} (aligned with crowd)`,
235
- };
236
- });
237
- // ─── 10. funding_extreme ───────────────────────────────────────────────
238
- // Funding > Nσ or < -Nσ from mean (for mean reversion setups)
239
- // Also determines direction: extreme positive → SHORT, extreme negative → LONG
240
- register('funding_extreme', (ctx, params, _dir, condCtx) => {
241
- const minZScore = params.minZScore ?? 3;
242
- const currentFunding = ctx.fundingRates[0] ?? 0;
243
- const zScore = ctx.fundingStd > 0 ? (currentFunding - ctx.fundingMean) / ctx.fundingStd : 0;
244
- const isExtremePositive = zScore > minZScore;
245
- const isExtremeNegative = zScore < -minZScore;
246
- const met = isExtremePositive || isExtremeNegative;
247
- // Store direction for from_funding rule
248
- if (isExtremePositive)
249
- condCtx.sweptDirection = 'SHORT';
250
- else if (isExtremeNegative)
251
- condCtx.sweptDirection = 'LONG';
252
- return {
253
- met,
254
- value: Math.round(zScore * 100) / 100,
255
- threshold: minZScore,
256
- description: met
257
- ? `Funding z-score ${zScore.toFixed(2)} (extreme ${isExtremePositive ? 'positive' : 'negative'})`
258
- : `Funding z-score ${zScore.toFixed(2)} (need >${minZScore}σ or <-${minZScore}σ)`,
259
- };
260
- });
261
- // ─── 11. oi_elevated ───────────────────────────────────────────────────
262
- // OI above N× its average (elevated positioning)
263
- register('oi_elevated', (ctx, params) => {
264
- const elevationRatio = params.elevationRatio ?? 1.1;
265
- const oiCurrent = ctx.oiValues[0] ?? 0;
266
- const oiAvg = ctx.oiValues.length > 10 ? mean(ctx.oiValues) : oiCurrent;
267
- const ratio = oiAvg > 0 ? oiCurrent / oiAvg : 1;
268
- const met = ratio > elevationRatio;
269
- return {
270
- met,
271
- value: Math.round(ratio * 100) / 100,
272
- threshold: elevationRatio,
273
- description: met
274
- ? `OI ${((ratio - 1) * 100).toFixed(0)}% above average (crowded positioning)`
275
- : `OI only ${((ratio - 1) * 100).toFixed(0)}% vs average (need >${((elevationRatio - 1) * 100).toFixed(0)}%)`,
276
- };
277
- });
278
- // ─── 12. price_at_level ────────────────────────────────────────────────
279
- // Price near a technically significant level (EMA50, EMA200, or swing)
280
- register('price_at_level', (ctx, params) => {
281
- const maxDistPct = params.maxDistPct ?? 0.005;
282
- const closes = ctx.ohlcv1h.map(b => b.close);
283
- const ema50 = computeEMA(closes, params.emaPeriod1 ?? 50);
284
- const ema200 = computeEMA(closes, params.emaPeriod2 ?? 200);
285
- const swingLookbackBars = params.swingLookbackBars ?? 48;
286
- const swingLookback = params.swingLookback ?? 3;
287
- const swings = findSwingPoints(ctx.ohlcv1h.slice(-swingLookbackBars), swingLookback);
288
- const distToEma50 = Math.abs(ctx.currentPrice - ema50) / ema50;
289
- const distToEma200 = Math.abs(ctx.currentPrice - ema200) / ema200;
290
- const distToSwing = Math.min(...swings.highs.slice(-5).map(h => Math.abs(ctx.currentPrice - h) / ctx.currentPrice), ...swings.lows.slice(-5).map(l => Math.abs(ctx.currentPrice - l) / ctx.currentPrice), 1);
291
- const closestDist = Math.min(distToEma50, distToEma200, distToSwing);
292
- const met = closestDist < maxDistPct;
293
- return {
294
- met,
295
- value: Math.round(closestDist * 10000) / 100,
296
- threshold: maxDistPct * 10000 / 100,
297
- description: met
298
- ? `Price within ${(closestDist * 100).toFixed(2)}% of significant level`
299
- : `Price ${(closestDist * 100).toFixed(2)}% from nearest level (need ≤${(maxDistPct * 100).toFixed(1)}%)`,
300
- };
301
- });
302
- // ─── 13. macd_crossover ──────────────────────────────────────────────────
303
- // MACD line crossed signal in trade direction
304
- register('macd_crossover', (ctx, params, direction) => {
305
- const closes = ctx.ohlcv1h.map(b => b.close);
306
- const macd = computeMACD(closes, params.fast ?? 12, params.slow ?? 26, params.signal ?? 9);
307
- const met = direction === 'LONG'
308
- ? macd.crossover === 'bullish'
309
- : direction === 'SHORT'
310
- ? macd.crossover === 'bearish'
311
- : macd.crossover !== 'none';
312
- return {
313
- met,
314
- value: Math.round(macd.histogram * 100) / 100,
315
- threshold: 0,
316
- description: met
317
- ? `MACD ${macd.crossover} crossover (histogram ${macd.histogram.toFixed(2)})`
318
- : `No MACD crossover in trade direction (histogram ${macd.histogram.toFixed(2)})`,
319
- };
320
- });
321
- // ─── 14. macd_divergence ─────────────────────────────────────────────────
322
- // Price vs MACD divergence (trend weakening)
323
- register('macd_divergence', (ctx) => {
324
- const closes = ctx.ohlcv1h.map(b => b.close);
325
- const macd = computeMACD(closes, 12, 26, 9);
326
- if (closes.length < 20) {
327
- return { met: false, value: 0, threshold: 0, description: 'Insufficient data for divergence' };
328
- }
329
- const recent = closes.slice(-20);
330
- const priceUp = recent[recent.length - 1] > recent[0];
331
- const histDown = macd.histogram < 0;
332
- const priceDown = recent[recent.length - 1] < recent[0];
333
- const histUp = macd.histogram > 0;
334
- const met = (priceUp && histDown) || (priceDown && histUp);
335
- return {
336
- met,
337
- value: Math.round(macd.histogram * 100) / 100,
338
- threshold: 0,
339
- description: met
340
- ? `MACD divergence: price ${priceUp ? 'rising' : 'falling'} vs histogram ${histUp ? 'positive' : 'negative'}`
341
- : 'No MACD divergence detected',
342
- };
343
- });
344
- // ─── 15. bollinger_squeeze ───────────────────────────────────────────────
345
- // Bollinger bandwidth below threshold (compression)
346
- register('bollinger_squeeze', (ctx, params) => {
347
- const closes = ctx.ohlcv1h.map(b => b.close);
348
- const bb = computeBollingerBands(closes, params.period ?? 20, params.stdDev ?? 2);
349
- const maxBandwidth = params.maxBandwidth ?? 3.0;
350
- const met = bb.bandwidth < maxBandwidth;
351
- return {
352
- met,
353
- value: Math.round(bb.bandwidth * 100) / 100,
354
- threshold: maxBandwidth,
355
- description: met
356
- ? `Bollinger squeeze: bandwidth ${bb.bandwidth.toFixed(2)}% (compressed)`
357
- : `Bollinger bandwidth ${bb.bandwidth.toFixed(2)}% (need <${maxBandwidth}%)`,
358
- };
359
- });
360
- // ─── 16. bollinger_breakout ──────────────────────────────────────────────
361
- // Price closes outside Bollinger band
362
- register('bollinger_breakout', (ctx, params, direction) => {
363
- const closes = ctx.ohlcv1h.map(b => b.close);
364
- const bb = computeBollingerBands(closes, params.period ?? 20, params.stdDev ?? 2);
365
- const met = direction === 'LONG'
366
- ? bb.percentB > 1
367
- : direction === 'SHORT'
368
- ? bb.percentB < 0
369
- : bb.percentB > 1 || bb.percentB < 0;
370
- return {
371
- met,
372
- value: Math.round(bb.percentB * 100) / 100,
373
- threshold: direction === 'SHORT' ? 0 : 1,
374
- description: met
375
- ? `Bollinger breakout: %B=${bb.percentB.toFixed(2)} (outside band)`
376
- : `Price inside Bollinger bands: %B=${bb.percentB.toFixed(2)}`,
377
- };
378
- });
379
- // ─── 17. vwap_position ───────────────────────────────────────────────────
380
- // Price above/below VWAP aligns with direction
381
- register('vwap_position', (ctx, params, direction) => {
382
- const bars = ctx.ohlcv1h.slice(-24).map(b => ({
383
- open: b.open, high: b.high, low: b.low, close: b.close, volume: b.volume,
384
- }));
385
- const vwap = computeVWAP(bars);
386
- const above = ctx.currentPrice > vwap;
387
- const met = direction === 'LONG' ? above : direction === 'SHORT' ? !above : true;
388
- const distPct = vwap > 0 ? ((ctx.currentPrice - vwap) / vwap) * 100 : 0;
389
- return {
390
- met,
391
- value: Math.round(distPct * 100) / 100,
392
- threshold: 0,
393
- description: met
394
- ? `Price ${above ? 'above' : 'below'} VWAP (${distPct.toFixed(2)}%)`
395
- : `Price on wrong side of VWAP for ${direction} (${distPct.toFixed(2)}%)`,
396
- };
397
- });
398
- // ─── 18. stoch_rsi_extreme ───────────────────────────────────────────────
399
- // StochRSI in extreme zone
400
- register('stoch_rsi_extreme', (ctx, params, direction) => {
401
- const closes = ctx.ohlcv1h.map(b => b.close);
402
- const sr = computeStochRSI(closes, 14, 14, 3, 3);
403
- const oversoldThreshold = params.oversold ?? 20;
404
- const overboughtThreshold = params.overbought ?? 80;
405
- const met = direction === 'LONG'
406
- ? sr.k < oversoldThreshold
407
- : direction === 'SHORT'
408
- ? sr.k > overboughtThreshold
409
- : sr.k < oversoldThreshold || sr.k > overboughtThreshold;
410
- return {
411
- met,
412
- value: sr.k,
413
- threshold: direction === 'SHORT' ? overboughtThreshold : oversoldThreshold,
414
- description: met
415
- ? `StochRSI extreme: K=${sr.k} (${sr.k < 50 ? 'oversold' : 'overbought'})`
416
- : `StochRSI K=${sr.k} (not extreme)`,
417
- };
418
- });
419
- // ─── 19. adx_trending ────────────────────────────────────────────────────
420
- // ADX above threshold (trending market)
421
- register('adx_trending', (ctx, params) => {
422
- const closes = ctx.ohlcv1h.map(b => b.close);
423
- const highs = ctx.ohlcv1h.map(b => b.high);
424
- const lows = ctx.ohlcv1h.map(b => b.low);
425
- const adx = computeADX(highs, lows, closes, params.period ?? 14);
426
- const minAdx = params.minAdx ?? 25;
427
- const met = adx.adx > minAdx;
428
- return {
429
- met,
430
- value: adx.adx,
431
- threshold: minAdx,
432
- description: met
433
- ? `ADX ${adx.adx} (trending, +DI=${adx.plusDI} -DI=${adx.minusDI})`
434
- : `ADX ${adx.adx} (not trending, need >${minAdx})`,
435
- };
436
- });
437
- // ─── 20. adx_ranging ─────────────────────────────────────────────────────
438
- // ADX below threshold (ranging market)
439
- register('adx_ranging', (ctx, params) => {
440
- const closes = ctx.ohlcv1h.map(b => b.close);
441
- const highs = ctx.ohlcv1h.map(b => b.high);
442
- const lows = ctx.ohlcv1h.map(b => b.low);
443
- const adx = computeADX(highs, lows, closes, params.period ?? 14);
444
- const maxAdx = params.maxAdx ?? 20;
445
- const met = adx.adx < maxAdx;
446
- return {
447
- met,
448
- value: adx.adx,
449
- threshold: maxAdx,
450
- description: met
451
- ? `ADX ${adx.adx} (ranging, choppy market)`
452
- : `ADX ${adx.adx} (too strong for range, need <${maxAdx})`,
453
- };
454
- });
455
- // ─── 21. ichimoku_cloud ──────────────────────────────────────────────────
456
- // Price position relative to Ichimoku cloud + TK cross
457
- register('ichimoku_cloud', (ctx, params, direction) => {
458
- const closes = ctx.ohlcv1h.map(b => b.close);
459
- const highs = ctx.ohlcv1h.map(b => b.high);
460
- const lows = ctx.ohlcv1h.map(b => b.low);
461
- const ichi = computeIchimoku(highs, lows, closes, 9, 26, 52);
462
- const bullish = ichi.cloudPosition === 'above' && ichi.tenkan > ichi.kijun;
463
- const bearish = ichi.cloudPosition === 'below' && ichi.tenkan < ichi.kijun;
464
- const met = direction === 'LONG' ? bullish : direction === 'SHORT' ? bearish : bullish || bearish;
465
- return {
466
- met,
467
- value: ichi.cloudPosition === 'above' ? 1 : ichi.cloudPosition === 'below' ? -1 : 0,
468
- threshold: 0,
469
- description: met
470
- ? `Ichimoku ${bullish ? 'bullish' : 'bearish'}: ${ichi.cloudPosition} cloud, TK ${ichi.tenkan > ichi.kijun ? 'bull' : 'bear'} cross`
471
- : `Ichimoku neutral: ${ichi.cloudPosition} cloud`,
472
- };
473
- });
474
- // ─── 22. obv_divergence ──────────────────────────────────────────────────
475
- // OBV diverges from price
476
- register('obv_divergence', (ctx, params, direction) => {
477
- const closes = ctx.ohlcv1h.map(b => b.close);
478
- const volumes = ctx.ohlcv1h.map(b => b.volume);
479
- if (closes.length < 20) {
480
- return { met: false, value: 0, threshold: 0, description: 'Insufficient data' };
481
- }
482
- const recentCloses = closes.slice(-20);
483
- const recentVolumes = volumes.slice(-20);
484
- let obv = 0;
485
- const obvSeries = [0];
486
- for (let i = 1; i < recentCloses.length; i++) {
487
- if (recentCloses[i] > recentCloses[i - 1])
488
- obv += recentVolumes[i];
489
- else if (recentCloses[i] < recentCloses[i - 1])
490
- obv -= recentVolumes[i];
491
- obvSeries.push(obv);
492
- }
493
- const priceSlope = linearSlope(recentCloses);
494
- const obvSlope = linearSlope(obvSeries);
495
- const bullishDiv = priceSlope < 0 && obvSlope > 0;
496
- const bearishDiv = priceSlope > 0 && obvSlope < 0;
497
- const met = direction === 'LONG' ? bullishDiv : direction === 'SHORT' ? bearishDiv : bullishDiv || bearishDiv;
498
- return {
499
- met,
500
- value: Math.round(obvSlope * 100) / 100,
501
- threshold: 0,
502
- description: met
503
- ? `OBV ${bullishDiv ? 'bullish' : 'bearish'} divergence (accumulation/distribution)`
504
- : 'No OBV divergence detected',
505
- };
506
- });
507
- // ─── 24. cvd_divergence_bullish ──────────────────────────────────────────
508
- // CVD bullish divergence detected (price lower low + CVD higher low)
509
- register('cvd_divergence_bullish', (ctx, _params, _dir, condCtx) => {
510
- if (!ctx.cvd) {
511
- if (ctx.backtestMode)
512
- return { met: true, value: 0, threshold: 0, description: 'Skipped (no CVD data in backtest)' };
513
- return { met: false, value: 0, threshold: 0, description: 'No CVD data available' };
514
- }
515
- const met = ctx.cvd.cvdDivergence === 'bullish';
516
- if (met)
517
- condCtx.sweptDirection = condCtx.sweptDirection ?? 'LONG';
518
- return {
519
- met,
520
- value: Math.round(ctx.cvd.cvdDivergenceStrength * 100),
521
- threshold: 0,
522
- description: met
523
- ? `CVD bullish divergence (strength ${(ctx.cvd.cvdDivergenceStrength * 100).toFixed(0)}%)`
524
- : 'No CVD bullish divergence',
525
- };
526
- });
527
- // ─── 25. cvd_divergence_bearish ─────────────────────────────────────────
528
- // CVD bearish divergence detected (price higher high + CVD lower high)
529
- register('cvd_divergence_bearish', (ctx, _params, _dir, condCtx) => {
530
- if (!ctx.cvd) {
531
- if (ctx.backtestMode)
532
- return { met: true, value: 0, threshold: 0, description: 'Skipped (no CVD data in backtest)' };
533
- return { met: false, value: 0, threshold: 0, description: 'No CVD data available' };
534
- }
535
- const met = ctx.cvd.cvdDivergence === 'bearish';
536
- if (met)
537
- condCtx.sweptDirection = condCtx.sweptDirection ?? 'SHORT';
538
- return {
539
- met,
540
- value: Math.round(ctx.cvd.cvdDivergenceStrength * 100),
541
- threshold: 0,
542
- description: met
543
- ? `CVD bearish divergence (strength ${(ctx.cvd.cvdDivergenceStrength * 100).toFixed(0)}%)`
544
- : 'No CVD bearish divergence',
545
- };
546
- });
547
- // ─── 26. cvd_positive_slope ─────────────────────────────────────────────
548
- // CVD slope is positive (buying pressure increasing)
549
- register('cvd_positive_slope', (ctx, params) => {
550
- if (!ctx.cvd) {
551
- if (ctx.backtestMode)
552
- return { met: true, value: 0, threshold: 0, description: 'Skipped (no CVD data in backtest)' };
553
- return { met: false, value: 0, threshold: 0, description: 'No CVD data available' };
554
- }
555
- const minSlope = params.minSlope ?? 0;
556
- const met = ctx.cvd.cvdSlope1h > minSlope;
557
- return {
558
- met,
559
- value: Math.round(ctx.cvd.cvdSlope1h * 100) / 100,
560
- threshold: minSlope,
561
- description: met
562
- ? `CVD slope positive: ${ctx.cvd.cvdSlope1h.toFixed(2)} (buying pressure increasing)`
563
- : `CVD slope ${ctx.cvd.cvdSlope1h.toFixed(2)} (buying pressure weak)`,
564
- };
565
- });
566
- // ─── 27. cvd_negative_slope ─────────────────────────────────────────────
567
- // CVD slope is negative (selling pressure increasing)
568
- register('cvd_negative_slope', (ctx, params) => {
569
- if (!ctx.cvd) {
570
- if (ctx.backtestMode)
571
- return { met: true, value: 0, threshold: 0, description: 'Skipped (no CVD data in backtest)' };
572
- return { met: false, value: 0, threshold: 0, description: 'No CVD data available' };
573
- }
574
- const maxSlope = params.maxSlope ?? 0;
575
- const met = ctx.cvd.cvdSlope1h < maxSlope;
576
- return {
577
- met,
578
- value: Math.round(ctx.cvd.cvdSlope1h * 100) / 100,
579
- threshold: maxSlope,
580
- description: met
581
- ? `CVD slope negative: ${ctx.cvd.cvdSlope1h.toFixed(2)} (selling pressure increasing)`
582
- : `CVD slope ${ctx.cvd.cvdSlope1h.toFixed(2)} (selling pressure weak)`,
583
- };
584
- });
585
- // ─── 28. cvd_extreme ────────────────────────────────────────────────────
586
- // CVD z-score at extreme (>2σ = exhaustion signal)
587
- register('cvd_extreme', (ctx, params) => {
588
- if (!ctx.cvd) {
589
- if (ctx.backtestMode)
590
- return { met: true, value: 0, threshold: 0, description: 'Skipped (no CVD data in backtest)' };
591
- return { met: false, value: 0, threshold: 0, description: 'No CVD data available' };
592
- }
593
- const minZScore = params.minZScore ?? 2;
594
- const absZ = Math.abs(ctx.cvd.cvdZScore);
595
- const met = absZ > minZScore;
596
- return {
597
- met,
598
- value: Math.round(ctx.cvd.cvdZScore * 100) / 100,
599
- threshold: minZScore,
600
- description: met
601
- ? `CVD extreme: z-score ${ctx.cvd.cvdZScore.toFixed(2)} (${ctx.cvd.cvdZScore > 0 ? 'buying' : 'selling'} exhaustion)`
602
- : `CVD z-score ${ctx.cvd.cvdZScore.toFixed(2)} (not extreme, need >${minZScore}σ)`,
603
- };
604
- });
605
- // ─── 23. supertrend_direction ────────────────────────────────────────────
606
- // Supertrend indicator direction matches trade direction
607
- register('supertrend_direction', (ctx, params, direction) => {
608
- const closes = ctx.ohlcv1h.map(b => b.close);
609
- const highs = ctx.ohlcv1h.map(b => b.high);
610
- const lows = ctx.ohlcv1h.map(b => b.low);
611
- const st = computeSupertrend(highs, lows, closes, params.period ?? 10, params.multiplier ?? 3);
612
- const met = direction === 'LONG'
613
- ? st.direction === 'bullish'
614
- : direction === 'SHORT'
615
- ? st.direction === 'bearish'
616
- : true;
617
- return {
618
- met,
619
- value: st.direction === 'bullish' ? 1 : -1,
620
- threshold: 0,
621
- description: met
622
- ? `Supertrend ${st.direction} (value ${st.value.toFixed(2)})`
623
- : `Supertrend ${st.direction} conflicts with ${direction} direction`,
624
- };
625
- });
626
- // ─── Helpers for higher-timeframe pattern conditions (Path B) ─────────
627
- // All conditions below honour params.tfHours: 1 (default, ohlcv1h),
628
- // 4 (ohlcv4h), or 24 (ohlcv1d). When the requested higher timeframe is
629
- // unavailable on this MarketContext, they fall back to ohlcv1h so unit
630
- // tests and legacy contexts still work.
631
- function pickBars(ctx, tfHours) {
632
- if (tfHours >= 24)
633
- return ctx.ohlcv1d ?? ctx.ohlcv1h;
634
- if (tfHours >= 4)
635
- return ctx.ohlcv4h ?? ctx.ohlcv1h;
636
- return ctx.ohlcv1h;
637
- }
638
- /** Pivot finder that returns indices alongside prices. We can't reuse
639
- * `findSwingPoints` from shared/indicators because it discards indices,
640
- * and trendline-fit + multi-pivot divergence both need them. */
641
- function findPivotsIdx(bars, lookback) {
642
- const highs = [];
643
- const lows = [];
644
- for (let i = lookback; i < bars.length - lookback; i++) {
645
- let isHigh = true;
646
- let isLow = true;
647
- for (let j = i - lookback; j <= i + lookback; j++) {
648
- if (j === i)
649
- continue;
650
- if (bars[j].high >= bars[i].high)
651
- isHigh = false;
652
- if (bars[j].low <= bars[i].low)
653
- isLow = false;
654
- }
655
- if (isHigh)
656
- highs.push({ idx: i, price: bars[i].high });
657
- if (isLow)
658
- lows.push({ idx: i, price: bars[i].low });
659
- }
660
- return { highs, lows };
661
- }
662
- // ─── 30. ema_slope ───────────────────────────────────────────────────
663
- // EMA(N) is sloping in the trade direction at >= minSlopePct per bar
664
- // (slope normalised by current EMA value, so it's a percent change rate).
665
- // params: period, lookback, minSlopePct, dirSign (1=up,-1=down,0=use trade direction), tfHours
666
- register('ema_slope', (ctx, params, direction) => {
667
- const period = Math.floor(params.period ?? 21);
668
- const lookback = Math.floor(params.lookback ?? 5);
669
- const minSlopePct = params.minSlopePct ?? 0;
670
- const dirSign = params.dirSign ?? 0;
671
- const tfHours = params.tfHours ?? 1;
672
- const bars = pickBars(ctx, tfHours);
673
- if (bars.length < period + lookback + 5) {
674
- return { met: false, value: 0, threshold: minSlopePct * 100, description: `Insufficient ${tfHours}h bars for ema_slope` };
675
- }
676
- const closes = bars.map((b) => b.close);
677
- const emaSeries = [];
678
- for (let i = closes.length - lookback - 1; i < closes.length; i++) {
679
- emaSeries.push(computeEMA(closes.slice(0, i + 1), period));
680
- }
681
- const slope = linearSlope(emaSeries);
682
- const lastEma = emaSeries[emaSeries.length - 1];
683
- const slopePct = lastEma > 0 ? slope / lastEma : 0;
684
- const requiredSign = dirSign !== 0
685
- ? Math.sign(dirSign)
686
- : direction === 'LONG' ? 1 : direction === 'SHORT' ? -1 : 0;
687
- const met = requiredSign === 0
688
- ? Math.abs(slopePct) >= minSlopePct
689
- : (slopePct * requiredSign) >= minSlopePct;
690
- return {
691
- met,
692
- value: Math.round(slopePct * 1_000_000) / 10_000,
693
- threshold: minSlopePct * 100,
694
- description: met
695
- ? `EMA(${period}) slope ${(slopePct * 100).toFixed(3)}%/bar over ${lookback} ${tfHours}h bars (${slopePct > 0 ? 'rising' : slopePct < 0 ? 'falling' : 'flat'})`
696
- : `EMA(${period}) slope ${(slopePct * 100).toFixed(3)}%/bar fails ${requiredSign > 0 ? '≥+' : requiredSign < 0 ? '≤−' : '|·|≥'}${(minSlopePct * 100).toFixed(3)}%`,
697
- };
698
- });
699
- // ─── 31. volume_vs_sma ───────────────────────────────────────────────
700
- // Latest bar volume >= multiplier × N-bar SMA of prior volumes.
701
- // params: period, minMultiplier, tfHours
702
- register('volume_vs_sma', (ctx, params) => {
703
- const period = Math.floor(params.period ?? 20);
704
- const minMultiplier = params.minMultiplier ?? 1.5;
705
- const tfHours = params.tfHours ?? 1;
706
- const bars = pickBars(ctx, tfHours);
707
- if (bars.length < period + 1) {
708
- return { met: false, value: 0, threshold: minMultiplier, description: `Insufficient ${tfHours}h bars for volume_vs_sma` };
709
- }
710
- const currentVol = bars[bars.length - 1].volume;
711
- const prevVols = bars.slice(-period - 1, -1).map((b) => b.volume);
712
- const sma = mean(prevVols);
713
- const ratio = sma > 0 ? currentVol / sma : 0;
714
- const met = ratio >= minMultiplier;
715
- return {
716
- met,
717
- value: Math.round(ratio * 100) / 100,
718
- threshold: minMultiplier,
719
- description: met
720
- ? `Volume ${ratio.toFixed(2)}× ${period}-bar SMA on ${tfHours}h`
721
- : `Volume only ${ratio.toFixed(2)}× ${period}-bar SMA (need ≥${minMultiplier}×)`,
722
- };
723
- });
724
- // Direction-aware return momentum over a fixed lookback.
725
- //
726
- // params:
727
- // lookback: bars back from the latest close
728
- // minReturnPct: decimal return threshold, e.g. 0.02 = 2%
729
- // maxAbsReturnPct: optional decimal cap to avoid chasing blow-off moves
730
- // mode: 1 = momentum with direction, -1 = counter-move into direction,
731
- // 0 = absolute move either way
732
- // dirSign: 1 long/up, -1 short/down, 0 use resolved trade direction
733
- // tfHours: 1, 4, or 24
734
- register('return_momentum', (ctx, params, direction) => {
735
- const lookback = Math.max(1, Math.floor(params.lookback ?? 12));
736
- const minReturnPct = params.minReturnPct ?? 0.01;
737
- const maxAbsReturnPct = params.maxAbsReturnPct ?? 0;
738
- const mode = params.mode === 0 ? 0 : params.mode && params.mode < 0 ? -1 : 1;
739
- const dirSign = params.dirSign ?? 0;
740
- const tfHours = params.tfHours ?? 1;
741
- const bars = pickBars(ctx, tfHours);
742
- if (bars.length <= lookback) {
743
- return {
744
- met: false,
745
- value: 0,
746
- threshold: minReturnPct * 100,
747
- description: `Insufficient ${tfHours}h bars for return_momentum`,
748
- };
749
- }
750
- const currentClose = bars[bars.length - 1].close;
751
- const priorClose = bars[bars.length - 1 - lookback].close;
752
- const ret = priorClose > 0 ? (currentClose - priorClose) / priorClose : 0;
753
- const absRet = Math.abs(ret);
754
- const baseSign = dirSign !== 0
755
- ? Math.sign(dirSign)
756
- : direction === 'LONG' ? 1 : direction === 'SHORT' ? -1 : 0;
757
- const targetSign = mode === 0 || baseSign === 0 ? 0 : baseSign * mode;
758
- const directionOk = targetSign > 0
759
- ? ret >= minReturnPct
760
- : targetSign < 0
761
- ? ret <= -minReturnPct
762
- : absRet >= minReturnPct;
763
- const extensionOk = maxAbsReturnPct <= 0 || absRet <= maxAbsReturnPct;
764
- const met = directionOk && extensionOk;
765
- const label = targetSign > 0
766
- ? 'upside momentum'
767
- : targetSign < 0
768
- ? 'downside momentum'
769
- : 'absolute momentum';
770
- const capText = maxAbsReturnPct > 0 ? `, cap ${(maxAbsReturnPct * 100).toFixed(1)}%` : '';
771
- return {
772
- met,
773
- value: Math.round(ret * 10000) / 100,
774
- threshold: minReturnPct * 100,
775
- description: met
776
- ? `${lookback}-bar ${tfHours}h return ${(ret * 100).toFixed(2)}% confirms ${label}`
777
- : `${lookback}-bar ${tfHours}h return ${(ret * 100).toFixed(2)}% fails ${label} threshold ${(minReturnPct * 100).toFixed(1)}%${capText}`,
778
- };
779
- });
780
- // Strict return-momentum episode transition. Unlike return_momentum, this is
781
- // an event rather than a level: it fires once when the lookback return crosses
782
- // zero (or an explicit threshold), then remains false until the sign resets.
783
- // That makes stop-outs and capacity-rejected entries naturally lock out for
784
- // the rest of the same momentum episode.
785
- register('return_momentum_zero_cross', (ctx, params, direction) => {
786
- const lookback = Math.max(1, Math.floor(params.lookback ?? 28));
787
- const thresholdPct = Math.max(0, params.thresholdPct ?? 0);
788
- const dirSign = params.dirSign ?? 0;
789
- const tfHours = params.tfHours ?? 24;
790
- const bars = pickBars(ctx, tfHours);
791
- if (bars.length <= lookback + 1) {
792
- return {
793
- met: false,
794
- value: 0,
795
- threshold: thresholdPct * 100,
796
- description: `Insufficient ${tfHours}h bars for return_momentum_zero_cross`,
797
- };
798
- }
799
- const currentClose = bars[bars.length - 1].close;
800
- const currentPrior = bars[bars.length - 1 - lookback].close;
801
- const previousClose = bars[bars.length - 2].close;
802
- const previousPrior = bars[bars.length - 2 - lookback].close;
803
- const currentReturn = currentPrior > 0
804
- ? (currentClose - currentPrior) / currentPrior
805
- : Number.NaN;
806
- const previousReturn = previousPrior > 0
807
- ? (previousClose - previousPrior) / previousPrior
808
- : Number.NaN;
809
- const sign = dirSign !== 0
810
- ? Math.sign(dirSign)
811
- : direction === 'LONG' ? 1 : direction === 'SHORT' ? -1 : 0;
812
- const met = Number.isFinite(currentReturn)
813
- && Number.isFinite(previousReturn)
814
- && (sign > 0
815
- ? previousReturn <= thresholdPct && currentReturn > thresholdPct
816
- : sign < 0
817
- ? previousReturn >= -thresholdPct && currentReturn < -thresholdPct
818
- : false);
819
- return {
820
- met,
821
- value: Number.isFinite(currentReturn)
822
- ? Math.round(currentReturn * 10000) / 100
823
- : 0,
824
- threshold: thresholdPct * 100,
825
- description: Number.isFinite(currentReturn) && Number.isFinite(previousReturn)
826
- ? `${lookback}-bar ${tfHours}h return moved from ${(previousReturn * 100).toFixed(2)}% to ${(currentReturn * 100).toFixed(2)}%`
827
- : `Invalid ${tfHours}h prices for return_momentum_zero_cross`,
828
- };
829
- });
830
- // ─── 32. rsi_multi_pivot_divergence ──────────────────────────────────
831
- // Strict N-pivot RSI divergence: price makes successively lower lows (or
832
- // higher highs) while RSI makes the opposite, with optional volume dry-up
833
- // at the final pivot.
834
- // params: pivots, swingLookback, rsiPeriod, volumeDryUp (0=disabled,
835
- // 0..1=ratio cap of last/first vol), direction (1=bullish, -1=bearish), tfHours
836
- register('rsi_multi_pivot_divergence', (ctx, params) => {
837
- const pivotsN = Math.max(2, Math.floor(params.pivots ?? 3));
838
- const swingLookback = Math.floor(params.swingLookback ?? 5);
839
- const rsiPeriod = Math.floor(params.rsiPeriod ?? 14);
840
- const volumeDryUp = params.volumeDryUp ?? 0;
841
- const dirParam = params.direction ?? 1;
842
- const isBullish = dirParam >= 0;
843
- const tfHours = params.tfHours ?? 1;
844
- const bars = pickBars(ctx, tfHours);
845
- if (bars.length < rsiPeriod + swingLookback * 2 + 10) {
846
- return { met: false, value: 0, threshold: pivotsN, description: `Insufficient ${tfHours}h bars for rsi_multi_pivot_divergence` };
847
- }
848
- const found = findPivotsIdx(bars, swingLookback);
849
- const piv = isBullish ? found.lows : found.highs;
850
- if (piv.length < pivotsN) {
851
- return {
852
- met: false,
853
- value: piv.length,
854
- threshold: pivotsN,
855
- description: `Only ${piv.length} pivot ${isBullish ? 'lows' : 'highs'} on ${tfHours}h (need ${pivotsN})`,
856
- };
857
- }
858
- const last = piv.slice(-pivotsN);
859
- // Price sequence check
860
- let priceSeqOk = true;
861
- for (let i = 1; i < last.length; i++) {
862
- if (isBullish && !(last[i].price < last[i - 1].price))
863
- priceSeqOk = false;
864
- if (!isBullish && !(last[i].price > last[i - 1].price))
865
- priceSeqOk = false;
866
- }
867
- if (!priceSeqOk) {
868
- return {
869
- met: false,
870
- value: 0,
871
- threshold: pivotsN,
872
- description: `${isBullish ? 'Price lows' : 'Price highs'} not in ${isBullish ? 'descending' : 'ascending'} sequence: ${last.map((p) => p.price.toFixed(2)).join('→')}`,
873
- };
874
- }
875
- // RSI at each pivot index
876
- const closes = bars.map((b) => b.close);
877
- const rsiAt = last.map((p) => computeRSI(closes.slice(0, p.idx + 1), rsiPeriod));
878
- let rsiSeqOk = true;
879
- for (let i = 1; i < rsiAt.length; i++) {
880
- if (isBullish && !(rsiAt[i] > rsiAt[i - 1]))
881
- rsiSeqOk = false;
882
- if (!isBullish && !(rsiAt[i] < rsiAt[i - 1]))
883
- rsiSeqOk = false;
884
- }
885
- if (!rsiSeqOk) {
886
- return {
887
- met: false,
888
- value: rsiAt[rsiAt.length - 1] - rsiAt[0],
889
- threshold: 0,
890
- description: `RSI not diverging: ${rsiAt.map((r) => r.toFixed(0)).join('→')}`,
891
- };
892
- }
893
- if (volumeDryUp > 0) {
894
- const v0 = bars[last[0].idx].volume;
895
- const vN = bars[last[last.length - 1].idx].volume;
896
- const ratio = v0 > 0 ? vN / v0 : 1;
897
- if (ratio > volumeDryUp) {
898
- return {
899
- met: false,
900
- value: Math.round(ratio * 100) / 100,
901
- threshold: volumeDryUp,
902
- description: `No volume dry-up: last pivot vol ${(ratio * 100).toFixed(0)}% of first (need ≤${(volumeDryUp * 100).toFixed(0)}%)`,
903
- };
904
- }
905
- }
906
- return {
907
- met: true,
908
- value: Math.round((rsiAt[rsiAt.length - 1] - rsiAt[0]) * 100) / 100,
909
- threshold: 0,
910
- description: `${pivotsN}-pivot ${isBullish ? 'bullish' : 'bearish'} divergence on ${tfHours}h: price ${last.map((p) => p.price.toFixed(2)).join('→')}, RSI ${rsiAt.map((r) => r.toFixed(0)).join('→')}`,
911
- };
912
- });
913
- // ─── 33. trendline_break ─────────────────────────────────────────────
914
- // Linear-fit a trendline through the last N pivot lows (or highs) and
915
- // check whether the latest close has broken through it in the requested
916
- // direction. Slope sign is also constrained to match the expected pattern
917
- // (descending trendline for bullish break above, ascending for bearish
918
- // break below).
919
- // params: pivots, swingLookback, useHighs (0=lows, 1=highs), breakDir
920
- // (1=above, -1=below), requireSlopeSign (1=ascending, -1=descending,
921
- // default flips with useHighs), tfHours
922
- register('trendline_break', (ctx, params) => {
923
- const pivotsN = Math.max(2, Math.floor(params.pivots ?? 3));
924
- const swingLookback = Math.floor(params.swingLookback ?? 5);
925
- const useHighs = (params.useHighs ?? 0) > 0;
926
- const breakDir = (params.breakDir ?? 1) >= 0 ? 1 : -1;
927
- const defaultSlope = useHighs ? 1 : -1;
928
- const requireSlope = (params.requireSlopeSign ?? defaultSlope) >= 0 ? 1 : -1;
929
- const tfHours = params.tfHours ?? 1;
930
- const bars = pickBars(ctx, tfHours);
931
- if (bars.length < swingLookback * 2 + pivotsN + 5) {
932
- return { met: false, value: 0, threshold: 0, description: `Insufficient ${tfHours}h bars for trendline_break` };
933
- }
934
- const found = findPivotsIdx(bars, swingLookback);
935
- const piv = useHighs ? found.highs : found.lows;
936
- if (piv.length < pivotsN) {
937
- return {
938
- met: false,
939
- value: piv.length,
940
- threshold: pivotsN,
941
- description: `Only ${piv.length} pivot ${useHighs ? 'highs' : 'lows'} on ${tfHours}h (need ${pivotsN})`,
942
- };
943
- }
944
- const lastPivots = piv.slice(-pivotsN);
945
- const xs = lastPivots.map((p) => p.idx);
946
- const ys = lastPivots.map((p) => p.price);
947
- const slope = linearSlope(ys, xs);
948
- const intercept = mean(ys) - slope * mean(xs);
949
- if ((requireSlope > 0 && slope <= 0) || (requireSlope < 0 && slope >= 0)) {
950
- return {
951
- met: false,
952
- value: Math.round(slope * 10000) / 10000,
953
- threshold: 0,
954
- description: `Trendline slope ${slope.toExponential(2)} wrong sign (need ${requireSlope > 0 ? '>0' : '<0'})`,
955
- };
956
- }
957
- const lastIdx = bars.length - 1;
958
- const lastClose = bars[lastIdx].close;
959
- const lineAtLast = slope * lastIdx + intercept;
960
- const broken = breakDir > 0 ? lastClose > lineAtLast : lastClose < lineAtLast;
961
- return {
962
- met: broken,
963
- value: Math.round((lastClose - lineAtLast) * 100) / 100,
964
- threshold: 0,
965
- description: broken
966
- ? `${pivotsN}-pivot ${useHighs ? 'highs' : 'lows'} trendline broken ${breakDir > 0 ? 'above' : 'below'}: close ${lastClose.toFixed(2)} vs line ${lineAtLast.toFixed(2)}`
967
- : `Close ${lastClose.toFixed(2)} ${breakDir > 0 ? 'still below' : 'still above'} trendline ${lineAtLast.toFixed(2)}`,
968
- };
969
- });
970
- // ─── 34. wedge_breakout ──────────────────────────────────────────────
971
- // Detect a falling wedge in the last N bars and confirm a breakout above
972
- // the upper trendline with volume confirmation. Falling wedge = both
973
- // trendlines sloping down with the upper line falling faster (converging).
974
- // params: minBars, swingLookback, minVolMultiplier, tfHours
975
- // (Currently implements falling wedge only — the bullish-break variant
976
- // the operator's strategies need. Rising wedge can be added later.)
977
- register('wedge_breakout', (ctx, params) => {
978
- const minBars = Math.floor(params.minBars ?? 40);
979
- const swingLookback = Math.floor(params.swingLookback ?? 5);
980
- const minVolMultiplier = params.minVolMultiplier ?? 1.5;
981
- const tfHours = params.tfHours ?? 1;
982
- const bars = pickBars(ctx, tfHours);
983
- if (bars.length < minBars + 5) {
984
- return { met: false, value: 0, threshold: 0, description: `Insufficient ${tfHours}h bars for wedge_breakout` };
985
- }
986
- const window = bars.slice(-minBars);
987
- const found = findPivotsIdx(window, swingLookback);
988
- if (found.highs.length < 3 || found.lows.length < 3) {
989
- return {
990
- met: false,
991
- value: 0,
992
- threshold: 0,
993
- description: `Need ≥3 highs+lows in ${minBars}-bar ${tfHours}h window (have ${found.highs.length}/${found.lows.length})`,
994
- };
995
- }
996
- const xsH = found.highs.map((h) => h.idx);
997
- const ysH = found.highs.map((h) => h.price);
998
- const xsL = found.lows.map((l) => l.idx);
999
- const ysL = found.lows.map((l) => l.price);
1000
- const slopeH = linearSlope(ysH, xsH);
1001
- const slopeL = linearSlope(ysL, xsL);
1002
- const interceptH = mean(ysH) - slopeH * mean(xsH);
1003
- const isFallingWedge = slopeH < 0 && slopeL < 0 && Math.abs(slopeH) > Math.abs(slopeL);
1004
- if (!isFallingWedge) {
1005
- return {
1006
- met: false,
1007
- value: 0,
1008
- threshold: 0,
1009
- description: `Not a falling wedge: upper slope ${slopeH.toExponential(2)}, lower ${slopeL.toExponential(2)}`,
1010
- };
1011
- }
1012
- const lastIdx = window.length - 1;
1013
- const upperAtLast = slopeH * lastIdx + interceptH;
1014
- const lastClose = window[lastIdx].close;
1015
- if (lastClose <= upperAtLast) {
1016
- return {
1017
- met: false,
1018
- value: Math.round((lastClose - upperAtLast) * 100) / 100,
1019
- threshold: 0,
1020
- description: `Wedge intact: close ${lastClose.toFixed(2)} ≤ upper line ${upperAtLast.toFixed(2)}`,
1021
- };
1022
- }
1023
- const lastVol = window[lastIdx].volume;
1024
- const volSma = mean(window.slice(-21, -1).map((b) => b.volume));
1025
- const volRatio = volSma > 0 ? lastVol / volSma : 0;
1026
- if (volRatio < minVolMultiplier) {
1027
- return {
1028
- met: false,
1029
- value: Math.round(volRatio * 100) / 100,
1030
- threshold: minVolMultiplier,
1031
- description: `Wedge break but volume ${volRatio.toFixed(2)}× SMA (need ≥${minVolMultiplier}×)`,
1032
- };
1033
- }
1034
- return {
1035
- met: true,
1036
- value: Math.round((lastClose - upperAtLast) * 100) / 100,
1037
- threshold: 0,
1038
- description: `Falling wedge break on ${tfHours}h: close ${lastClose.toFixed(2)} > upper ${upperAtLast.toFixed(2)}, vol ${volRatio.toFixed(1)}× SMA`,
1039
- };
1040
- });
1041
- // ─── 35. distance_to_resistance_R ────────────────────────────────────
1042
- // Measures the distance from current price to the nearest swing high
1043
- // (LONG) / swing low (SHORT) in units of ATR(14). Used to ensure a setup
1044
- // has at least N R of headroom before hitting structural resistance.
1045
- // params: minR, swingLookback, lookbackBars, tfHours
1046
- register('distance_to_resistance_R', (ctx, params, direction) => {
1047
- const minR = params.minR ?? 1.5;
1048
- const swingLookback = Math.floor(params.swingLookback ?? 5);
1049
- const lookbackBars = Math.floor(params.lookbackBars ?? 100);
1050
- const tfHours = params.tfHours ?? 1;
1051
- const bars = pickBars(ctx, tfHours);
1052
- if (bars.length < lookbackBars) {
1053
- return { met: false, value: 0, threshold: minR, description: `Insufficient ${tfHours}h bars for distance_to_resistance_R` };
1054
- }
1055
- if (ctx.atr14 <= 0) {
1056
- return { met: false, value: 0, threshold: minR, description: 'ATR(14) is zero — cannot compute R distance' };
1057
- }
1058
- const window = bars.slice(-lookbackBars);
1059
- const found = findPivotsIdx(window, swingLookback);
1060
- const isLong = direction === 'LONG' || direction === null;
1061
- const candidates = isLong
1062
- ? found.highs.map((h) => h.price).filter((p) => p > ctx.currentPrice)
1063
- : found.lows.map((l) => l.price).filter((p) => p < ctx.currentPrice);
1064
- if (candidates.length === 0) {
1065
- return {
1066
- met: true,
1067
- value: 999,
1068
- threshold: minR,
1069
- description: `No ${isLong ? 'overhead resistance' : 'support below'} in last ${lookbackBars} ${tfHours}h bars`,
1070
- };
1071
- }
1072
- const nearest = isLong ? Math.min(...candidates) : Math.max(...candidates);
1073
- const distance = Math.abs(nearest - ctx.currentPrice);
1074
- const distanceR = distance / ctx.atr14;
1075
- const met = distanceR >= minR;
1076
- return {
1077
- met,
1078
- value: Math.round(distanceR * 100) / 100,
1079
- threshold: minR,
1080
- description: met
1081
- ? `${distanceR.toFixed(2)}R clear to nearest ${isLong ? 'resistance' : 'support'} at ${nearest.toFixed(2)}`
1082
- : `Only ${distanceR.toFixed(2)}R to nearest ${isLong ? 'resistance' : 'support'} at ${nearest.toFixed(2)} (need ≥${minR}R)`,
1083
- };
1084
- });
1085
- // ─── 36. btc_rsi_overbought (skipIf helper) ──────────────────────────
1086
- // Reads the global market context (BTC daily RSI) and returns met=true
1087
- // when BTC RSI exceeds the threshold. Designed to be used in StrategyConfig.skipIf
1088
- // to block longs when BTC daily RSI is overbought. Fail-open: if global
1089
- // context is missing the condition returns met=false (do not skip).
1090
- // params: maxRsi (default 70)
1091
- register('btc_rsi_overbought', (ctx, params) => {
1092
- const maxRsi = params.maxRsi ?? 70;
1093
- const btcRsi = ctx.globalContext?.btcRsi14_1d;
1094
- if (btcRsi === undefined) {
1095
- return {
1096
- met: false,
1097
- value: 0,
1098
- threshold: maxRsi,
1099
- description: 'BTC 1d RSI unavailable in global context (fail-open)',
1100
- };
1101
- }
1102
- const met = btcRsi > maxRsi;
1103
- return {
1104
- met,
1105
- value: Math.round(btcRsi * 100) / 100,
1106
- threshold: maxRsi,
1107
- description: met
1108
- ? `BTC 1d RSI ${btcRsi.toFixed(1)} > ${maxRsi} (overbought — skip longs)`
1109
- : `BTC 1d RSI ${btcRsi.toFixed(1)} ≤ ${maxRsi}`,
1110
- };
1111
- });
1112
- // ─── cascade_pulse_safety ──────────────────────────────────────────────
1113
- // `met=true` when an `active_cascade` is in progress on the majors group
1114
- // AND the dominant side opposes the trade direction — i.e. liquidations
1115
- // are running INTO our entry. Use as skipIf to keep the agent out of
1116
- // liquidation cascades that typically continue for 60-180 seconds.
1117
- //
1118
- // Params (ConditionConfig schema is `Record<string, number>` so flags are
1119
- // numeric):
1120
- // scope: 0 = majors (default), 1 = symbol
1121
- // minSeverity: 1 = elevated, 2 = active_cascade (default)
1122
- //
1123
- // Fail-open semantics: returns met=false when the microstructure flag is
1124
- // off (fields undefined) or events=0 in window (dominantSide=balanced).
1125
- // This keeps the condition safe to add to a strategy before Phase 1 is on.
1126
- register('cascade_pulse_safety', (ctx, params, direction) => {
1127
- const useSymbolScope = params.scope === 1;
1128
- const minSeverity = params.minSeverity === 1 ? 1 : 2; // default: active_cascade
1129
- const classification = useSymbolScope ? ctx.cascadePulse60s : ctx.majorsCascadePulse60s;
1130
- const dominant = ctx.cascadeDominantSide;
1131
- const scopeLabel = useSymbolScope ? 'symbol' : 'majors';
1132
- if (classification === undefined || dominant === undefined) {
1133
- return {
1134
- met: false,
1135
- value: 0,
1136
- threshold: 0,
1137
- description: 'Cascade pulse unavailable (feature flag off — fail-open)',
1138
- };
1139
- }
1140
- const severityRank = classification === 'active_cascade' ? 2 : classification === 'elevated' ? 1 : 0;
1141
- const meetsSeverity = severityRank >= minSeverity;
1142
- // Direction-aware: a long entry is endangered when LONGS are being
1143
- // liquidated (cascading SELL pressure). A short entry is endangered when
1144
- // SHORTS are being liquidated (cascading BUY pressure).
1145
- const opposing = direction === 'LONG' ? dominant === 'long' :
1146
- direction === 'SHORT' ? dominant === 'short' :
1147
- false;
1148
- const met = meetsSeverity && opposing;
1149
- return {
1150
- met,
1151
- value: severityRank,
1152
- threshold: minSeverity,
1153
- description: met
1154
- ? `${scopeLabel} cascade ${classification} dominant=${dominant} opposing ${direction} — skip`
1155
- : `${scopeLabel} pulse=${classification}, dominant=${dominant}, dir=${direction} (no skip)`,
1156
- };
1157
- });
1158
- // ─── resting_band_unfavourable ─────────────────────────────────────────
1159
- // `met=true` when banded resting liquidity in the 0.5%/1% band shows the
1160
- // opposite side has substantially more notional resting against the
1161
- // trade direction — i.e. there's a wall of overhead supply against a
1162
- // long, or a wall of underlying support against a short.
1163
- //
1164
- // Direction-aware:
1165
- // LONG : skip if ask-side share of (mid, mid+0.5%] > minMargin (default 0.55)
1166
- // SHORT : skip if bid-side share of (mid-0.5%, mid] > minMargin
1167
- //
1168
- // Fail-open: returns met=false (no skip) when bandedLiquidity flag is
1169
- // off OR coverage_pct < minCoverage (data unreliable on tight books like
1170
- // BTCUSDT where depth-20 reaches < 0.01%). Strategies on those symbols
1171
- // effectively bypass this filter.
1172
- //
1173
- // Numeric params (ConditionConfig schema is Record<string, number>):
1174
- // band: 5 = 0.5% (default), 10 = 1.0%
1175
- // minMargin: 0.55 default — minimum share against direction to trigger
1176
- // minCoverage: 0.5 default (% units) — coverage_pct floor for reliability
1177
- register('resting_band_unfavourable', (ctx, params, direction) => {
1178
- const useBand10 = params.band === 10;
1179
- const minMargin = params.minMargin ?? 0.55;
1180
- const minCoverage = params.minCoverage ?? 0.5;
1181
- const imbalance = useBand10 ? ctx.restingBandImbalance10 : ctx.restingBandImbalance05;
1182
- const coverage = ctx.restingCoveragePct;
1183
- const bandLabel = useBand10 ? '1.0%' : '0.5%';
1184
- if (imbalance === undefined || coverage === undefined) {
1185
- return {
1186
- met: false,
1187
- value: 0,
1188
- threshold: minMargin,
1189
- description: 'Resting bands unavailable (feature flag off — fail-open)',
1190
- };
1191
- }
1192
- if (coverage < minCoverage) {
1193
- return {
1194
- met: false,
1195
- value: imbalance,
1196
- threshold: minMargin,
1197
- description: `Coverage ${coverage.toFixed(3)}% < ${minCoverage}% — depth-20 too tight, fail-open`,
1198
- };
1199
- }
1200
- // imbalance is bid-share. ask-share = 1 - imbalance.
1201
- // For LONG, "unfavourable" = ask-share dominant = imbalance < 1 - minMargin.
1202
- // For SHORT, "unfavourable" = bid-share dominant = imbalance > minMargin.
1203
- let unfavourable = false;
1204
- let actualShare = 0;
1205
- if (direction === 'LONG') {
1206
- actualShare = 1 - imbalance; // ask side
1207
- unfavourable = actualShare > minMargin;
1208
- }
1209
- else if (direction === 'SHORT') {
1210
- actualShare = imbalance; // bid side
1211
- unfavourable = actualShare > minMargin;
1212
- }
1213
- return {
1214
- met: unfavourable,
1215
- value: Math.round(actualShare * 1000) / 1000,
1216
- threshold: minMargin,
1217
- description: unfavourable
1218
- ? `${bandLabel} band ${direction === 'LONG' ? 'ask' : 'bid'}-share ${(actualShare * 100).toFixed(1)}% > ${(minMargin * 100).toFixed(0)}% — wall against ${direction} entry, skip`
1219
- : `${bandLabel} band ${direction === 'LONG' ? 'ask' : 'bid'}-share ${(actualShare * 100).toFixed(1)}%, coverage ${coverage.toFixed(2)}% (no skip)`,
1220
- };
1221
- });
1222
- // ─── funding_extreme_skip ──────────────────────────────────────────────
1223
- // `met=true` (skip) when current funding sits in the extreme tail of its
1224
- // 30-day distribution AND the trade direction would be entering INTO that
1225
- // crowded positioning:
1226
- // - LONG skipped when percentileRank > upperRank (default 0.90)
1227
- // — funding is in the top decile = crowded longs, top-tick risk
1228
- // - SHORT skipped when percentileRank < lowerRank (default 0.10)
1229
- // — funding is in the bottom decile = crowded shorts, squeeze risk
1230
- //
1231
- // Per-symbol adaptive by construction: the rank is computed against each
1232
- // symbol's own 30d distribution server-side (see funding-percentile-api.ts),
1233
- // so DOT/WIF heavy tails and BTC/BNB tight ranges both get sensible cutoffs.
1234
- //
1235
- // Numeric params:
1236
- // upperRank: default 0.90 — skip LONG if rank > this
1237
- // lowerRank: default 0.10 — skip SHORT if rank < this
1238
- //
1239
- // Fail-open: returns met=false when fundingPercentile is undefined
1240
- // (FUNDING_OVERLAY flag off, insufficient sample, or symbol missing data).
1241
- // Direction-aware via pass2 evaluation; in skipIf (direction=null) it
1242
- // effectively no-ops since neither branch matches.
1243
- register('funding_extreme_skip', (ctx, params, direction) => {
1244
- const upperRank = params.upperRank ?? 0.90;
1245
- const lowerRank = params.lowerRank ?? 0.10;
1246
- const fp = ctx.fundingPercentile;
1247
- if (!fp) {
1248
- return {
1249
- met: false,
1250
- value: 0,
1251
- threshold: 0,
1252
- description: 'Funding percentile unavailable (FUNDING_OVERLAY off or no data — fail-open)',
1253
- };
1254
- }
1255
- const rate_bps = fp.current * 10000;
1256
- let met = false;
1257
- let why = '';
1258
- if (direction === 'LONG' && fp.percentileRank > upperRank) {
1259
- met = true;
1260
- why = `rank ${(fp.percentileRank * 100).toFixed(0)}th > ${(upperRank * 100).toFixed(0)}th — crowded longs (rate ${rate_bps.toFixed(2)} bps), skip LONG`;
1261
- }
1262
- else if (direction === 'SHORT' && fp.percentileRank < lowerRank) {
1263
- met = true;
1264
- why = `rank ${(fp.percentileRank * 100).toFixed(0)}th < ${(lowerRank * 100).toFixed(0)}th — crowded shorts (rate ${rate_bps.toFixed(2)} bps), skip SHORT`;
1265
- }
1266
- else {
1267
- why = `rank ${(fp.percentileRank * 100).toFixed(0)}th, dir=${direction ?? 'null'} (no skip)`;
1268
- }
1269
- return {
1270
- met,
1271
- value: Math.round(fp.percentileRank * 100) / 100,
1272
- threshold: direction === 'LONG' ? upperRank : direction === 'SHORT' ? lowerRank : 0,
1273
- description: why,
1274
- };
1275
- });
1276
- // ─── funding_position_ok ───────────────────────────────────────────────
1277
- // Inverse of `funding_extreme_skip` for use in a strategy's `conditions`
1278
- // list (gate semantics: met=true means "funding is OK to trade in this
1279
- // direction"). Where `funding_extreme_skip` is intended for skipIf in
1280
- // fixed-direction strategies, this variant is what regular conditions
1281
- // pipelines need: pass-2 evaluation re-runs after directionRule resolves,
1282
- // giving the agent direction-aware behaviour without changing the adapter.
1283
- //
1284
- // met=true when the trade is NOT entering INTO crowded positioning:
1285
- // LONG acceptable if percentileRank ≤ upperRank (default 0.90)
1286
- // SHORT acceptable if percentileRank ≥ lowerRank (default 0.10)
1287
- //
1288
- // Same numeric params as funding_extreme_skip. Same fail-open contract:
1289
- // returns met=true when fundingPercentile is undefined (FUNDING_OVERLAY
1290
- // off, no data, or insufficient sample) so the strategy is not blocked
1291
- // by missing data.
1292
- register('funding_position_ok', (ctx, params, direction) => {
1293
- const upperRank = params.upperRank ?? 0.90;
1294
- const lowerRank = params.lowerRank ?? 0.10;
1295
- const fp = ctx.fundingPercentile;
1296
- if (!fp) {
1297
- return {
1298
- met: true,
1299
- value: 0,
1300
- threshold: 0,
1301
- description: 'Funding percentile unavailable (FUNDING_OVERLAY off or no data — fail-open)',
1302
- };
1303
- }
1304
- const rate_bps = fp.current * 10000;
1305
- let met = true;
1306
- let why = '';
1307
- if (direction === 'LONG' && fp.percentileRank > upperRank) {
1308
- met = false;
1309
- why = `rank ${(fp.percentileRank * 100).toFixed(0)}th > ${(upperRank * 100).toFixed(0)}th — crowded longs (rate ${rate_bps.toFixed(2)} bps), block LONG`;
1310
- }
1311
- else if (direction === 'SHORT' && fp.percentileRank < lowerRank) {
1312
- met = false;
1313
- why = `rank ${(fp.percentileRank * 100).toFixed(0)}th < ${(lowerRank * 100).toFixed(0)}th — crowded shorts (rate ${rate_bps.toFixed(2)} bps), block SHORT`;
1314
- }
1315
- else {
1316
- why = `rank ${(fp.percentileRank * 100).toFixed(0)}th, dir=${direction ?? 'null'} (funding OK for this direction)`;
1317
- }
1318
- return {
1319
- met,
1320
- value: Math.round(fp.percentileRank * 100) / 100,
1321
- threshold: direction === 'LONG' ? upperRank : direction === 'SHORT' ? lowerRank : 0,
1322
- description: why,
1323
- };
1324
- });