@senpi/trading-runtime 1.0.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 (535) hide show
  1. package/README.md +117 -0
  2. package/dist/actions/decision-engine.d.ts +28 -0
  3. package/dist/actions/decision-engine.d.ts.map +1 -0
  4. package/dist/actions/decision-engine.js +65 -0
  5. package/dist/actions/decision-engine.js.map +1 -0
  6. package/dist/actions/llm-decision/anthropic-llm-decision.d.ts +12 -0
  7. package/dist/actions/llm-decision/anthropic-llm-decision.d.ts.map +1 -0
  8. package/dist/actions/llm-decision/anthropic-llm-decision.js +74 -0
  9. package/dist/actions/llm-decision/anthropic-llm-decision.js.map +1 -0
  10. package/dist/actions/llm-decision/factory.d.ts +17 -0
  11. package/dist/actions/llm-decision/factory.d.ts.map +1 -0
  12. package/dist/actions/llm-decision/factory.js +29 -0
  13. package/dist/actions/llm-decision/factory.js.map +1 -0
  14. package/dist/actions/llm-decision/index.d.ts +8 -0
  15. package/dist/actions/llm-decision/index.d.ts.map +1 -0
  16. package/dist/actions/llm-decision/index.js +5 -0
  17. package/dist/actions/llm-decision/index.js.map +1 -0
  18. package/dist/actions/llm-decision/openclaw-llm-decision.d.ts +14 -0
  19. package/dist/actions/llm-decision/openclaw-llm-decision.d.ts.map +1 -0
  20. package/dist/actions/llm-decision/openclaw-llm-decision.js +58 -0
  21. package/dist/actions/llm-decision/openclaw-llm-decision.js.map +1 -0
  22. package/dist/actions/llm-decision/shared.d.ts +41 -0
  23. package/dist/actions/llm-decision/shared.d.ts.map +1 -0
  24. package/dist/actions/llm-decision/shared.js +117 -0
  25. package/dist/actions/llm-decision/shared.js.map +1 -0
  26. package/dist/actions/open-position/open-position.action.d.ts +15 -0
  27. package/dist/actions/open-position/open-position.action.d.ts.map +1 -0
  28. package/dist/actions/open-position/open-position.action.js +302 -0
  29. package/dist/actions/open-position/open-position.action.js.map +1 -0
  30. package/dist/actions/types.d.ts +123 -0
  31. package/dist/actions/types.d.ts.map +1 -0
  32. package/dist/actions/types.js +3 -0
  33. package/dist/actions/types.js.map +1 -0
  34. package/dist/cli/guide-commands.d.ts +26 -0
  35. package/dist/cli/guide-commands.d.ts.map +1 -0
  36. package/dist/cli/guide-commands.js +332 -0
  37. package/dist/cli/guide-commands.js.map +1 -0
  38. package/dist/cli/senpi-commands.d.ts +22 -0
  39. package/dist/cli/senpi-commands.d.ts.map +1 -0
  40. package/dist/cli/senpi-commands.js +138 -0
  41. package/dist/cli/senpi-commands.js.map +1 -0
  42. package/dist/constants/dsl/index.d.ts +39 -0
  43. package/dist/constants/dsl/index.d.ts.map +1 -0
  44. package/dist/constants/dsl/index.js +39 -0
  45. package/dist/constants/dsl/index.js.map +1 -0
  46. package/dist/context/context-builder.d.ts +14 -0
  47. package/dist/context/context-builder.d.ts.map +1 -0
  48. package/dist/context/context-builder.js +36 -0
  49. package/dist/context/context-builder.js.map +1 -0
  50. package/dist/context/index.d.ts +5 -0
  51. package/dist/context/index.d.ts.map +1 -0
  52. package/dist/context/index.js +5 -0
  53. package/dist/context/index.js.map +1 -0
  54. package/dist/context/interpolate.d.ts +6 -0
  55. package/dist/context/interpolate.d.ts.map +1 -0
  56. package/dist/context/interpolate.js +15 -0
  57. package/dist/context/interpolate.js.map +1 -0
  58. package/dist/context/providers/asset-trend.provider.d.ts +42 -0
  59. package/dist/context/providers/asset-trend.provider.d.ts.map +1 -0
  60. package/dist/context/providers/asset-trend.provider.js +94 -0
  61. package/dist/context/providers/asset-trend.provider.js.map +1 -0
  62. package/dist/context/providers/index.d.ts +5 -0
  63. package/dist/context/providers/index.d.ts.map +1 -0
  64. package/dist/context/providers/index.js +5 -0
  65. package/dist/context/providers/index.js.map +1 -0
  66. package/dist/context/providers/regime.provider.d.ts +14 -0
  67. package/dist/context/providers/regime.provider.d.ts.map +1 -0
  68. package/dist/context/providers/regime.provider.js +29 -0
  69. package/dist/context/providers/regime.provider.js.map +1 -0
  70. package/dist/context/providers/signal.provider.d.ts +11 -0
  71. package/dist/context/providers/signal.provider.d.ts.map +1 -0
  72. package/dist/context/providers/signal.provider.js +27 -0
  73. package/dist/context/providers/signal.provider.js.map +1 -0
  74. package/dist/context/providers/strategy.provider.d.ts +14 -0
  75. package/dist/context/providers/strategy.provider.d.ts.map +1 -0
  76. package/dist/context/providers/strategy.provider.js +68 -0
  77. package/dist/context/providers/strategy.provider.js.map +1 -0
  78. package/dist/context/types.d.ts +30 -0
  79. package/dist/context/types.d.ts.map +1 -0
  80. package/dist/context/types.js +2 -0
  81. package/dist/context/types.js.map +1 -0
  82. package/dist/dsl/config/index.d.ts +43 -0
  83. package/dist/dsl/config/index.d.ts.map +1 -0
  84. package/dist/dsl/config/index.js +108 -0
  85. package/dist/dsl/config/index.js.map +1 -0
  86. package/dist/dsl/constants/index.d.ts +3 -0
  87. package/dist/dsl/constants/index.d.ts.map +1 -0
  88. package/dist/dsl/constants/index.js +3 -0
  89. package/dist/dsl/constants/index.js.map +1 -0
  90. package/dist/dsl/engine/index.d.ts +7 -0
  91. package/dist/dsl/engine/index.d.ts.map +1 -0
  92. package/dist/dsl/engine/index.js +123 -0
  93. package/dist/dsl/engine/index.js.map +1 -0
  94. package/dist/dsl/events/bus-types.d.ts +7 -0
  95. package/dist/dsl/events/bus-types.d.ts.map +1 -0
  96. package/dist/dsl/events/bus-types.js +6 -0
  97. package/dist/dsl/events/bus-types.js.map +1 -0
  98. package/dist/dsl/events/guards.d.ts +17 -0
  99. package/dist/dsl/events/guards.d.ts.map +1 -0
  100. package/dist/dsl/events/guards.js +94 -0
  101. package/dist/dsl/events/guards.js.map +1 -0
  102. package/dist/dsl/events/handlers.d.ts +15 -0
  103. package/dist/dsl/events/handlers.d.ts.map +1 -0
  104. package/dist/dsl/events/handlers.js +229 -0
  105. package/dist/dsl/events/handlers.js.map +1 -0
  106. package/dist/dsl/events/index.d.ts +4 -0
  107. package/dist/dsl/events/index.d.ts.map +1 -0
  108. package/dist/dsl/events/index.js +4 -0
  109. package/dist/dsl/events/index.js.map +1 -0
  110. package/dist/dsl/index.d.ts +13 -0
  111. package/dist/dsl/index.d.ts.map +1 -0
  112. package/dist/dsl/index.js +35 -0
  113. package/dist/dsl/index.js.map +1 -0
  114. package/dist/dsl/monitor/index.d.ts +18 -0
  115. package/dist/dsl/monitor/index.d.ts.map +1 -0
  116. package/dist/dsl/monitor/index.js +296 -0
  117. package/dist/dsl/monitor/index.js.map +1 -0
  118. package/dist/dsl/plugin/index.d.ts +29 -0
  119. package/dist/dsl/plugin/index.d.ts.map +1 -0
  120. package/dist/dsl/plugin/index.js +85 -0
  121. package/dist/dsl/plugin/index.js.map +1 -0
  122. package/dist/dsl/types/index.d.ts +3 -0
  123. package/dist/dsl/types/index.d.ts.map +1 -0
  124. package/dist/dsl/types/index.js +3 -0
  125. package/dist/dsl/types/index.js.map +1 -0
  126. package/dist/health/index.d.ts +9 -0
  127. package/dist/health/index.d.ts.map +1 -0
  128. package/dist/health/index.js +9 -0
  129. package/dist/health/index.js.map +1 -0
  130. package/dist/index.d.ts +94 -0
  131. package/dist/index.d.ts.map +1 -0
  132. package/dist/index.js +218 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/risk/index.d.ts +9 -0
  135. package/dist/risk/index.d.ts.map +1 -0
  136. package/dist/risk/index.js +9 -0
  137. package/dist/risk/index.js.map +1 -0
  138. package/dist/runtime/action-registry.d.ts +12 -0
  139. package/dist/runtime/action-registry.d.ts.map +1 -0
  140. package/dist/runtime/action-registry.js +18 -0
  141. package/dist/runtime/action-registry.js.map +1 -0
  142. package/dist/runtime/build-action-scan-payload.d.ts +20 -0
  143. package/dist/runtime/build-action-scan-payload.d.ts.map +1 -0
  144. package/dist/runtime/build-action-scan-payload.js +52 -0
  145. package/dist/runtime/build-action-scan-payload.js.map +1 -0
  146. package/dist/runtime/bus.d.ts +6 -0
  147. package/dist/runtime/bus.d.ts.map +1 -0
  148. package/dist/runtime/bus.js +19 -0
  149. package/dist/runtime/bus.js.map +1 -0
  150. package/dist/runtime/create-action-context.d.ts +34 -0
  151. package/dist/runtime/create-action-context.d.ts.map +1 -0
  152. package/dist/runtime/create-action-context.js +22 -0
  153. package/dist/runtime/create-action-context.js.map +1 -0
  154. package/dist/runtime/create-actions.d.ts +30 -0
  155. package/dist/runtime/create-actions.d.ts.map +1 -0
  156. package/dist/runtime/create-actions.js +78 -0
  157. package/dist/runtime/create-actions.js.map +1 -0
  158. package/dist/runtime/create-runtime-context.d.ts +33 -0
  159. package/dist/runtime/create-runtime-context.d.ts.map +1 -0
  160. package/dist/runtime/create-runtime-context.js +32 -0
  161. package/dist/runtime/create-runtime-context.js.map +1 -0
  162. package/dist/runtime/create-scanner-module.d.ts +14 -0
  163. package/dist/runtime/create-scanner-module.d.ts.map +1 -0
  164. package/dist/runtime/create-scanner-module.js +26 -0
  165. package/dist/runtime/create-scanner-module.js.map +1 -0
  166. package/dist/runtime/create-scanner-providers-for-run.d.ts +20 -0
  167. package/dist/runtime/create-scanner-providers-for-run.d.ts.map +1 -0
  168. package/dist/runtime/create-scanner-providers-for-run.js +34 -0
  169. package/dist/runtime/create-scanner-providers-for-run.js.map +1 -0
  170. package/dist/runtime/default-state-dir.d.ts +10 -0
  171. package/dist/runtime/default-state-dir.d.ts.map +1 -0
  172. package/dist/runtime/default-state-dir.js +22 -0
  173. package/dist/runtime/default-state-dir.js.map +1 -0
  174. package/dist/runtime/env-resolve.d.ts +17 -0
  175. package/dist/runtime/env-resolve.d.ts.map +1 -0
  176. package/dist/runtime/env-resolve.js +45 -0
  177. package/dist/runtime/env-resolve.js.map +1 -0
  178. package/dist/runtime/hook-system.d.ts +7 -0
  179. package/dist/runtime/hook-system.d.ts.map +1 -0
  180. package/dist/runtime/hook-system.js +16 -0
  181. package/dist/runtime/hook-system.js.map +1 -0
  182. package/dist/runtime/index.d.ts +37 -0
  183. package/dist/runtime/index.d.ts.map +1 -0
  184. package/dist/runtime/index.js +26 -0
  185. package/dist/runtime/index.js.map +1 -0
  186. package/dist/runtime/load-trading-strategy.d.ts +18 -0
  187. package/dist/runtime/load-trading-strategy.d.ts.map +1 -0
  188. package/dist/runtime/load-trading-strategy.js +51 -0
  189. package/dist/runtime/load-trading-strategy.js.map +1 -0
  190. package/dist/runtime/map-exit-config.d.ts +12 -0
  191. package/dist/runtime/map-exit-config.d.ts.map +1 -0
  192. package/dist/runtime/map-exit-config.js +89 -0
  193. package/dist/runtime/map-exit-config.js.map +1 -0
  194. package/dist/runtime/map-strategies-registration.d.ts +12 -0
  195. package/dist/runtime/map-strategies-registration.d.ts.map +1 -0
  196. package/dist/runtime/map-strategies-registration.js +52 -0
  197. package/dist/runtime/map-strategies-registration.js.map +1 -0
  198. package/dist/runtime/map-strategies.d.ts +12 -0
  199. package/dist/runtime/map-strategies.d.ts.map +1 -0
  200. package/dist/runtime/map-strategies.js +46 -0
  201. package/dist/runtime/map-strategies.js.map +1 -0
  202. package/dist/runtime/notifications.d.ts +10 -0
  203. package/dist/runtime/notifications.d.ts.map +1 -0
  204. package/dist/runtime/notifications.js +14 -0
  205. package/dist/runtime/notifications.js.map +1 -0
  206. package/dist/runtime/parse-interval.d.ts +11 -0
  207. package/dist/runtime/parse-interval.d.ts.map +1 -0
  208. package/dist/runtime/parse-interval.js +35 -0
  209. package/dist/runtime/parse-interval.js.map +1 -0
  210. package/dist/runtime/reconcile-state.d.ts +25 -0
  211. package/dist/runtime/reconcile-state.d.ts.map +1 -0
  212. package/dist/runtime/reconcile-state.js +114 -0
  213. package/dist/runtime/reconcile-state.js.map +1 -0
  214. package/dist/runtime/risk-guard.d.ts +6 -0
  215. package/dist/runtime/risk-guard.d.ts.map +1 -0
  216. package/dist/runtime/risk-guard.js +14 -0
  217. package/dist/runtime/risk-guard.js.map +1 -0
  218. package/dist/runtime/run.d.ts +21 -0
  219. package/dist/runtime/run.d.ts.map +1 -0
  220. package/dist/runtime/run.js +43 -0
  221. package/dist/runtime/run.js.map +1 -0
  222. package/dist/runtime/runtime.d.ts +40 -0
  223. package/dist/runtime/runtime.d.ts.map +1 -0
  224. package/dist/runtime/runtime.js +249 -0
  225. package/dist/runtime/runtime.js.map +1 -0
  226. package/dist/runtime/scanner-registry.d.ts +22 -0
  227. package/dist/runtime/scanner-registry.d.ts.map +1 -0
  228. package/dist/runtime/scanner-registry.js +72 -0
  229. package/dist/runtime/scanner-registry.js.map +1 -0
  230. package/dist/runtime/strategy-registry.d.ts +49 -0
  231. package/dist/runtime/strategy-registry.d.ts.map +1 -0
  232. package/dist/runtime/strategy-registry.js +150 -0
  233. package/dist/runtime/strategy-registry.js.map +1 -0
  234. package/dist/runtime/trading-strategy-config.d.ts +6 -0
  235. package/dist/runtime/trading-strategy-config.d.ts.map +1 -0
  236. package/dist/runtime/trading-strategy-config.js +5 -0
  237. package/dist/runtime/trading-strategy-config.js.map +1 -0
  238. package/dist/runtime/trading-strategy-schema.d.ts +19757 -0
  239. package/dist/runtime/trading-strategy-schema.d.ts.map +1 -0
  240. package/dist/runtime/trading-strategy-schema.js +107 -0
  241. package/dist/runtime/trading-strategy-schema.js.map +1 -0
  242. package/dist/scanners/__tests__/fixtures/index.d.ts +3 -0
  243. package/dist/scanners/__tests__/fixtures/index.d.ts.map +1 -0
  244. package/dist/scanners/__tests__/fixtures/index.js +3 -0
  245. package/dist/scanners/__tests__/fixtures/index.js.map +1 -0
  246. package/dist/scanners/__tests__/fixtures/scan-results.d.ts +23 -0
  247. package/dist/scanners/__tests__/fixtures/scan-results.d.ts.map +1 -0
  248. package/dist/scanners/__tests__/fixtures/scan-results.js +128 -0
  249. package/dist/scanners/__tests__/fixtures/scan-results.js.map +1 -0
  250. package/dist/scanners/__tests__/fixtures/signals.d.ts +18 -0
  251. package/dist/scanners/__tests__/fixtures/signals.d.ts.map +1 -0
  252. package/dist/scanners/__tests__/fixtures/signals.js +84 -0
  253. package/dist/scanners/__tests__/fixtures/signals.js.map +1 -0
  254. package/dist/scanners/__tests__/test-helpers.d.ts +4 -0
  255. package/dist/scanners/__tests__/test-helpers.d.ts.map +1 -0
  256. package/dist/scanners/__tests__/test-helpers.js +23 -0
  257. package/dist/scanners/__tests__/test-helpers.js.map +1 -0
  258. package/dist/scanners/artifacts.d.ts +119 -0
  259. package/dist/scanners/artifacts.d.ts.map +1 -0
  260. package/dist/scanners/artifacts.js +72 -0
  261. package/dist/scanners/artifacts.js.map +1 -0
  262. package/dist/scanners/create-scanner.d.ts +13 -0
  263. package/dist/scanners/create-scanner.d.ts.map +1 -0
  264. package/dist/scanners/create-scanner.js +11 -0
  265. package/dist/scanners/create-scanner.js.map +1 -0
  266. package/dist/scanners/engine/config-validator.d.ts +7 -0
  267. package/dist/scanners/engine/config-validator.d.ts.map +1 -0
  268. package/dist/scanners/engine/config-validator.js +16 -0
  269. package/dist/scanners/engine/config-validator.js.map +1 -0
  270. package/dist/scanners/engine/data-providers.d.ts +43 -0
  271. package/dist/scanners/engine/data-providers.d.ts.map +1 -0
  272. package/dist/scanners/engine/data-providers.js +8 -0
  273. package/dist/scanners/engine/data-providers.js.map +1 -0
  274. package/dist/scanners/engine/engine.d.ts +76 -0
  275. package/dist/scanners/engine/engine.d.ts.map +1 -0
  276. package/dist/scanners/engine/engine.js +399 -0
  277. package/dist/scanners/engine/engine.js.map +1 -0
  278. package/dist/scanners/engine/index.d.ts +9 -0
  279. package/dist/scanners/engine/index.d.ts.map +1 -0
  280. package/dist/scanners/engine/index.js +7 -0
  281. package/dist/scanners/engine/index.js.map +1 -0
  282. package/dist/scanners/engine/input-resolver.d.ts +21 -0
  283. package/dist/scanners/engine/input-resolver.d.ts.map +1 -0
  284. package/dist/scanners/engine/input-resolver.js +140 -0
  285. package/dist/scanners/engine/input-resolver.js.map +1 -0
  286. package/dist/scanners/engine/lifecycle.d.ts +21 -0
  287. package/dist/scanners/engine/lifecycle.d.ts.map +1 -0
  288. package/dist/scanners/engine/lifecycle.js +2 -0
  289. package/dist/scanners/engine/lifecycle.js.map +1 -0
  290. package/dist/scanners/engine/result-builder.d.ts +30 -0
  291. package/dist/scanners/engine/result-builder.d.ts.map +1 -0
  292. package/dist/scanners/engine/result-builder.js +129 -0
  293. package/dist/scanners/engine/result-builder.js.map +1 -0
  294. package/dist/scanners/engine/strategy-registry.d.ts +45 -0
  295. package/dist/scanners/engine/strategy-registry.d.ts.map +1 -0
  296. package/dist/scanners/engine/strategy-registry.js +93 -0
  297. package/dist/scanners/engine/strategy-registry.js.map +1 -0
  298. package/dist/scanners/events.d.ts +41 -0
  299. package/dist/scanners/events.d.ts.map +1 -0
  300. package/dist/scanners/events.js +33 -0
  301. package/dist/scanners/events.js.map +1 -0
  302. package/dist/scanners/implementations/emerging-movers.d.ts +212 -0
  303. package/dist/scanners/implementations/emerging-movers.d.ts.map +1 -0
  304. package/dist/scanners/implementations/emerging-movers.js +702 -0
  305. package/dist/scanners/implementations/emerging-movers.js.map +1 -0
  306. package/dist/scanners/implementations/index.d.ts +8 -0
  307. package/dist/scanners/implementations/index.d.ts.map +1 -0
  308. package/dist/scanners/implementations/index.js +8 -0
  309. package/dist/scanners/implementations/index.js.map +1 -0
  310. package/dist/scanners/implementations/indicators.d.ts +40 -0
  311. package/dist/scanners/implementations/indicators.d.ts.map +1 -0
  312. package/dist/scanners/implementations/indicators.js +114 -0
  313. package/dist/scanners/implementations/indicators.js.map +1 -0
  314. package/dist/scanners/implementations/market-regime.d.ts +52 -0
  315. package/dist/scanners/implementations/market-regime.d.ts.map +1 -0
  316. package/dist/scanners/implementations/market-regime.js +201 -0
  317. package/dist/scanners/implementations/market-regime.js.map +1 -0
  318. package/dist/scanners/implementations/momentum.d.ts +21 -0
  319. package/dist/scanners/implementations/momentum.d.ts.map +1 -0
  320. package/dist/scanners/implementations/momentum.js +128 -0
  321. package/dist/scanners/implementations/momentum.js.map +1 -0
  322. package/dist/scanners/implementations/oi-tracker.d.ts +13 -0
  323. package/dist/scanners/implementations/oi-tracker.d.ts.map +1 -0
  324. package/dist/scanners/implementations/oi-tracker.js +67 -0
  325. package/dist/scanners/implementations/oi-tracker.js.map +1 -0
  326. package/dist/scanners/implementations/opportunity.d.ts +104 -0
  327. package/dist/scanners/implementations/opportunity.d.ts.map +1 -0
  328. package/dist/scanners/implementations/opportunity.js +441 -0
  329. package/dist/scanners/implementations/opportunity.js.map +1 -0
  330. package/dist/scanners/implementations/prescreener.d.ts +20 -0
  331. package/dist/scanners/implementations/prescreener.d.ts.map +1 -0
  332. package/dist/scanners/implementations/prescreener.js +97 -0
  333. package/dist/scanners/implementations/prescreener.js.map +1 -0
  334. package/dist/scanners/implementations/sm-flip.d.ts +43 -0
  335. package/dist/scanners/implementations/sm-flip.d.ts.map +1 -0
  336. package/dist/scanners/implementations/sm-flip.js +200 -0
  337. package/dist/scanners/implementations/sm-flip.js.map +1 -0
  338. package/dist/scanners/index.d.ts +24 -0
  339. package/dist/scanners/index.d.ts.map +1 -0
  340. package/dist/scanners/index.js +17 -0
  341. package/dist/scanners/index.js.map +1 -0
  342. package/dist/scanners/input-descriptors.d.ts +49 -0
  343. package/dist/scanners/input-descriptors.d.ts.map +1 -0
  344. package/dist/scanners/input-descriptors.js +35 -0
  345. package/dist/scanners/input-descriptors.js.map +1 -0
  346. package/dist/scanners/protocol/context.d.ts +71 -0
  347. package/dist/scanners/protocol/context.d.ts.map +1 -0
  348. package/dist/scanners/protocol/context.js +2 -0
  349. package/dist/scanners/protocol/context.js.map +1 -0
  350. package/dist/scanners/protocol/hooks.d.ts +18 -0
  351. package/dist/scanners/protocol/hooks.d.ts.map +1 -0
  352. package/dist/scanners/protocol/hooks.js +12 -0
  353. package/dist/scanners/protocol/hooks.js.map +1 -0
  354. package/dist/scanners/protocol/index.d.ts +5 -0
  355. package/dist/scanners/protocol/index.d.ts.map +1 -0
  356. package/dist/scanners/protocol/index.js +2 -0
  357. package/dist/scanners/protocol/index.js.map +1 -0
  358. package/dist/scanners/protocol/scanner.d.ts +47 -0
  359. package/dist/scanners/protocol/scanner.d.ts.map +1 -0
  360. package/dist/scanners/protocol/scanner.js +2 -0
  361. package/dist/scanners/protocol/scanner.js.map +1 -0
  362. package/dist/scanners/providers/adapter.d.ts +23 -0
  363. package/dist/scanners/providers/adapter.d.ts.map +1 -0
  364. package/dist/scanners/providers/adapter.js +227 -0
  365. package/dist/scanners/providers/adapter.js.map +1 -0
  366. package/dist/scanners/providers/cache.d.ts +20 -0
  367. package/dist/scanners/providers/cache.d.ts.map +1 -0
  368. package/dist/scanners/providers/cache.js +61 -0
  369. package/dist/scanners/providers/cache.js.map +1 -0
  370. package/dist/scanners/providers/client.d.ts +53 -0
  371. package/dist/scanners/providers/client.d.ts.map +1 -0
  372. package/dist/scanners/providers/client.js +174 -0
  373. package/dist/scanners/providers/client.js.map +1 -0
  374. package/dist/scanners/providers/factory.d.ts +42 -0
  375. package/dist/scanners/providers/factory.d.ts.map +1 -0
  376. package/dist/scanners/providers/factory.js +156 -0
  377. package/dist/scanners/providers/factory.js.map +1 -0
  378. package/dist/scanners/providers/types.d.ts +61 -0
  379. package/dist/scanners/providers/types.d.ts.map +1 -0
  380. package/dist/scanners/providers/types.js +6 -0
  381. package/dist/scanners/providers/types.js.map +1 -0
  382. package/dist/scanners/runtime-module.d.ts +54 -0
  383. package/dist/scanners/runtime-module.d.ts.map +1 -0
  384. package/dist/scanners/runtime-module.js +94 -0
  385. package/dist/scanners/runtime-module.js.map +1 -0
  386. package/dist/scanners/scanner-definition.d.ts +58 -0
  387. package/dist/scanners/scanner-definition.d.ts.map +1 -0
  388. package/dist/scanners/scanner-definition.js +19 -0
  389. package/dist/scanners/scanner-definition.js.map +1 -0
  390. package/dist/scanners/schema-utils.d.ts +6 -0
  391. package/dist/scanners/schema-utils.d.ts.map +1 -0
  392. package/dist/scanners/schema-utils.js +14 -0
  393. package/dist/scanners/schema-utils.js.map +1 -0
  394. package/dist/scanners/score-utils.d.ts +19 -0
  395. package/dist/scanners/score-utils.d.ts.map +1 -0
  396. package/dist/scanners/score-utils.js +47 -0
  397. package/dist/scanners/score-utils.js.map +1 -0
  398. package/dist/scanners/store/fs-utils.d.ts +12 -0
  399. package/dist/scanners/store/fs-utils.d.ts.map +1 -0
  400. package/dist/scanners/store/fs-utils.js +47 -0
  401. package/dist/scanners/store/fs-utils.js.map +1 -0
  402. package/dist/scanners/store/index.d.ts +5 -0
  403. package/dist/scanners/store/index.d.ts.map +1 -0
  404. package/dist/scanners/store/index.js +5 -0
  405. package/dist/scanners/store/index.js.map +1 -0
  406. package/dist/scanners/store/runtime-store.d.ts +50 -0
  407. package/dist/scanners/store/runtime-store.d.ts.map +1 -0
  408. package/dist/scanners/store/runtime-store.js +91 -0
  409. package/dist/scanners/store/runtime-store.js.map +1 -0
  410. package/dist/scanners/store/shared-artifact-store.d.ts +31 -0
  411. package/dist/scanners/store/shared-artifact-store.d.ts.map +1 -0
  412. package/dist/scanners/store/shared-artifact-store.js +89 -0
  413. package/dist/scanners/store/shared-artifact-store.js.map +1 -0
  414. package/dist/scanners/store/signal-store.d.ts +36 -0
  415. package/dist/scanners/store/signal-store.d.ts.map +1 -0
  416. package/dist/scanners/store/signal-store.js +135 -0
  417. package/dist/scanners/store/signal-store.js.map +1 -0
  418. package/dist/scanners/types.d.ts +115 -0
  419. package/dist/scanners/types.d.ts.map +1 -0
  420. package/dist/scanners/types.js +3 -0
  421. package/dist/scanners/types.js.map +1 -0
  422. package/dist/scripts/run-dsl-standalone-live.d.ts +27 -0
  423. package/dist/scripts/run-dsl-standalone-live.d.ts.map +1 -0
  424. package/dist/scripts/run-dsl-standalone-live.js +207 -0
  425. package/dist/scripts/run-dsl-standalone-live.js.map +1 -0
  426. package/dist/scripts/run-dsl-standalone-mock.d.ts +13 -0
  427. package/dist/scripts/run-dsl-standalone-mock.d.ts.map +1 -0
  428. package/dist/scripts/run-dsl-standalone-mock.js +255 -0
  429. package/dist/scripts/run-dsl-standalone-mock.js.map +1 -0
  430. package/dist/senpi/client.d.ts +70 -0
  431. package/dist/senpi/client.d.ts.map +1 -0
  432. package/dist/senpi/client.js +482 -0
  433. package/dist/senpi/client.js.map +1 -0
  434. package/dist/senpi/constants.d.ts +7 -0
  435. package/dist/senpi/constants.d.ts.map +1 -0
  436. package/dist/senpi/constants.js +7 -0
  437. package/dist/senpi/constants.js.map +1 -0
  438. package/dist/senpi/index.d.ts +3 -0
  439. package/dist/senpi/index.d.ts.map +1 -0
  440. package/dist/senpi/index.js +2 -0
  441. package/dist/senpi/index.js.map +1 -0
  442. package/dist/senpi/types.d.ts +132 -0
  443. package/dist/senpi/types.d.ts.map +1 -0
  444. package/dist/senpi/types.js +3 -0
  445. package/dist/senpi/types.js.map +1 -0
  446. package/dist/state/index.d.ts +3 -0
  447. package/dist/state/index.d.ts.map +1 -0
  448. package/dist/state/index.js +3 -0
  449. package/dist/state/index.js.map +1 -0
  450. package/dist/state/paths.d.ts +8 -0
  451. package/dist/state/paths.d.ts.map +1 -0
  452. package/dist/state/paths.js +19 -0
  453. package/dist/state/paths.js.map +1 -0
  454. package/dist/state/state-manager.d.ts +56 -0
  455. package/dist/state/state-manager.d.ts.map +1 -0
  456. package/dist/state/state-manager.js +213 -0
  457. package/dist/state/state-manager.js.map +1 -0
  458. package/dist/strategy/index.d.ts +2 -0
  459. package/dist/strategy/index.d.ts.map +1 -0
  460. package/dist/strategy/index.js +2 -0
  461. package/dist/strategy/index.js.map +1 -0
  462. package/dist/strategy/strategy-state.d.ts +47 -0
  463. package/dist/strategy/strategy-state.d.ts.map +1 -0
  464. package/dist/strategy/strategy-state.js +179 -0
  465. package/dist/strategy/strategy-state.js.map +1 -0
  466. package/dist/types/action.d.ts +15 -0
  467. package/dist/types/action.d.ts.map +1 -0
  468. package/dist/types/action.js +2 -0
  469. package/dist/types/action.js.map +1 -0
  470. package/dist/types/dsl/index.d.ts +156 -0
  471. package/dist/types/dsl/index.d.ts.map +1 -0
  472. package/dist/types/dsl/index.js +5 -0
  473. package/dist/types/dsl/index.js.map +1 -0
  474. package/dist/types/event-bus.d.ts +11 -0
  475. package/dist/types/event-bus.d.ts.map +1 -0
  476. package/dist/types/event-bus.js +6 -0
  477. package/dist/types/event-bus.js.map +1 -0
  478. package/dist/types/hooks.d.ts +27 -0
  479. package/dist/types/hooks.d.ts.map +1 -0
  480. package/dist/types/hooks.js +15 -0
  481. package/dist/types/hooks.js.map +1 -0
  482. package/dist/types/index.d.ts +6 -0
  483. package/dist/types/index.d.ts.map +1 -0
  484. package/dist/types/index.js +6 -0
  485. package/dist/types/index.js.map +1 -0
  486. package/dist/types/notification.d.ts +4 -0
  487. package/dist/types/notification.d.ts.map +1 -0
  488. package/dist/types/notification.js +3 -0
  489. package/dist/types/notification.js.map +1 -0
  490. package/dist/types/runtime.d.ts +16 -0
  491. package/dist/types/runtime.d.ts.map +1 -0
  492. package/dist/types/runtime.js +2 -0
  493. package/dist/types/runtime.js.map +1 -0
  494. package/dist/types/scanner.d.ts +59 -0
  495. package/dist/types/scanner.d.ts.map +1 -0
  496. package/dist/types/scanner.js +13 -0
  497. package/dist/types/scanner.js.map +1 -0
  498. package/dist/types/strategy.d.ts +97 -0
  499. package/dist/types/strategy.d.ts.map +1 -0
  500. package/dist/types/strategy.js +2 -0
  501. package/dist/types/strategy.js.map +1 -0
  502. package/dist/utils/logger/dsl.d.ts +8 -0
  503. package/dist/utils/logger/dsl.d.ts.map +1 -0
  504. package/dist/utils/logger/dsl.js +7 -0
  505. package/dist/utils/logger/dsl.js.map +1 -0
  506. package/dist/utils/logger.d.ts +13 -0
  507. package/dist/utils/logger.d.ts.map +1 -0
  508. package/dist/utils/logger.js +65 -0
  509. package/dist/utils/logger.js.map +1 -0
  510. package/dist/utils/response.d.ts +14 -0
  511. package/dist/utils/response.d.ts.map +1 -0
  512. package/dist/utils/response.js +44 -0
  513. package/dist/utils/response.js.map +1 -0
  514. package/dist/version.d.ts +2 -0
  515. package/dist/version.d.ts.map +1 -0
  516. package/dist/version.js +2 -0
  517. package/dist/version.js.map +1 -0
  518. package/examples/.env.example +15 -0
  519. package/examples/README.md +75 -0
  520. package/examples/scanners-consumer-quickstart.mjs +134 -0
  521. package/examples/scanners-emerging-movers-v4.mjs +285 -0
  522. package/examples/scanners-live-mcp.mjs +208 -0
  523. package/examples/scanners-multi-strategy.mjs +165 -0
  524. package/examples/scanners-scheduled-events.mjs +95 -0
  525. package/examples/scanners-senpi-provider-client.mjs +182 -0
  526. package/examples/scanners-single-strategy.mjs +198 -0
  527. package/examples/strategies/README.md +45 -0
  528. package/examples/strategies/dsl-showcase.yaml +62 -0
  529. package/examples/strategies/fox.yaml +55 -0
  530. package/examples/strategies/minimal.yaml +41 -0
  531. package/examples/strategies/viper.yaml +63 -0
  532. package/examples/strategies/wolf.yaml +60 -0
  533. package/examples/yamls/sample-with-dsl-and-emerging-movers.yaml +111 -0
  534. package/openclaw.plugin.json +15 -0
  535. package/package.json +52 -0
@@ -0,0 +1,702 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { createScanner } from "../create-scanner.js";
3
+ import { consumeSharedArtifact, MarketRegime, marketRegimeArtifact } from "../artifacts.js";
4
+ import { providerInput } from "../input-descriptors.js";
5
+ import { clamp01 } from "../score-utils.js";
6
+ import { PersistencePolicy, RetentionPolicy } from "../scanner-definition.js";
7
+ /**
8
+ * Numeric priority per signal type (1 = highest).
9
+ * Used to sort signals so that the action layer processes the most urgent first.
10
+ */
11
+ const SIGNAL_PRIORITY = {
12
+ FIRST_JUMP: 1,
13
+ CONTRIB_EXPLOSION: 2,
14
+ IMMEDIATE_MOVER: 3,
15
+ NEW_ENTRY_DEEP: 4,
16
+ DEEP_CLIMBER: 5,
17
+ };
18
+ /**
19
+ * Default conviction score per signal type, mapped to the [0, 1] range
20
+ * consumed by the action layer. Higher conviction = stronger entry signal.
21
+ * These are used as the signal's `score` field so downstream consumers can
22
+ * sort/filter without knowing the signal type taxonomy.
23
+ */
24
+ const SIGNAL_CONVICTION = {
25
+ FIRST_JUMP: 0.95,
26
+ CONTRIB_EXPLOSION: 0.90,
27
+ IMMEDIATE_MOVER: 0.85,
28
+ NEW_ENTRY_DEEP: 0.80,
29
+ DEEP_CLIMBER: 0.60,
30
+ };
31
+ // ─── Config ─────────────────────────────────────────────────────────────────
32
+ const emergingMoversConfigSchema = Type.Object({
33
+ topN: Type.Number({ minimum: 1, default: 50 }),
34
+ minRankJump: Type.Number({ minimum: 1, default: 5 }),
35
+ minTopTradersGain: Type.Number({ minimum: 0, default: 0.1 }),
36
+ minScansBeforeSignals: Type.Number({ minimum: 1, default: 2 }),
37
+ historyLimit: Type.Number({ minimum: 2, default: 60 }),
38
+ immediateJumpThreshold: Type.Optional(Type.Number({ minimum: 1, default: 10 })),
39
+ deepClimbRankThreshold: Type.Optional(Type.Number({ minimum: 1, default: 25 })),
40
+ contribExplosionMultiplier: Type.Optional(Type.Number({ minimum: 1, default: 3.0 })),
41
+ contribAccelThreshold: Type.Optional(Type.Number({ minimum: 0, default: 0.003 })),
42
+ minVelocityForDeepClimber: Type.Optional(Type.Number({ minimum: 0, default: 0.0003 })),
43
+ erraticReversalThreshold: Type.Optional(Type.Number({ minimum: 1, default: 5 })),
44
+ climbStreakScans: Type.Optional(Type.Number({ minimum: 2, default: 3 })),
45
+ rankClimbThreshold: Type.Optional(Type.Number({ minimum: 1, default: 3 })),
46
+ newEntryDeepMaxRank: Type.Optional(Type.Number({ minimum: 1, default: 20 })),
47
+ newEntryMaxRank: Type.Optional(Type.Number({ minimum: 1, default: 35 })),
48
+ firstJumpMinPrevRank: Type.Optional(Type.Number({ minimum: 1, default: 30 })),
49
+ /** Enable regime-aware score gating via market-regime artifact. Default: false (disabled). */
50
+ regimeScoreGating: Type.Optional(Type.Boolean({ default: false })),
51
+ /** Score threshold when market regime is NEUTRAL. Only used when regimeScoreGating is true. */
52
+ regimeScoreNeutral: Type.Optional(Type.Number({ minimum: 0, maximum: 1, default: 0.8 })),
53
+ /** Score threshold when market regime is BULLISH or BEARISH. Only used when regimeScoreGating is true. */
54
+ regimeScoreDefault: Type.Optional(Type.Number({ minimum: 0, maximum: 1, default: 0.7 })),
55
+ /** Minimum asset max leverage to emit signals. 0 = disabled (no filtering). */
56
+ minLeverage: Type.Optional(Type.Number({ minimum: 0, default: 0 })),
57
+ });
58
+ const emergingMoversStateSchema = Type.Object({
59
+ scans: Type.Number({ minimum: 0 }),
60
+ rankHistory: Type.Record(Type.String(), Type.Array(Type.Number())),
61
+ contribHistory: Type.Record(Type.String(), Type.Array(Type.Number())),
62
+ prevTopTokens: Type.Array(Type.String()),
63
+ });
64
+ // ─── Defaults ───────────────────────────────────────────────────────────────
65
+ const defaultConfig = {
66
+ topN: 50,
67
+ minRankJump: 5,
68
+ minTopTradersGain: 0.1,
69
+ minScansBeforeSignals: 2,
70
+ historyLimit: 60,
71
+ immediateJumpThreshold: 10,
72
+ deepClimbRankThreshold: 25,
73
+ contribExplosionMultiplier: 3.0,
74
+ contribAccelThreshold: 0.003,
75
+ minVelocityForDeepClimber: 0.0003,
76
+ erraticReversalThreshold: 5,
77
+ climbStreakScans: 3,
78
+ rankClimbThreshold: 3,
79
+ newEntryDeepMaxRank: 20,
80
+ newEntryMaxRank: 35,
81
+ firstJumpMinPrevRank: 30,
82
+ regimeScoreGating: false,
83
+ regimeScoreNeutral: 0.8,
84
+ regimeScoreDefault: 0.7,
85
+ minLeverage: 0,
86
+ };
87
+ const defaultState = {
88
+ scans: 0,
89
+ rankHistory: {},
90
+ contribHistory: {},
91
+ prevTopTokens: [],
92
+ };
93
+ // ─── Pure helpers (exported for testing) ────────────────────────────────────
94
+ /**
95
+ * Detects zigzag rank patterns that indicate market noise rather than a
96
+ * genuine trend.
97
+ *
98
+ * Scans consecutive rank deltas for direction reversals that exceed the
99
+ * threshold (e.g. improving 10 ranks then dropping 8). Such zigzags suggest
100
+ * the token is bouncing rather than sustainably climbing, and are used to
101
+ * downgrade IMMEDIATE_MOVER signals to the lower-priority DEEP_CLIMBER.
102
+ *
103
+ * @param excludeLast - When `true`, the final entry (this scan's rank) is
104
+ * removed before checking. This is critical for big-jump signals: the
105
+ * jump itself is the signal, not noise, so only the *pre-jump* history
106
+ * should be evaluated for erratic behaviour.
107
+ */
108
+ export function isErraticHistory(rankHistory, erraticReversalThreshold, excludeLast = false) {
109
+ let nums = rankHistory.filter((r) => r !== null);
110
+ if (excludeLast && nums.length > 1) {
111
+ nums = nums.slice(0, -1);
112
+ }
113
+ if (nums.length < 3)
114
+ return false;
115
+ for (let i = 1; i < nums.length - 1; i++) {
116
+ const prevDelta = nums[i] - nums[i - 1];
117
+ const nextDelta = nums[i + 1] - nums[i];
118
+ if (prevDelta < 0 && nextDelta > erraticReversalThreshold)
119
+ return true;
120
+ if (prevDelta > 0 && nextDelta < -erraticReversalThreshold)
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+ /**
126
+ * Computes average contribution change per scan over the given window.
127
+ *
128
+ * Uses (last - first) / (length - 1) rather than summing individual deltas,
129
+ * which is algebraically identical but avoids floating-point accumulation.
130
+ * A positive result means contribution is trending up; the velocity gate
131
+ * uses this to filter out tokens whose rank improvement is not backed by
132
+ * growing smart-money concentration.
133
+ */
134
+ export function computeContribVelocity(contribs) {
135
+ if (contribs.length < 2)
136
+ return 0;
137
+ const total = contribs[contribs.length - 1] - contribs[0];
138
+ return total / (contribs.length - 1);
139
+ }
140
+ /**
141
+ * Classifies an alert candidate into a signal type and applies quality
142
+ * filters (erratic history, velocity gate).
143
+ *
144
+ * The classification pipeline is:
145
+ * 1. **Resolve signal type** from detection flags (priority order).
146
+ * 2. **Check erratic history** — zigzag rank patterns indicate noise.
147
+ * 3. **Check velocity gate** — contribution velocity must be positive
148
+ * (IMMEDIATE/FIRST_JUMP) or above a minimum (DEEP_CLIMBER).
149
+ * 4. **Apply downgrades** — IMMEDIATE_MOVER is demoted to DEEP_CLIMBER
150
+ * when erratic or low-velocity. FIRST_JUMP and CONTRIB_EXPLOSION are
151
+ * immune to downgrades because they represent decisive, one-scan events
152
+ * where historical noise is irrelevant.
153
+ *
154
+ * @returns Classification result, or `null` if the candidate has no reasons
155
+ * (should not happen if called from {@link analyzeMarket}).
156
+ */
157
+ export function classifySignal(alert, config) {
158
+ if (alert.reasons.length === 0)
159
+ return null;
160
+ let signalType = resolveSignalType(alert);
161
+ const erratic = checkErratic(alert, config);
162
+ const lowVelocity = checkLowVelocity(alert, config);
163
+ // FIRST_JUMP and CONTRIB_EXPLOSION are never downgraded
164
+ if (signalType === "IMMEDIATE_MOVER" && (erratic || lowVelocity)) {
165
+ signalType = "DEEP_CLIMBER";
166
+ }
167
+ return {
168
+ signalType,
169
+ priority: SIGNAL_PRIORITY[signalType],
170
+ conviction: SIGNAL_CONVICTION[signalType],
171
+ erratic,
172
+ lowVelocity,
173
+ };
174
+ }
175
+ // ─── Signal type resolution ─────────────────────────────────────────────────
176
+ /**
177
+ * Maps detection flags to the highest-priority applicable signal type.
178
+ *
179
+ * The check order is deliberate:
180
+ * - FIRST_JUMP before everything — it's the strongest "catch it early" signal.
181
+ * - CONTRIB_EXPLOSION before IMMEDIATE — contribution spike is independent of rank.
182
+ * - NEW_ENTRY_DEEP before IMMEDIATE — a brand-new token appearing in the top 20
183
+ * is semantically different from an existing token jumping ranks.
184
+ * - IMMEDIATE_MOVER before DEEP_CLIMBER — larger, faster jumps rank higher.
185
+ * - DEEP_CLIMBER is the fallback for any remaining alert with reasons.
186
+ */
187
+ function resolveSignalType(alert) {
188
+ if (alert.isFirstJump)
189
+ return "FIRST_JUMP";
190
+ if (alert.isContribExplosion)
191
+ return "CONTRIB_EXPLOSION";
192
+ if (alert.isDeepClimber && alert.reasons.some((r) => r.includes("NEW_ENTRY_DEEP"))) {
193
+ return "NEW_ENTRY_DEEP";
194
+ }
195
+ if (alert.isImmediate)
196
+ return "IMMEDIATE_MOVER";
197
+ return "DEEP_CLIMBER";
198
+ }
199
+ /**
200
+ * Determines whether the alert's rank history is erratic (zigzag).
201
+ *
202
+ * CONTRIB_EXPLOSION is exempt because the contribution spike itself is the
203
+ * signal — past rank noise doesn't diminish it. For big single-scan jumps
204
+ * (IMMEDIATE/FIRST_JUMP), only the *pre-jump* history is checked because
205
+ * the jump itself shouldn't count as a reversal.
206
+ */
207
+ function checkErratic(alert, cfg) {
208
+ if (alert.isContribExplosion)
209
+ return false;
210
+ const excludeLast = alert.rankJumpThisScan >= cfg.immediateJumpThreshold || alert.isFirstJump;
211
+ return isErraticHistory(alert.rankHistoryWindow, cfg.erraticReversalThreshold, excludeLast);
212
+ }
213
+ /**
214
+ * Determines whether the alert's contribution velocity is too low.
215
+ *
216
+ * The threshold is split by urgency:
217
+ * - IMMEDIATE/FIRST_JUMP only require velocity > 0. These are one-scan
218
+ * signals where velocity hasn't had time to build; demanding more
219
+ * would filter out exactly the early entries the scanner is designed to catch.
220
+ * - DEEP_CLIMBER (slower, multi-scan) requires velocity >= `minVelocityForDeepClimber`
221
+ * to confirm that the rank improvement is backed by growing SM concentration.
222
+ */
223
+ function checkLowVelocity(alert, cfg) {
224
+ if (alert.isImmediate || alert.isFirstJump)
225
+ return alert.contribVelocity <= 0;
226
+ return alert.contribVelocity < cfg.minVelocityForDeepClimber;
227
+ }
228
+ // ─── Detection rules ────────────────────────────────────────────────────────
229
+ // Each rule inspects the immutable MarketContext and appends reasons / flips
230
+ // flags in the mutable DetectionFlags. Rules are ordered to match the Python
231
+ // v4 detection pipeline so that flag interactions (e.g. isImmediate being set
232
+ // before FIRST_JUMP is checked) are preserved.
233
+ /**
234
+ * Rule 1 — Fresh entry detection.
235
+ *
236
+ * Fires when the token was not in the previous scan's top-N (`prevRank === null`).
237
+ * If the token lands directly in the top 20, it's flagged as NEW_ENTRY_DEEP
238
+ * with `isImmediate` (the market didn't gradually climb — it appeared strong).
239
+ * Tokens landing between #20 and #35 get a lower-priority NEW_ENTRY tag.
240
+ */
241
+ function detectNewEntry(mctx, cfg, flags) {
242
+ if (mctx.prevRank !== null)
243
+ return;
244
+ if (mctx.rank <= cfg.newEntryDeepMaxRank) {
245
+ flags.reasons.push(`NEW_ENTRY_DEEP at rank #${mctx.rank}`);
246
+ flags.isDeepClimber = true;
247
+ flags.isImmediate = true;
248
+ }
249
+ else if (mctx.rank <= cfg.newEntryMaxRank) {
250
+ flags.reasons.push(`NEW_ENTRY at rank #${mctx.rank}`);
251
+ }
252
+ }
253
+ /**
254
+ * Rule 2 — Single-scan rank jump detection.
255
+ *
256
+ * Compares the token's rank in this scan vs. the previous one. Three tiers:
257
+ *
258
+ * - **FIRST_JUMP**: jump >= `immediateJumpThreshold` from >= `deepClimbRankThreshold`,
259
+ * AND the token was either absent from the previous top-N or ranked >= `firstJumpMinPrevRank`.
260
+ * This is the highest-priority signal — it catches tokens accelerating into
261
+ * the leaderboard before they're widely noticed.
262
+ *
263
+ * - **IMMEDIATE_MOVER**: same jump magnitude but the token was already tracked
264
+ * in the top-N at a rank < `firstJumpMinPrevRank` — still a strong signal
265
+ * but with less surprise value than FIRST_JUMP.
266
+ *
267
+ * - **DEEP_CLIMBER**: jump >= `minRankJump` from >= `deepClimbRankThreshold`.
268
+ * Moderate but notable improvement from deep in the list.
269
+ *
270
+ * Also records a RANK_UP reason for any jump >= 2 (informational, even if no
271
+ * signal type is triggered).
272
+ */
273
+ function detectSingleScanJump(mctx, cfg, flags, prevTopTokenSet) {
274
+ if (mctx.prevRank === null)
275
+ return;
276
+ const jump = mctx.prevRank - mctx.rank;
277
+ flags.rankJumpThisScan = jump;
278
+ if (jump >= 2) {
279
+ flags.reasons.push(`RANK_UP +${jump} (#${mctx.prevRank}→#${mctx.rank})`);
280
+ }
281
+ if (jump >= cfg.immediateJumpThreshold && mctx.prevRank >= cfg.deepClimbRankThreshold) {
282
+ flags.isDeepClimber = true;
283
+ flags.isImmediate = true;
284
+ flags.reasons.push(`IMMEDIATE_MOVER +${jump} from #${mctx.prevRank} in ONE scan`);
285
+ const wasInPrev = prevTopTokenSet.has(mctx.tk);
286
+ if (!wasInPrev || mctx.prevRank >= cfg.firstJumpMinPrevRank) {
287
+ flags.isFirstJump = true;
288
+ flags.reasons.push(`FIRST_JUMP from #${mctx.prevRank}→#${mctx.rank} — highest priority`);
289
+ }
290
+ }
291
+ else if (jump >= cfg.minRankJump && mctx.prevRank >= cfg.deepClimbRankThreshold) {
292
+ flags.isDeepClimber = true;
293
+ flags.reasons.push(`DEEP_CLIMBER +${jump} from #${mctx.prevRank}`);
294
+ }
295
+ }
296
+ /**
297
+ * Rule 3 — Contribution explosion detection.
298
+ *
299
+ * Fires when the token's `pctOfTopTradersGain` increased by
300
+ * `contribExplosionMultiplier`x or more in a single scan. This indicates a
301
+ * sudden concentration of smart-money capital into the token — often the
302
+ * earliest sign of a coordinated move.
303
+ *
304
+ * When the explosion originates from a deep position (prevRank >= 20),
305
+ * the alert is also flagged as immediate/deep-climber to boost its priority.
306
+ */
307
+ function detectContribExplosion(mctx, cfg, flags) {
308
+ if (mctx.prevContrib === null || mctx.prevContrib <= 0)
309
+ return;
310
+ const ratio = mctx.currentContrib / mctx.prevContrib;
311
+ if (ratio < cfg.contribExplosionMultiplier)
312
+ return;
313
+ const prev = (mctx.prevContrib * 100).toFixed(2);
314
+ const curr = (mctx.currentContrib * 100).toFixed(2);
315
+ flags.reasons.push(`CONTRIB_EXPLOSION ${ratio.toFixed(1)}x in one scan (${prev}→${curr})`);
316
+ flags.isContribExplosion = true;
317
+ if (mctx.prevRank !== null && mctx.prevRank >= cfg.newEntryDeepMaxRank) {
318
+ flags.isImmediate = true;
319
+ flags.isDeepClimber = true;
320
+ }
321
+ }
322
+ /**
323
+ * Rule 4 — Multi-scan cumulative climb detection.
324
+ *
325
+ * Looks back up to 5 scans and compares the oldest available rank to the
326
+ * current one. A total climb >= `rankClimbThreshold` adds a CLIMBING reason.
327
+ *
328
+ * If the cumulative climb is large enough (>= `immediateJumpThreshold`) and
329
+ * started from rank >= 30, it additionally flags as DEEP_CLIMBER — the token
330
+ * may not have jumped dramatically in any single scan but has been steadily
331
+ * rising from the depths.
332
+ */
333
+ function detectMultiScanClimb(mctx, cfg, flags) {
334
+ const lookback = Math.min(mctx.prevRankHistory.length, 5);
335
+ if (lookback < 1)
336
+ return;
337
+ const oldRank = mctx.prevRankHistory[mctx.prevRankHistory.length - lookback];
338
+ if (oldRank === undefined)
339
+ return;
340
+ const totalClimb = oldRank - mctx.rank;
341
+ if (totalClimb >= cfg.rankClimbThreshold) {
342
+ flags.reasons.push(`CLIMBING +${totalClimb} over ${lookback} scans`);
343
+ }
344
+ if (totalClimb >= cfg.immediateJumpThreshold && oldRank >= 30) {
345
+ flags.isDeepClimber = true;
346
+ const alreadyTagged = flags.reasons.some((r) => r.includes("DEEP_CLIMBER") || r.includes("IMMEDIATE"));
347
+ if (!alreadyTagged) {
348
+ flags.reasons.push(`DEEP_CLIMBER +${totalClimb} from #${oldRank} over ${lookback} scans`);
349
+ }
350
+ }
351
+ }
352
+ /**
353
+ * Rule 5 — Single-scan contribution acceleration.
354
+ *
355
+ * Fires when the contribution delta (current - previous) exceeds
356
+ * `contribAccelThreshold`. Unlike the explosion rule (which checks
357
+ * *ratios*), this catches absolute contribution increases — relevant
358
+ * when the previous contribution was already substantial.
359
+ */
360
+ function detectContribAccel(mctx, cfg, flags) {
361
+ if (mctx.prevContrib === null)
362
+ return;
363
+ const delta = mctx.currentContrib - mctx.prevContrib;
364
+ if (delta >= cfg.contribAccelThreshold) {
365
+ flags.reasons.push(`ACCEL +${delta.toFixed(3)} contribution`);
366
+ }
367
+ }
368
+ /**
369
+ * Rule 6 — Consistent climb streak detection.
370
+ *
371
+ * Checks whether the token's rank has improved (or stayed equal) in every
372
+ * scan over the last `climbStreakScans + 1` data points (including the current
373
+ * scan). A monotonically improving rank over multiple scans is a strong
374
+ * trend signal even when no single scan's jump is dramatic.
375
+ */
376
+ function detectClimbStreak(mctx, cfg, flags) {
377
+ if (mctx.prevRankHistory.length < cfg.climbStreakScans)
378
+ return;
379
+ const window = [...mctx.prevRankHistory.slice(-cfg.climbStreakScans), mctx.rank];
380
+ const isMonotonic = window.every((r, i) => i === 0 || window[i - 1] >= r);
381
+ const totalGain = window[0] - window[window.length - 1];
382
+ if (isMonotonic && totalGain >= 2) {
383
+ flags.reasons.push(`STREAK climbing ${totalGain} ranks over ${window.length} checks`);
384
+ }
385
+ }
386
+ /**
387
+ * Rule 7 — Contribution velocity as a standalone signal.
388
+ *
389
+ * Computes the average contribution change per scan over the last 5 scans.
390
+ * If velocity exceeds 0.2%/scan, at least 3 data points are available, and
391
+ * *no other detection rule has fired*, a VELOCITY reason is emitted.
392
+ *
393
+ * This acts as a catch-all for tokens that are gaining SM concentration
394
+ * gradually — not enough for an explosion or a big rank jump, but sustained
395
+ * enough to be noteworthy.
396
+ *
397
+ * @returns The computed velocity value (always returned, regardless of whether
398
+ * a reason was added) so that the caller can attach it to the alert.
399
+ */
400
+ function detectVelocitySignal(mctx, flags) {
401
+ const recent = [...mctx.prevContribHistory.slice(-5), mctx.currentContrib];
402
+ const velocity = computeContribVelocity(recent);
403
+ if (velocity > 0.002 && recent.length >= 3 && flags.reasons.length === 0) {
404
+ flags.reasons.push(`VELOCITY +${(velocity * 100).toFixed(3)}%/scan sustained`);
405
+ }
406
+ return velocity;
407
+ }
408
+ // ─── Market analysis pipeline ───────────────────────────────────────────────
409
+ /**
410
+ * Runs all detection rules against a single ranked market.
411
+ *
412
+ * This is the core of the scanner: it initialises empty detection flags,
413
+ * runs each rule in sequence (order matters — flags set by earlier rules
414
+ * affect later ones), computes contribution velocity, and assembles the
415
+ * full {@link AlertCandidate} if any reasons were found.
416
+ *
417
+ * @returns An alert candidate, or `null` if no detection rule fired.
418
+ */
419
+ function analyzeMarket(mctx, cfg, prevTopTokenSet) {
420
+ const flags = {
421
+ reasons: [],
422
+ isDeepClimber: false,
423
+ isImmediate: false,
424
+ isFirstJump: false,
425
+ isContribExplosion: false,
426
+ rankJumpThisScan: 0,
427
+ };
428
+ detectNewEntry(mctx, cfg, flags);
429
+ detectSingleScanJump(mctx, cfg, flags, prevTopTokenSet);
430
+ detectContribExplosion(mctx, cfg, flags);
431
+ detectMultiScanClimb(mctx, cfg, flags);
432
+ detectContribAccel(mctx, cfg, flags);
433
+ detectClimbStreak(mctx, cfg, flags);
434
+ const contribVelocity = detectVelocitySignal(mctx, flags);
435
+ if (flags.reasons.length === 0)
436
+ return null;
437
+ return {
438
+ token: mctx.market.token,
439
+ dex: mctx.market.dex,
440
+ direction: mctx.market.direction,
441
+ currentRank: mctx.rank,
442
+ currentContrib: mctx.currentContrib,
443
+ priceChg4h: mctx.market.tokenPriceChangePct4h,
444
+ traderCount: mctx.market.traderCount,
445
+ ...flags,
446
+ contribVelocity,
447
+ rankHistoryWindow: [...mctx.prevRankHistory.slice(-5), mctx.rank],
448
+ contribHistoryWindow: [
449
+ ...mctx.prevContribHistory.slice(-5).map((c) => Math.round(c * 10000) / 100),
450
+ Math.round(mctx.currentContrib * 10000) / 100,
451
+ ],
452
+ };
453
+ }
454
+ // ─── Alert → Signal conversion ──────────────────────────────────────────────
455
+ /**
456
+ * Converts a classified alert candidate into the canonical {@link Signal} shape.
457
+ *
458
+ * Handles DEX-qualified asset naming (`xyz:TOKEN`), direction normalisation
459
+ * (the SM data uses lowercase strings, signals require "LONG" | "SHORT"),
460
+ * and packs all detection metadata into the signal's `factors` and `meta`
461
+ * fields for downstream transparency.
462
+ *
463
+ * @returns A signal, or `null` if classification returned null (defensive).
464
+ */
465
+ function alertToSignal(alert, cfg, ctx) {
466
+ const classification = classifySignal(alert, cfg);
467
+ if (classification === null)
468
+ return null;
469
+ return {
470
+ address: ctx.address,
471
+ scannerId: ctx.scannerId,
472
+ signalType: classification.signalType,
473
+ asset: alert.dex === "xyz" ? `xyz:${alert.token}` : alert.token,
474
+ direction: alert.direction.toUpperCase() === "SHORT" ? "SHORT" : "LONG",
475
+ score: clamp01(classification.conviction),
476
+ timestamp: ctx.timestamp,
477
+ factors: {
478
+ rank_jump: alert.rankJumpThisScan > 0,
479
+ new_entry: alert.reasons.some((r) => r.startsWith("NEW_ENTRY")),
480
+ contrib_explosion: alert.isContribExplosion,
481
+ deep_climber: alert.isDeepClimber,
482
+ immediate_mover: alert.isImmediate,
483
+ first_jump: alert.isFirstJump,
484
+ erratic: classification.erratic,
485
+ low_velocity: classification.lowVelocity,
486
+ },
487
+ meta: {
488
+ signalPriority: classification.priority,
489
+ rank: alert.currentRank,
490
+ prevRank: alert.rankHistoryWindow.length > 1
491
+ ? alert.rankHistoryWindow[alert.rankHistoryWindow.length - 2]
492
+ : null,
493
+ jump: alert.rankJumpThisScan,
494
+ dex: alert.dex ?? null,
495
+ direction: alert.direction,
496
+ topTradersGain: alert.currentContrib,
497
+ priceChg4h: alert.priceChg4h,
498
+ traderCount: alert.traderCount,
499
+ contribVelocity: Math.round(alert.contribVelocity * 10000) / 10000,
500
+ rankHistory: alert.rankHistoryWindow,
501
+ contribHistory: alert.contribHistoryWindow,
502
+ reasons: alert.reasons,
503
+ marketRegime: ctx.marketRegime ?? "UNKNOWN",
504
+ },
505
+ };
506
+ }
507
+ /**
508
+ * Sorts signals in-place by descending urgency.
509
+ *
510
+ * Primary key: signal priority (1 = most urgent).
511
+ * Secondary key: absolute contribution velocity (higher = more active).
512
+ * Tertiary key: number of detection reasons (more reasons = richer signal).
513
+ *
514
+ * This ordering ensures the action layer processes the most time-sensitive
515
+ * signals first when iterating through the FIFO queue.
516
+ */
517
+ function sortByPriority(signals) {
518
+ signals.sort((a, b) => {
519
+ const pa = a.meta.signalPriority ?? 99;
520
+ const pb = b.meta.signalPriority ?? 99;
521
+ if (pa !== pb)
522
+ return pa - pb;
523
+ const va = Math.abs(a.meta.contribVelocity ?? 0);
524
+ const vb = Math.abs(b.meta.contribVelocity ?? 0);
525
+ if (va !== vb)
526
+ return vb - va;
527
+ return (b.meta.reasons ?? []).length - (a.meta.reasons ?? []).length;
528
+ });
529
+ }
530
+ /**
531
+ * Builds the scan result summary with per-signal-type counts.
532
+ *
533
+ * Exposed in the scan result's `summary` field for observability dashboards
534
+ * and health checks — operators can see at a glance which signal categories
535
+ * fired in a given scan without parsing individual signals.
536
+ */
537
+ function buildSummary(signals, rankedCount, scans) {
538
+ return {
539
+ rankedCount,
540
+ scans,
541
+ signalBreakdown: {
542
+ firstJumps: signals.filter((s) => s.signalType === "FIRST_JUMP").length,
543
+ contribExplosions: signals.filter((s) => s.signalType === "CONTRIB_EXPLOSION").length,
544
+ immediateMovers: signals.filter((s) => s.signalType === "IMMEDIATE_MOVER").length,
545
+ newEntryDeep: signals.filter((s) => s.signalType === "NEW_ENTRY_DEEP").length,
546
+ deepClimbers: signals.filter((s) => s.signalType === "DEEP_CLIMBER").length,
547
+ },
548
+ };
549
+ }
550
+ // ─── Utility ────────────────────────────────────────────────────────────────
551
+ /**
552
+ * Fills in default values for optional config fields.
553
+ *
554
+ * v4 added many config knobs that are optional in the TypeBox schema (so
555
+ * existing YAML configs don't break). This function merges them with
556
+ * defaults so detection rules always see a fully populated config.
557
+ */
558
+ function resolveConfig(config) {
559
+ return {
560
+ ...defaultConfig,
561
+ ...Object.fromEntries(Object.entries(config).filter(([, v]) => v !== undefined)),
562
+ };
563
+ }
564
+ /**
565
+ * Produces a unique key for a market, incorporating the DEX when present.
566
+ *
567
+ * The same token symbol can appear on multiple DEXes (e.g. "SOL" on main
568
+ * vs. "SOL" on xyz). Keying history by `dex:token` prevents cross-DEX
569
+ * rank comparisons that would produce spurious signals.
570
+ */
571
+ function tokenKey(market) {
572
+ return market.dex ? `${market.dex}:${market.token}` : market.token;
573
+ }
574
+ /**
575
+ * Filters out malformed market rows that would cause NaN in scoring.
576
+ *
577
+ * The SM data provider may return rows with missing or non-numeric fields
578
+ * (e.g. newly listed tokens with incomplete data). These are silently
579
+ * dropped to avoid polluting rank calculations.
580
+ */
581
+ function normalizeMarkets(markets) {
582
+ return markets.filter((m) => typeof m.token === "string" && m.token.length > 0 &&
583
+ Number.isFinite(m.pctOfTopTradersGain) && Number.isFinite(m.traderCount));
584
+ }
585
+ /** Returns the last element of an array, or `null` if empty. */
586
+ function lastOf(arr) {
587
+ return arr.length > 0 ? arr[arr.length - 1] : null;
588
+ }
589
+ // ─── Scanner factory ────────────────────────────────────────────────────────
590
+ /**
591
+ * Creates the emerging-movers scanner instance (v4).
592
+ *
593
+ * This is the entry point registered in the scanner registry. It produces a
594
+ * stateful scanner that:
595
+ * 1. Ranks markets by `pctOfTopTradersGain` and tracks the top N.
596
+ * 2. Maintains per-token rank and contribution history across scans.
597
+ * 3. Runs 7 detection rules per market to identify emerging movers.
598
+ * 4. Classifies alerts into 5 signal types with conviction scoring.
599
+ * 5. Applies erratic-history and velocity-gate filters to reduce noise.
600
+ */
601
+ export function emergingMoversScanner() {
602
+ return createScanner({
603
+ id: "emerging-movers",
604
+ signalType: "EMERGING_MOVER",
605
+ description: "Detects top-trader rank movers with persistent local history (v4: multi-signal).",
606
+ intervalSeconds: 180,
607
+ retention: RetentionPolicy.ROLLING_WINDOW,
608
+ retentionMaxRuns: 300,
609
+ persistence: PersistencePolicy.ACTIONABLE_ONLY,
610
+ inputs: [
611
+ providerInput.smData(),
612
+ providerInput.instruments(),
613
+ consumeSharedArtifact(marketRegimeArtifact, {
614
+ required: false,
615
+ maxAgeMs: 7_200_000,
616
+ }),
617
+ ],
618
+ configSchema: emergingMoversConfigSchema,
619
+ defaultConfig,
620
+ stateSchema: emergingMoversStateSchema,
621
+ defaultState,
622
+ hooks: {
623
+ isActionable: (signal, ctx) => {
624
+ const cfg = resolveConfig(ctx.config);
625
+ // 2A: Regime-aware score gating (disabled by default)
626
+ if (cfg.regimeScoreGating) {
627
+ const regime = ctx.inputs.artifacts?.["market-regime"];
628
+ const threshold = regime?.regime === MarketRegime.NEUTRAL
629
+ ? cfg.regimeScoreNeutral
630
+ : cfg.regimeScoreDefault;
631
+ if (signal.score < threshold)
632
+ return false;
633
+ }
634
+ // 2D: Leverage floor (disabled when minLeverage = 0)
635
+ if (cfg.minLeverage > 0 && ctx.inputs.instruments) {
636
+ const instrument = ctx.inputs.instruments.find((i) => i.name === signal.asset);
637
+ if (instrument && instrument.maxLeverage < cfg.minLeverage) {
638
+ return false;
639
+ }
640
+ }
641
+ return true;
642
+ },
643
+ },
644
+ scan: async (ctx) => {
645
+ const now = ctx.now();
646
+ const state = ctx.state;
647
+ const cfg = resolveConfig(ctx.config);
648
+ const ranked = normalizeMarkets(ctx.inputs.smData ?? [])
649
+ .sort((a, b) => b.pctOfTopTradersGain - a.pctOfTopTradersGain)
650
+ .slice(0, cfg.topN);
651
+ const prevTopTokenSet = new Set(state.prevTopTokens);
652
+ const nextState = {
653
+ scans: state.scans + 1,
654
+ rankHistory: { ...state.rankHistory },
655
+ contribHistory: { ...state.contribHistory },
656
+ prevTopTokens: ranked.map(tokenKey),
657
+ };
658
+ // Analyze each ranked market and collect alerts
659
+ const alerts = [];
660
+ for (let i = 0; i < ranked.length; i++) {
661
+ const market = ranked[i];
662
+ const tk = tokenKey(market);
663
+ const prevRankHistory = state.rankHistory[tk] ?? [];
664
+ const prevContribHistory = state.contribHistory[tk] ?? [];
665
+ const rank = i + 1;
666
+ // Update state histories
667
+ nextState.rankHistory[tk] = [...prevRankHistory, rank].slice(-cfg.historyLimit);
668
+ nextState.contribHistory[tk] = [...prevContribHistory, market.pctOfTopTradersGain].slice(-cfg.historyLimit);
669
+ if (nextState.scans < cfg.minScansBeforeSignals)
670
+ continue;
671
+ const mctx = {
672
+ market,
673
+ tk,
674
+ rank,
675
+ currentContrib: market.pctOfTopTradersGain,
676
+ prevRank: lastOf(prevRankHistory),
677
+ prevContrib: lastOf(prevContribHistory),
678
+ prevRankHistory,
679
+ prevContribHistory,
680
+ };
681
+ const alert = analyzeMarket(mctx, cfg, prevTopTokenSet);
682
+ if (alert)
683
+ alerts.push(alert);
684
+ }
685
+ // Convert alerts to signals, sort, persist
686
+ const regimeArtifact = ctx.inputs.artifacts?.["market-regime"];
687
+ const marketRegime = regimeArtifact?.regime;
688
+ const signals = alerts
689
+ .map((a) => alertToSignal(a, cfg, { address: ctx.address, scannerId: ctx.scannerId, timestamp: now, marketRegime }))
690
+ .filter((s) => s !== null);
691
+ sortByPriority(signals);
692
+ await ctx.setState(nextState);
693
+ return {
694
+ timestamp: now,
695
+ scannedCount: ranked.length,
696
+ signals,
697
+ summary: buildSummary(signals, ranked.length, nextState.scans),
698
+ };
699
+ },
700
+ });
701
+ }
702
+ //# sourceMappingURL=emerging-movers.js.map