opencandle 0.2.0 → 0.4.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 (401) hide show
  1. package/README.md +110 -87
  2. package/assets/logo.svg +187 -0
  3. package/dist/analysts/orchestrator.js +1 -2
  4. package/dist/analysts/orchestrator.js.map +1 -1
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +38 -2
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts +34 -5
  9. package/dist/config.js +29 -8
  10. package/dist/config.js.map +1 -1
  11. package/dist/infra/browser.d.ts +10 -0
  12. package/dist/infra/browser.js +1 -0
  13. package/dist/infra/browser.js.map +1 -1
  14. package/dist/infra/cache.d.ts +4 -0
  15. package/dist/infra/cache.js +4 -0
  16. package/dist/infra/cache.js.map +1 -1
  17. package/dist/infra/native-dependencies.d.ts +1 -0
  18. package/dist/infra/native-dependencies.js +10 -0
  19. package/dist/infra/native-dependencies.js.map +1 -0
  20. package/dist/infra/node-version.d.ts +2 -0
  21. package/dist/infra/node-version.js +23 -0
  22. package/dist/infra/node-version.js.map +1 -0
  23. package/dist/infra/rate-limiter.js +6 -0
  24. package/dist/infra/rate-limiter.js.map +1 -1
  25. package/dist/memory/index.d.ts +2 -0
  26. package/dist/memory/index.js +1 -0
  27. package/dist/memory/index.js.map +1 -1
  28. package/dist/memory/sqlite.js +42 -4
  29. package/dist/memory/sqlite.js.map +1 -1
  30. package/dist/memory/storage.d.ts +6 -0
  31. package/dist/memory/storage.js +3 -3
  32. package/dist/memory/storage.js.map +1 -1
  33. package/dist/memory/tool-defaults.d.ts +8 -0
  34. package/dist/memory/tool-defaults.js +59 -0
  35. package/dist/memory/tool-defaults.js.map +1 -0
  36. package/dist/onboarding/connect.d.ts +35 -0
  37. package/dist/onboarding/connect.js +118 -0
  38. package/dist/onboarding/connect.js.map +1 -0
  39. package/dist/onboarding/credential-interceptor.d.ts +44 -0
  40. package/dist/onboarding/credential-interceptor.js +72 -0
  41. package/dist/onboarding/credential-interceptor.js.map +1 -0
  42. package/dist/onboarding/degradation-accumulator.d.ts +21 -0
  43. package/dist/onboarding/degradation-accumulator.js +55 -0
  44. package/dist/onboarding/degradation-accumulator.js.map +1 -0
  45. package/dist/onboarding/prompt-user.d.ts +23 -0
  46. package/dist/onboarding/prompt-user.js +61 -0
  47. package/dist/onboarding/prompt-user.js.map +1 -0
  48. package/dist/onboarding/providers.d.ts +116 -0
  49. package/dist/onboarding/providers.js +239 -0
  50. package/dist/onboarding/providers.js.map +1 -0
  51. package/dist/onboarding/state.d.ts +31 -2
  52. package/dist/onboarding/state.js +141 -13
  53. package/dist/onboarding/state.js.map +1 -1
  54. package/dist/onboarding/tool-helpers.d.ts +34 -0
  55. package/dist/onboarding/tool-helpers.js +80 -0
  56. package/dist/onboarding/tool-helpers.js.map +1 -0
  57. package/dist/onboarding/tool-tags.d.ts +37 -0
  58. package/dist/onboarding/tool-tags.js +149 -0
  59. package/dist/onboarding/tool-tags.js.map +1 -0
  60. package/dist/onboarding/validation.d.ts +19 -0
  61. package/dist/onboarding/validation.js +117 -0
  62. package/dist/onboarding/validation.js.map +1 -0
  63. package/dist/pi/opencandle-extension.d.ts +7 -1
  64. package/dist/pi/opencandle-extension.js +488 -13
  65. package/dist/pi/opencandle-extension.js.map +1 -1
  66. package/dist/pi/session-storage.d.ts +2 -0
  67. package/dist/pi/session-storage.js +5 -0
  68. package/dist/pi/session-storage.js.map +1 -0
  69. package/dist/pi/session.d.ts +4 -1
  70. package/dist/pi/session.js +25 -3
  71. package/dist/pi/session.js.map +1 -1
  72. package/dist/pi/setup.d.ts +1 -2
  73. package/dist/pi/setup.js +67 -120
  74. package/dist/pi/setup.js.map +1 -1
  75. package/dist/pi/tool-adapter.d.ts +2 -2
  76. package/dist/pi/tool-adapter.js +14 -1
  77. package/dist/pi/tool-adapter.js.map +1 -1
  78. package/dist/prompts/context-builder.d.ts +22 -0
  79. package/dist/prompts/context-builder.js +47 -11
  80. package/dist/prompts/context-builder.js.map +1 -1
  81. package/dist/prompts/disclaimer.d.ts +6 -0
  82. package/dist/prompts/disclaimer.js +9 -0
  83. package/dist/prompts/disclaimer.js.map +1 -0
  84. package/dist/prompts/workflow-prompts.d.ts +8 -0
  85. package/dist/prompts/workflow-prompts.js +39 -5
  86. package/dist/prompts/workflow-prompts.js.map +1 -1
  87. package/dist/providers/alpha-vantage.js +20 -1
  88. package/dist/providers/alpha-vantage.js.map +1 -1
  89. package/dist/providers/exa-search.d.ts +39 -0
  90. package/dist/providers/exa-search.js +276 -0
  91. package/dist/providers/exa-search.js.map +1 -0
  92. package/dist/providers/finnhub.d.ts +17 -0
  93. package/dist/providers/finnhub.js +94 -0
  94. package/dist/providers/finnhub.js.map +1 -0
  95. package/dist/providers/fred.js +13 -1
  96. package/dist/providers/fred.js.map +1 -1
  97. package/dist/providers/index.d.ts +2 -0
  98. package/dist/providers/index.js +1 -0
  99. package/dist/providers/index.js.map +1 -1
  100. package/dist/providers/provider-credential-error.d.ts +8 -0
  101. package/dist/providers/provider-credential-error.js +22 -0
  102. package/dist/providers/provider-credential-error.js.map +1 -0
  103. package/dist/providers/reddit.d.ts +8 -0
  104. package/dist/providers/reddit.js +36 -9
  105. package/dist/providers/reddit.js.map +1 -1
  106. package/dist/providers/twitter.js +2 -8
  107. package/dist/providers/twitter.js.map +1 -1
  108. package/dist/providers/web-search.d.ts +17 -0
  109. package/dist/providers/web-search.js +224 -0
  110. package/dist/providers/web-search.js.map +1 -0
  111. package/dist/providers/wrap-provider.d.ts +7 -0
  112. package/dist/providers/wrap-provider.js +15 -0
  113. package/dist/providers/wrap-provider.js.map +1 -1
  114. package/dist/providers/yahoo-finance.js +70 -33
  115. package/dist/providers/yahoo-finance.js.map +1 -1
  116. package/dist/routing/classify-intent.js +22 -0
  117. package/dist/routing/classify-intent.js.map +1 -1
  118. package/dist/routing/defaults.js +1 -1
  119. package/dist/routing/defaults.js.map +1 -1
  120. package/dist/routing/index.d.ts +4 -0
  121. package/dist/routing/index.js +3 -0
  122. package/dist/routing/index.js.map +1 -1
  123. package/dist/routing/router-llm-client.d.ts +11 -0
  124. package/dist/routing/router-llm-client.js +42 -0
  125. package/dist/routing/router-llm-client.js.map +1 -0
  126. package/dist/routing/router-prompt.d.ts +2 -0
  127. package/dist/routing/router-prompt.js +138 -0
  128. package/dist/routing/router-prompt.js.map +1 -0
  129. package/dist/routing/router-types.d.ts +62 -0
  130. package/dist/routing/router-types.js +2 -0
  131. package/dist/routing/router-types.js.map +1 -0
  132. package/dist/routing/router.d.ts +10 -0
  133. package/dist/routing/router.js +194 -0
  134. package/dist/routing/router.js.map +1 -0
  135. package/dist/runtime/session-coordinator.d.ts +63 -4
  136. package/dist/runtime/session-coordinator.js +155 -4
  137. package/dist/runtime/session-coordinator.js.map +1 -1
  138. package/dist/runtime/tool-defaults-wrapper.d.ts +3 -0
  139. package/dist/runtime/tool-defaults-wrapper.js +25 -0
  140. package/dist/runtime/tool-defaults-wrapper.js.map +1 -0
  141. package/dist/sentiment/adapters/finnhub.d.ts +7 -0
  142. package/dist/sentiment/adapters/finnhub.js +39 -0
  143. package/dist/sentiment/adapters/finnhub.js.map +1 -0
  144. package/dist/sentiment/adapters/reddit.d.ts +11 -0
  145. package/dist/sentiment/adapters/reddit.js +54 -0
  146. package/dist/sentiment/adapters/reddit.js.map +1 -0
  147. package/dist/sentiment/adapters/twitter.d.ts +9 -0
  148. package/dist/sentiment/adapters/twitter.js +32 -0
  149. package/dist/sentiment/adapters/twitter.js.map +1 -0
  150. package/dist/sentiment/adapters/web.d.ts +9 -0
  151. package/dist/sentiment/adapters/web.js +40 -0
  152. package/dist/sentiment/adapters/web.js.map +1 -0
  153. package/dist/sentiment/index.d.ts +16 -0
  154. package/dist/sentiment/index.js +44 -0
  155. package/dist/sentiment/index.js.map +1 -0
  156. package/dist/sentiment/keywords.d.ts +2 -0
  157. package/dist/sentiment/keywords.js +9 -0
  158. package/dist/sentiment/keywords.js.map +1 -0
  159. package/dist/sentiment/pipeline.d.ts +9 -0
  160. package/dist/sentiment/pipeline.js +57 -0
  161. package/dist/sentiment/pipeline.js.map +1 -0
  162. package/dist/sentiment/scorer.d.ts +9 -0
  163. package/dist/sentiment/scorer.js +64 -0
  164. package/dist/sentiment/scorer.js.map +1 -0
  165. package/dist/sentiment/store.d.ts +24 -0
  166. package/dist/sentiment/store.js +182 -0
  167. package/dist/sentiment/store.js.map +1 -0
  168. package/dist/sentiment/trends.d.ts +13 -0
  169. package/dist/sentiment/trends.js +73 -0
  170. package/dist/sentiment/trends.js.map +1 -0
  171. package/dist/sentiment/types.d.ts +66 -0
  172. package/dist/sentiment/types.js +54 -0
  173. package/dist/sentiment/types.js.map +1 -0
  174. package/dist/system-prompt.js +29 -13
  175. package/dist/system-prompt.js.map +1 -1
  176. package/dist/tool-kit.d.ts +4 -4
  177. package/dist/tools/fundamentals/company-overview.d.ts +4 -2
  178. package/dist/tools/fundamentals/company-overview.js +27 -27
  179. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  180. package/dist/tools/fundamentals/comps.d.ts +1 -1
  181. package/dist/tools/fundamentals/comps.js +45 -45
  182. package/dist/tools/fundamentals/comps.js.map +1 -1
  183. package/dist/tools/fundamentals/dcf.d.ts +1 -1
  184. package/dist/tools/fundamentals/dcf.js +82 -82
  185. package/dist/tools/fundamentals/dcf.js.map +1 -1
  186. package/dist/tools/fundamentals/earnings.d.ts +4 -2
  187. package/dist/tools/fundamentals/earnings.js +25 -25
  188. package/dist/tools/fundamentals/earnings.js.map +1 -1
  189. package/dist/tools/fundamentals/financials.d.ts +4 -2
  190. package/dist/tools/fundamentals/financials.js +23 -23
  191. package/dist/tools/fundamentals/financials.js.map +1 -1
  192. package/dist/tools/fundamentals/sec-filings.d.ts +1 -1
  193. package/dist/tools/index.d.ts +28 -1
  194. package/dist/tools/index.js +35 -2
  195. package/dist/tools/index.js.map +1 -1
  196. package/dist/tools/interaction/ask-user.d.ts +1 -1
  197. package/dist/tools/interaction/ask-user.js +28 -64
  198. package/dist/tools/interaction/ask-user.js.map +1 -1
  199. package/dist/tools/interaction/twitter-login.d.ts +1 -1
  200. package/dist/tools/macro/fear-greed.d.ts +1 -1
  201. package/dist/tools/macro/fred-data.d.ts +4 -2
  202. package/dist/tools/macro/fred-data.js +26 -26
  203. package/dist/tools/macro/fred-data.js.map +1 -1
  204. package/dist/tools/market/crypto-history.d.ts +1 -1
  205. package/dist/tools/market/crypto-price.d.ts +1 -1
  206. package/dist/tools/market/search-ticker.d.ts +1 -1
  207. package/dist/tools/market/stock-history.d.ts +1 -1
  208. package/dist/tools/market/stock-quote.d.ts +1 -1
  209. package/dist/tools/options/option-chain.d.ts +1 -1
  210. package/dist/tools/options/option-chain.js +4 -1
  211. package/dist/tools/options/option-chain.js.map +1 -1
  212. package/dist/tools/portfolio/correlation.d.ts +1 -1
  213. package/dist/tools/portfolio/predictions.d.ts +1 -1
  214. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  215. package/dist/tools/portfolio/tracker.d.ts +1 -1
  216. package/dist/tools/portfolio/watchlist.d.ts +1 -1
  217. package/dist/tools/sentiment/reddit-sentiment.d.ts +4 -2
  218. package/dist/tools/sentiment/reddit-sentiment.js +107 -22
  219. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  220. package/dist/tools/sentiment/sentiment-summary.d.ts +7 -0
  221. package/dist/tools/sentiment/sentiment-summary.js +230 -0
  222. package/dist/tools/sentiment/sentiment-summary.js.map +1 -0
  223. package/dist/tools/sentiment/sentiment-trend.d.ts +22 -0
  224. package/dist/tools/sentiment/sentiment-trend.js +39 -0
  225. package/dist/tools/sentiment/sentiment-trend.js.map +1 -0
  226. package/dist/tools/sentiment/twitter-sentiment.d.ts +1 -1
  227. package/dist/tools/sentiment/twitter-sentiment.js +17 -0
  228. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  229. package/dist/tools/sentiment/web-search.d.ts +11 -0
  230. package/dist/tools/sentiment/web-search.js +115 -0
  231. package/dist/tools/sentiment/web-search.js.map +1 -0
  232. package/dist/tools/sentiment/web-sentiment.d.ts +8 -0
  233. package/dist/tools/sentiment/web-sentiment.js +66 -0
  234. package/dist/tools/sentiment/web-sentiment.js.map +1 -0
  235. package/dist/tools/technical/backtest.d.ts +1 -1
  236. package/dist/tools/technical/indicators.d.ts +1 -1
  237. package/dist/tools/technical/indicators.js +7 -1
  238. package/dist/tools/technical/indicators.js.map +1 -1
  239. package/dist/types/index.d.ts +1 -1
  240. package/dist/types/sentiment.d.ts +21 -0
  241. package/dist/workflows/options-screener.js +7 -2
  242. package/dist/workflows/options-screener.js.map +1 -1
  243. package/dist/workflows/portfolio-builder.js +3 -3
  244. package/dist/workflows/portfolio-builder.js.map +1 -1
  245. package/gui/server/background-quotes.ts +31 -0
  246. package/gui/server/chat-event-adapter.ts +142 -0
  247. package/gui/server/invoke-tool.ts +89 -0
  248. package/gui/server/live-chat-event-adapter.ts +181 -0
  249. package/gui/server/model-setup.ts +100 -0
  250. package/gui/server/package.json +5 -0
  251. package/gui/server/projector.ts +212 -0
  252. package/gui/server/server.ts +592 -0
  253. package/gui/server/session-actions.ts +31 -0
  254. package/gui/server/tool-metadata.ts +88 -0
  255. package/gui/server/websocket.ts +128 -0
  256. package/gui/server/writer-lock.ts +118 -0
  257. package/gui/shared/chat-events.ts +118 -0
  258. package/gui/shared/event-reducer.ts +186 -0
  259. package/gui/web/dist/assets/CatalogOverlay-D1ImSJTe.js +1 -0
  260. package/gui/web/dist/assets/index-DBrWq43L.css +1 -0
  261. package/gui/web/dist/assets/index-RflHaj0y.js +67 -0
  262. package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
  263. package/gui/web/dist/index.html +17 -0
  264. package/package.json +62 -20
  265. package/src/analysts/contracts.ts +189 -0
  266. package/src/analysts/orchestrator.ts +300 -0
  267. package/src/cli.ts +205 -0
  268. package/src/config.ts +161 -0
  269. package/src/index.ts +5 -0
  270. package/src/infra/browser.ts +111 -0
  271. package/src/infra/cache.ts +103 -0
  272. package/src/infra/http-client.ts +68 -0
  273. package/src/infra/index.ts +18 -0
  274. package/src/infra/native-dependencies.ts +12 -0
  275. package/src/infra/node-version.ts +24 -0
  276. package/src/infra/open-url.ts +28 -0
  277. package/src/infra/opencandle-paths.ts +64 -0
  278. package/src/infra/rate-limiter.ts +64 -0
  279. package/src/memory/index.ts +10 -0
  280. package/src/memory/manager.ts +159 -0
  281. package/src/memory/preference-extractor.ts +106 -0
  282. package/src/memory/retrieval.ts +70 -0
  283. package/src/memory/sqlite.ts +172 -0
  284. package/src/memory/storage.ts +204 -0
  285. package/src/memory/tool-defaults.ts +87 -0
  286. package/src/memory/types.ts +67 -0
  287. package/src/onboarding/connect.ts +184 -0
  288. package/src/onboarding/credential-interceptor.ts +134 -0
  289. package/src/onboarding/degradation-accumulator.ts +79 -0
  290. package/src/onboarding/prompt-user.ts +85 -0
  291. package/src/onboarding/providers.ts +315 -0
  292. package/src/onboarding/state.ts +218 -0
  293. package/src/onboarding/tool-helpers.ts +111 -0
  294. package/src/onboarding/tool-tags.ts +201 -0
  295. package/src/onboarding/validation.ts +158 -0
  296. package/src/pi/opencandle-extension.ts +724 -0
  297. package/src/pi/session-storage.ts +5 -0
  298. package/src/pi/session.ts +81 -0
  299. package/src/pi/setup.ts +371 -0
  300. package/src/pi/tool-adapter.ts +36 -0
  301. package/src/prompts/context-builder.ts +204 -0
  302. package/src/prompts/disclaimer.ts +9 -0
  303. package/src/prompts/sections.ts +46 -0
  304. package/src/prompts/workflow-prompts.ts +279 -0
  305. package/src/providers/alpha-vantage.ts +292 -0
  306. package/src/providers/coingecko.ts +96 -0
  307. package/src/providers/exa-search.ts +373 -0
  308. package/src/providers/fear-greed.ts +45 -0
  309. package/src/providers/finnhub.ts +124 -0
  310. package/src/providers/fred.ts +83 -0
  311. package/src/providers/index.ts +9 -0
  312. package/src/providers/provider-credential-error.ts +23 -0
  313. package/src/providers/reddit.ts +151 -0
  314. package/src/providers/sec-edgar.ts +96 -0
  315. package/src/providers/twitter.ts +173 -0
  316. package/src/providers/web-search.ts +293 -0
  317. package/src/providers/with-fallback.ts +41 -0
  318. package/src/providers/wrap-provider.ts +64 -0
  319. package/src/providers/yahoo-finance.ts +367 -0
  320. package/src/routing/classify-intent.ts +194 -0
  321. package/src/routing/defaults.ts +29 -0
  322. package/src/routing/entity-extractor.ts +140 -0
  323. package/src/routing/index.ts +26 -0
  324. package/src/routing/router-llm-client.ts +51 -0
  325. package/src/routing/router-prompt.ts +159 -0
  326. package/src/routing/router-types.ts +66 -0
  327. package/src/routing/router.ts +213 -0
  328. package/src/routing/slot-resolver.ts +152 -0
  329. package/src/routing/types.ts +63 -0
  330. package/src/runtime/evidence.ts +77 -0
  331. package/src/runtime/index.ts +55 -0
  332. package/src/runtime/prompt-step.ts +75 -0
  333. package/src/runtime/provider-ids.ts +15 -0
  334. package/src/runtime/provider-tracker.ts +40 -0
  335. package/src/runtime/run-context.ts +22 -0
  336. package/src/runtime/session-coordinator.ts +406 -0
  337. package/src/runtime/tool-defaults-wrapper.ts +35 -0
  338. package/src/runtime/validation.ts +214 -0
  339. package/src/runtime/workflow-events.ts +75 -0
  340. package/src/runtime/workflow-runner.ts +188 -0
  341. package/src/runtime/workflow-types.ts +102 -0
  342. package/src/sentiment/adapters/finnhub.ts +44 -0
  343. package/src/sentiment/adapters/reddit.ts +65 -0
  344. package/src/sentiment/adapters/twitter.ts +36 -0
  345. package/src/sentiment/adapters/web.ts +44 -0
  346. package/src/sentiment/index.ts +58 -0
  347. package/src/sentiment/keywords.ts +9 -0
  348. package/src/sentiment/pipeline.ts +68 -0
  349. package/src/sentiment/scorer.ts +78 -0
  350. package/src/sentiment/store.ts +260 -0
  351. package/src/sentiment/trends.ts +90 -0
  352. package/src/sentiment/types.ts +108 -0
  353. package/src/system-prompt.ts +115 -0
  354. package/src/tool-kit.ts +68 -0
  355. package/src/tools/AGENTS.md +36 -0
  356. package/src/tools/fundamentals/company-overview.ts +54 -0
  357. package/src/tools/fundamentals/comps.ts +156 -0
  358. package/src/tools/fundamentals/dcf.ts +267 -0
  359. package/src/tools/fundamentals/earnings.ts +47 -0
  360. package/src/tools/fundamentals/financials.ts +54 -0
  361. package/src/tools/fundamentals/sec-filings.ts +61 -0
  362. package/src/tools/index.ts +88 -0
  363. package/src/tools/interaction/ask-user.ts +81 -0
  364. package/src/tools/interaction/twitter-login.ts +93 -0
  365. package/src/tools/macro/fear-greed.ts +41 -0
  366. package/src/tools/macro/fred-data.ts +54 -0
  367. package/src/tools/market/crypto-history.ts +51 -0
  368. package/src/tools/market/crypto-price.ts +53 -0
  369. package/src/tools/market/search-ticker.ts +53 -0
  370. package/src/tools/market/stock-history.ts +79 -0
  371. package/src/tools/market/stock-quote.ts +64 -0
  372. package/src/tools/options/greeks.ts +82 -0
  373. package/src/tools/options/option-chain.ts +91 -0
  374. package/src/tools/portfolio/correlation.ts +162 -0
  375. package/src/tools/portfolio/predictions.ts +253 -0
  376. package/src/tools/portfolio/risk-analysis.ts +134 -0
  377. package/src/tools/portfolio/tracker.ts +147 -0
  378. package/src/tools/portfolio/watchlist.ts +153 -0
  379. package/src/tools/sentiment/reddit-sentiment.ts +164 -0
  380. package/src/tools/sentiment/sentiment-summary.ts +256 -0
  381. package/src/tools/sentiment/sentiment-trend.ts +58 -0
  382. package/src/tools/sentiment/twitter-sentiment.ts +96 -0
  383. package/src/tools/sentiment/web-search.ts +150 -0
  384. package/src/tools/sentiment/web-sentiment.ts +76 -0
  385. package/src/tools/technical/backtest.ts +246 -0
  386. package/src/tools/technical/indicators.ts +258 -0
  387. package/src/types/fundamentals.ts +46 -0
  388. package/src/types/index.ts +20 -0
  389. package/src/types/macro.ts +27 -0
  390. package/src/types/market.ts +43 -0
  391. package/src/types/options.ts +35 -0
  392. package/src/types/portfolio.ts +41 -0
  393. package/src/types/sentiment.ts +70 -0
  394. package/src/workflows/compare-assets.ts +39 -0
  395. package/src/workflows/index.ts +4 -0
  396. package/src/workflows/options-screener.ts +49 -0
  397. package/src/workflows/portfolio-builder.ts +52 -0
  398. package/src/workflows/types.ts +4 -0
  399. package/dist/tools/sentiment/news-sentiment.d.ts +0 -7
  400. package/dist/tools/sentiment/news-sentiment.js +0 -55
  401. package/dist/tools/sentiment/news-sentiment.js.map +0 -1
@@ -0,0 +1,367 @@
1
+ import { httpGet } from "../infra/http-client.js";
2
+ import { cache, TTL, STALE_LIMIT } from "../infra/cache.js";
3
+ import { rateLimiter } from "../infra/rate-limiter.js";
4
+ import { StealthBrowser } from "../infra/browser.js";
5
+ import type { StockQuote, OHLCV } from "../types/market.js";
6
+ import type { OptionsChain, OptionContract } from "../types/options.js";
7
+ import { computeGreeks } from "../tools/options/greeks.js";
8
+
9
+ const BASE_URL = "https://query1.finance.yahoo.com/v8/finance/chart";
10
+
11
+ interface YahooChartResponse {
12
+ chart: {
13
+ result: Array<{
14
+ meta: Record<string, any>;
15
+ timestamp: number[];
16
+ indicators: {
17
+ quote: Array<{
18
+ open: number[];
19
+ high: number[];
20
+ low: number[];
21
+ close: number[];
22
+ volume: number[];
23
+ }>;
24
+ adjclose?: Array<{ adjclose: number[] }>;
25
+ };
26
+ }>;
27
+ error?: { code: string; description: string };
28
+ };
29
+ }
30
+
31
+ export async function getQuote(symbol: string): Promise<StockQuote> {
32
+ const cacheKey = `yahoo:quote:${symbol}`;
33
+ const cached = cache.get<StockQuote>(cacheKey);
34
+ if (cached) return cached;
35
+
36
+ try {
37
+ await rateLimiter.acquire("yahoo");
38
+
39
+ const url = `${BASE_URL}/${encodeURIComponent(symbol)}?interval=1d&range=1d`;
40
+ const data = await httpGet<YahooChartResponse>(url, {
41
+ headers: { "User-Agent": "OpenCandle/1.0" },
42
+ });
43
+
44
+ if (data.chart.error) {
45
+ throw new Error(`Yahoo Finance: ${data.chart.error.description}`);
46
+ }
47
+
48
+ const result = data.chart.result[0];
49
+ const meta = result.meta;
50
+ const indicators = result.indicators.quote[0];
51
+
52
+ const price = meta.regularMarketPrice ?? 0;
53
+ const prevClose = meta.chartPreviousClose ?? meta.previousClose ?? price;
54
+ const change = price - prevClose;
55
+ const changePercent = prevClose !== 0 ? (change / prevClose) * 100 : 0;
56
+
57
+ // Open price: try meta first, fall back to indicators
58
+ const open = meta.regularMarketOpen ?? indicators?.open?.[0] ?? price;
59
+
60
+ const quote: StockQuote = {
61
+ symbol: meta.symbol,
62
+ price,
63
+ change,
64
+ changePercent,
65
+ open,
66
+ high: meta.regularMarketDayHigh ?? indicators?.high?.[0] ?? price,
67
+ low: meta.regularMarketDayLow ?? indicators?.low?.[0] ?? price,
68
+ previousClose: prevClose,
69
+ volume: meta.regularMarketVolume ?? 0,
70
+ marketCap: meta.marketCap ?? 0,
71
+ pe: null, // Not in chart endpoint
72
+ week52High: meta.fiftyTwoWeekHigh ?? 0,
73
+ week52Low: meta.fiftyTwoWeekLow ?? 0,
74
+ timestamp: Date.now(),
75
+ };
76
+
77
+ cache.set(cacheKey, quote, TTL.QUOTE);
78
+ return quote;
79
+ } catch (error) {
80
+ const stale = cache.getStale<StockQuote>(cacheKey, STALE_LIMIT.QUOTE);
81
+ if (stale) return stale.value;
82
+ throw error;
83
+ }
84
+ }
85
+
86
+ export async function getHistory(
87
+ symbol: string,
88
+ range: string = "6mo",
89
+ interval: string = "1d",
90
+ ): Promise<OHLCV[]> {
91
+ const cacheKey = `yahoo:history:${symbol}:${range}:${interval}`;
92
+ const cached = cache.get<OHLCV[]>(cacheKey);
93
+ if (cached) return cached;
94
+
95
+ try {
96
+ await rateLimiter.acquire("yahoo");
97
+
98
+ const url = `${BASE_URL}/${encodeURIComponent(symbol)}?interval=${interval}&range=${range}`;
99
+ const data = await httpGet<YahooChartResponse>(url, {
100
+ headers: { "User-Agent": "OpenCandle/1.0" },
101
+ });
102
+
103
+ if (data.chart.error) {
104
+ throw new Error(`Yahoo Finance: ${data.chart.error.description}`);
105
+ }
106
+
107
+ const result = data.chart.result[0];
108
+ const timestamps = result.timestamp;
109
+ const quotes = result.indicators.quote[0];
110
+
111
+ const ohlcv: OHLCV[] = timestamps
112
+ .map((ts, i) => ({
113
+ date: new Date(ts * 1000).toISOString().split("T")[0],
114
+ open: quotes.open[i],
115
+ high: quotes.high[i],
116
+ low: quotes.low[i],
117
+ close: quotes.close[i],
118
+ volume: quotes.volume[i],
119
+ }))
120
+ .filter((bar) => bar.open != null && bar.close != null);
121
+
122
+ cache.set(cacheKey, ohlcv, TTL.HISTORY);
123
+ return ohlcv;
124
+ } catch (error) {
125
+ const stale = cache.getStale<OHLCV[]>(cacheKey, STALE_LIMIT.HISTORY);
126
+ if (stale) return stale.value;
127
+ throw error;
128
+ }
129
+ }
130
+
131
+ // --- Options Chain (v7 API with crumb+cookie auth) ---
132
+
133
+ const BROWSER_UA =
134
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
135
+
136
+ let cachedCrumb: { crumb: string; cookie: string; expiresAt: number } | null = null;
137
+
138
+ export function clearCrumbCache(): void {
139
+ cachedCrumb = null;
140
+ }
141
+
142
+ export async function getYahooCrumb(): Promise<{ crumb: string; cookie: string }> {
143
+ if (cachedCrumb && Date.now() < cachedCrumb.expiresAt) {
144
+ return { crumb: cachedCrumb.crumb, cookie: cachedCrumb.cookie };
145
+ }
146
+
147
+ // Step 1: Hit fc.yahoo.com to get a session cookie
148
+ const cookieRes = await fetch("https://fc.yahoo.com/t", {
149
+ headers: { "User-Agent": BROWSER_UA },
150
+ });
151
+ const setCookie = cookieRes.headers.get("set-cookie") ?? "";
152
+ const cookie = setCookie.split(";")[0]; // Extract just the cookie value
153
+
154
+ // Step 2: Use the cookie to get a crumb
155
+ const crumbRes = await fetch("https://query2.finance.yahoo.com/v1/test/getcrumb", {
156
+ headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
157
+ });
158
+ const crumb = await crumbRes.text();
159
+
160
+ if (!crumb || crumb.includes("Unauthorized")) {
161
+ throw new Error("Failed to acquire Yahoo Finance crumb");
162
+ }
163
+
164
+ cachedCrumb = { crumb, cookie, expiresAt: Date.now() + TTL.CRUMB };
165
+ return { crumb, cookie };
166
+ }
167
+
168
+ interface YahooOptionsResponse {
169
+ optionChain: {
170
+ result: Array<{
171
+ underlyingSymbol: string;
172
+ expirationDates: number[];
173
+ strikes: number[];
174
+ quote: Record<string, any>;
175
+ options: Array<{
176
+ expirationDate: number;
177
+ calls: any[];
178
+ puts: any[];
179
+ }>;
180
+ }>;
181
+ error?: any;
182
+ };
183
+ }
184
+
185
+ export async function getOptionsChain(
186
+ symbol: string,
187
+ expiration?: number,
188
+ ): Promise<OptionsChain> {
189
+ const cacheKey = `yahoo:options:${symbol}:${expiration ?? "nearest"}`;
190
+ const cached = cache.get<OptionsChain>(cacheKey);
191
+ if (cached) return cached;
192
+
193
+ await rateLimiter.acquire("yahoo");
194
+
195
+ const { crumb, cookie } = await getYahooCrumb();
196
+ const dateParam = expiration ? `&date=${expiration}` : "";
197
+ const url = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(crumb)}${dateParam}`;
198
+
199
+ let res: Response | null = null;
200
+ let fetchError: unknown;
201
+ try {
202
+ res = await fetch(url, {
203
+ headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
204
+ });
205
+ } catch (error) {
206
+ fetchError = error;
207
+ }
208
+
209
+ // On 401 or 429, refresh crumb and retry once
210
+ if (res?.status === 401 || res?.status === 429) {
211
+ try {
212
+ clearCrumbCache();
213
+ const fresh = await getYahooCrumb();
214
+ const retryUrl = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(fresh.crumb)}${dateParam}`;
215
+ res = await fetch(retryUrl, {
216
+ headers: { "User-Agent": BROWSER_UA, Cookie: fresh.cookie },
217
+ });
218
+ } catch (error) {
219
+ fetchError = error;
220
+ res = null;
221
+ }
222
+ }
223
+
224
+ // If still failing, fall back to stealth browser (bypasses TLS fingerprinting)
225
+ if (!res?.ok) {
226
+ let browserError: unknown;
227
+ try {
228
+ const browserData = await fetchOptionsViaBrowser(symbol, expiration);
229
+ if (browserData) {
230
+ const chain = parseOptionsResponse(symbol, browserData);
231
+ cache.set(cacheKey, chain, TTL.OPTIONS_CHAIN);
232
+ return chain;
233
+ }
234
+ } catch (error) {
235
+ browserError = error;
236
+ }
237
+ // All fetches failed — try stale cache before giving up
238
+ const stale = cache.getStale<OptionsChain>(cacheKey, STALE_LIMIT.OPTIONS_CHAIN);
239
+ if (stale) return stale.value;
240
+ if (res) {
241
+ const message = `Yahoo Finance options: HTTP ${res.status}`;
242
+ if (browserError instanceof Error) {
243
+ throw new Error(`${message}; browser fallback failed: ${browserError.message}`);
244
+ }
245
+ throw new Error(message);
246
+ }
247
+ if (browserError instanceof Error) {
248
+ const message = fetchError instanceof Error ? fetchError.message : "Yahoo Finance options: fetch failed";
249
+ throw new Error(`${message}; browser fallback failed: ${browserError.message}`);
250
+ }
251
+ throw fetchError instanceof Error ? fetchError : new Error("Yahoo Finance options: fetch failed");
252
+ }
253
+
254
+ const data: YahooOptionsResponse = await res.json();
255
+ const chain = parseOptionsResponse(symbol, data);
256
+ cache.set(cacheKey, chain, TTL.OPTIONS_CHAIN);
257
+ return chain;
258
+ }
259
+
260
+ /**
261
+ * Compute time to expiry in years from a Yahoo expiration timestamp (midnight UTC).
262
+ * US equity options expire at 4:00 PM ET. During EDT that is 20:00 UTC.
263
+ * We use 21:00 UTC (4 PM EST / 5 PM EDT) as a conservative close offset
264
+ * and apply a floor of ~1 hour to prevent numerical instability near expiry.
265
+ */
266
+ export function computeTimeToExpiry(expirationTs: number, nowMs: number = Date.now()): number {
267
+ const MARKET_CLOSE_OFFSET_S = 21 * 3600; // 21:00 UTC ≈ 4 PM ET
268
+ const MIN_TIME_YEARS = 1 / (365 * 24); // ~1 hour floor
269
+ const SECONDS_PER_YEAR = 365 * 24 * 3600;
270
+
271
+ const expiryCloseTs = expirationTs + MARKET_CLOSE_OFFSET_S;
272
+ const remainingS = expiryCloseTs - nowMs / 1000;
273
+
274
+ if (remainingS <= 0) return 0;
275
+ return Math.max(MIN_TIME_YEARS, remainingS / SECONDS_PER_YEAR);
276
+ }
277
+
278
+ function parseOptionsResponse(symbol: string, data: YahooOptionsResponse): OptionsChain {
279
+ if (data.optionChain.error) {
280
+ throw new Error(`Yahoo Finance options: ${JSON.stringify(data.optionChain.error)}`);
281
+ }
282
+
283
+ const result = data.optionChain.result[0];
284
+ const quote = result.quote;
285
+ const underlyingPrice = quote.regularMarketPrice ?? 0;
286
+ const opts = result.options[0];
287
+ const riskFreeRate = 0.05;
288
+
289
+ const expirationTs = opts.expirationDate;
290
+ const expirationDate = new Date(expirationTs * 1000).toISOString().split("T")[0];
291
+ const timeYears = computeTimeToExpiry(expirationTs);
292
+
293
+ const mapContract = (c: any, type: "call" | "put"): OptionContract => {
294
+ const strike = c.strike ?? c.strike?.raw ?? 0;
295
+ const iv = c.impliedVolatility ?? c.impliedVolatility?.raw ?? 0;
296
+ const greeks = computeGreeks({ type, spot: underlyingPrice, strike, timeYears, iv, riskFreeRate });
297
+ return {
298
+ contractSymbol: c.contractSymbol ?? "",
299
+ type,
300
+ strike,
301
+ expiration: expirationDate,
302
+ bid: c.bid ?? c.bid?.raw ?? 0,
303
+ ask: c.ask ?? c.ask?.raw ?? 0,
304
+ lastPrice: c.lastPrice ?? c.lastPrice?.raw ?? 0,
305
+ volume: c.volume ?? c.volume?.raw ?? 0,
306
+ openInterest: c.openInterest ?? c.openInterest?.raw ?? 0,
307
+ impliedVolatility: iv,
308
+ inTheMoney: c.inTheMoney ?? false,
309
+ greeks,
310
+ };
311
+ };
312
+
313
+ const calls = (opts.calls ?? []).map((c: any) => mapContract(c, "call"));
314
+ const puts = (opts.puts ?? []).map((c: any) => mapContract(c, "put"));
315
+ const totalCallVolume = calls.reduce((s, c) => s + c.volume, 0);
316
+ const totalPutVolume = puts.reduce((s, c) => s + c.volume, 0);
317
+
318
+ return {
319
+ symbol: result.underlyingSymbol,
320
+ underlyingPrice,
321
+ expirationDate,
322
+ expirationDates: result.expirationDates.map((ts) => new Date(ts * 1000).toISOString().split("T")[0]),
323
+ calls,
324
+ puts,
325
+ totalCallVolume,
326
+ totalPutVolume,
327
+ putCallRatio: totalCallVolume > 0 ? totalPutVolume / totalCallVolume : 0,
328
+ fetchedAt: new Date().toISOString(),
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Fallback: fetch options data via Camoufox stealth browser.
334
+ * Bypasses Yahoo's TLS fingerprinting and rate limiting.
335
+ */
336
+ async function fetchOptionsViaBrowser(
337
+ symbol: string,
338
+ expiration?: number,
339
+ ): Promise<YahooOptionsResponse | null> {
340
+ try {
341
+ // Avoid loading the script-heavy Yahoo Finance homepage: Playwright 1.60
342
+ // can crash on some pageerror payloads emitted by finance.yahoo.com.
343
+ // Navigating directly to Yahoo's JSON endpoints still uses the browser's
344
+ // cookies/TLS fingerprint without requiring cross-origin fetch from page JS.
345
+ const dateParam = expiration ? `&date=${expiration}` : "";
346
+ return await StealthBrowser.run(async (page) => {
347
+ await page.goto("https://query2.finance.yahoo.com/v1/test/getcrumb", {
348
+ waitUntil: "domcontentloaded",
349
+ timeout: 15000,
350
+ });
351
+ const crumb = (await page.locator("body").innerText()).trim();
352
+ if (!crumb) return null;
353
+
354
+ const url = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(crumb)}${dateParam}`;
355
+ const response = await page.goto(url, {
356
+ waitUntil: "domcontentloaded",
357
+ timeout: 15000,
358
+ });
359
+ if (!response?.ok()) return null;
360
+
361
+ const text = (await page.locator("body").innerText()).trim();
362
+ return JSON.parse(text) as YahooOptionsResponse;
363
+ });
364
+ } catch (error) {
365
+ throw error instanceof Error ? error : new Error(String(error));
366
+ }
367
+ }
@@ -0,0 +1,194 @@
1
+ import type { ClassificationResult, WorkflowType, ExtractedEntities } from "./types.js";
2
+ import { extractEntities } from "./entity-extractor.js";
3
+
4
+ interface Rule {
5
+ workflow: WorkflowType;
6
+ test: (input: string, entities: ExtractedEntities) => boolean;
7
+ confidence: number;
8
+ }
9
+
10
+ const ANALYSIS_PATTERNS = [
11
+ /^\s*analyze\s+\$?([A-Za-z]{1,5})\s*$/i,
12
+ /^\s*full\s+analysis\s+(?:of\s+)?\$?([A-Za-z]{1,5})\s*$/i,
13
+ /^\s*deep\s+dive\s+(?:on\s+)?\$?([A-Za-z]{1,5})\s*$/i,
14
+ ];
15
+
16
+ const RULES: Rule[] = [
17
+ // Exact-match analysis patterns (highest priority)
18
+ {
19
+ workflow: "single_asset_analysis",
20
+ confidence: 1.0,
21
+ test: (input) => ANALYSIS_PATTERNS.some((p) => p.test(input)),
22
+ },
23
+ {
24
+ workflow: "single_asset_analysis",
25
+ confidence: 0.85,
26
+ test: (input, entities) => {
27
+ const lower = input.toLowerCase();
28
+ return (
29
+ entities.symbols.length === 1 &&
30
+ (/\bis\s+\S+\s+(?:attractive|undervalued|overvalued|cheap|expensive)/i.test(lower) ||
31
+ /\bshould\s+i\s+buy\s+\$?[a-z]{1,5}\b/i.test(lower) ||
32
+ /\bwhat\s+do\s+you\s+think\s+(?:of|about)\s+\$?[a-z]{1,5}\b/i.test(lower))
33
+ );
34
+ },
35
+ },
36
+ // News / event queries — symbol or topic + news keywords
37
+ // Must come before compare_assets to prevent "NVDA and AI chip exports" from
38
+ // matching as a 2-symbol comparison.
39
+ {
40
+ workflow: "general_finance_qa",
41
+ confidence: 0.9,
42
+ test: (input) => {
43
+ const lower = input.toLowerCase();
44
+ const hasNewsKeyword =
45
+ /\bnews\b/.test(lower) ||
46
+ /\blatest\b/.test(lower) ||
47
+ /\brecent\b/.test(lower) ||
48
+ /\bhappening\b/.test(lower) ||
49
+ /\bannouncement/.test(lower) ||
50
+ /\binvestigation\b/.test(lower) ||
51
+ /\bregulat/.test(lower) ||
52
+ /\blawsuit\b/.test(lower) ||
53
+ /\btariff/.test(lower) ||
54
+ /\bexport/.test(lower) ||
55
+ /\bupdate\b/.test(lower);
56
+ return hasNewsKeyword;
57
+ },
58
+ },
59
+ // Options: symbol + option keyword
60
+ {
61
+ workflow: "options_screener",
62
+ confidence: 0.95,
63
+ test: (input, entities) => {
64
+ const lower = input.toLowerCase();
65
+ const hasOptionKeywords =
66
+ /\bcalls?\b/.test(lower) ||
67
+ /\bputs?\b/.test(lower) ||
68
+ /\boption(?:s)?\s*chain\b/.test(lower) ||
69
+ /\boptions?\b/.test(lower);
70
+ return hasOptionKeywords && entities.symbols.length >= 1;
71
+ },
72
+ },
73
+ // Compare: keyword + 2+ symbols (uppercase)
74
+ {
75
+ workflow: "compare_assets",
76
+ confidence: 0.95,
77
+ test: (input, entities) => {
78
+ const lower = input.toLowerCase();
79
+ const hasCompareKeywords =
80
+ /\bcompare\b/.test(lower) ||
81
+ /\bvs\.?\b/.test(lower) ||
82
+ /\bversus\b/.test(lower) ||
83
+ /\bwhich\s+is\s+better\b/.test(lower);
84
+ return hasCompareKeywords && entities.symbols.length >= 2;
85
+ },
86
+ },
87
+ // Compare: keyword + lowercase tickers ("Compare aapl and msft")
88
+ {
89
+ workflow: "compare_assets",
90
+ confidence: 0.85,
91
+ test: (input) => {
92
+ const lower = input.toLowerCase();
93
+ return /\bcompare\s+[a-z]{1,5}(?:\s*,?\s*(?:and\s+)?[a-z]{1,5})+/.test(lower);
94
+ },
95
+ },
96
+ // Compare: 2+ uppercase symbols without explicit keyword
97
+ {
98
+ workflow: "compare_assets",
99
+ confidence: 0.8,
100
+ test: (_input, entities) => entities.symbols.length >= 2,
101
+ },
102
+ // Watchlist/tracking: must come before portfolio_builder to catch "show my portfolio"
103
+ {
104
+ workflow: "watchlist_or_tracking",
105
+ confidence: 0.95,
106
+ test: (input) => {
107
+ const lower = input.toLowerCase();
108
+ return (
109
+ /\bwatchlist\b/.test(lower) ||
110
+ /\bprediction/i.test(lower) ||
111
+ /\bshow\s+my\s+portfolio\b/.test(lower) ||
112
+ /\bmy\s+portfolio\b/.test(lower) ||
113
+ /\btrack\b/.test(lower)
114
+ );
115
+ },
116
+ },
117
+ // Portfolio: budget + invest keyword
118
+ {
119
+ workflow: "portfolio_builder",
120
+ confidence: 0.9,
121
+ test: (input, entities) => {
122
+ const lower = input.toLowerCase();
123
+ return (
124
+ entities.budget !== undefined &&
125
+ (/\binvest\b/.test(lower) ||
126
+ /\bportfolio\b/.test(lower) ||
127
+ /\ballocat/i.test(lower) ||
128
+ /\bposition/i.test(lower) ||
129
+ /\bbuy\b/.test(lower))
130
+ );
131
+ },
132
+ },
133
+ // Portfolio: keyword-only (no budget required)
134
+ {
135
+ workflow: "portfolio_builder",
136
+ confidence: 0.8,
137
+ test: (input) => {
138
+ const lower = input.toLowerCase();
139
+ return (
140
+ /\bportfolio\b/.test(lower) ||
141
+ /\bwhat\s+should\s+i\s+(?:invest|buy)\b/.test(lower) ||
142
+ /\bbuild\s+(?:me\s+)?a?\s*(?:diversified\s+)?.*portfolio\b/.test(lower) ||
143
+ (/\binvest\s+in\b/.test(lower) && /\bwhat\b/.test(lower))
144
+ );
145
+ },
146
+ },
147
+ // General finance Q&A
148
+ {
149
+ workflow: "general_finance_qa",
150
+ confidence: 0.85,
151
+ test: (input) => {
152
+ const lower = input.toLowerCase();
153
+ return (
154
+ /^what\s+(?:is|are|does|do)\b/.test(lower) ||
155
+ /^how\s+(?:does|do|is|are)\b/.test(lower) ||
156
+ /^explain\b/.test(lower) ||
157
+ /^define\b/.test(lower) ||
158
+ /\bwhat\s+does\s+\S+\s+mean\b/.test(lower)
159
+ );
160
+ },
161
+ },
162
+ ];
163
+
164
+ export function classifyIntent(input: string): ClassificationResult {
165
+ const trimmed = input.trim();
166
+ if (!trimmed) {
167
+ return {
168
+ workflow: "unclassified",
169
+ confidence: 0,
170
+ tier: "rule",
171
+ entities: { symbols: [] },
172
+ };
173
+ }
174
+
175
+ const entities = extractEntities(trimmed);
176
+
177
+ for (const rule of RULES) {
178
+ if (rule.test(trimmed, entities)) {
179
+ return {
180
+ workflow: rule.workflow,
181
+ confidence: rule.confidence,
182
+ tier: "rule",
183
+ entities,
184
+ };
185
+ }
186
+ }
187
+
188
+ return {
189
+ workflow: "unclassified",
190
+ confidence: 0,
191
+ tier: "rule",
192
+ entities,
193
+ };
194
+ }
@@ -0,0 +1,29 @@
1
+ import type { PortfolioSlots, OptionsScreenerSlots } from "./types.js";
2
+
3
+ export const PORTFOLIO_DEFAULTS: Omit<PortfolioSlots, "budget"> = {
4
+ riskProfile: "balanced",
5
+ timeHorizon: "1y_plus",
6
+ assetScope: "mixed_etf_and_large_cap_equities",
7
+ positionCount: 4,
8
+ maxSinglePositionPct: 35,
9
+ };
10
+
11
+ export const OPTIONS_SCREENER_DEFAULTS: Omit<OptionsScreenerSlots, "symbol" | "direction"> = {
12
+ dteTarget: "25_to_45_days",
13
+ objective: "balanced_leverage_and_probability",
14
+ moneynessPreference: "atm_to_slightly_otm",
15
+ liquidityMinimum: "high_open_interest_and_tight_spread",
16
+ };
17
+
18
+ export function parseDteTarget(dteTarget: string): { minDays: number; maxDays: number } | null {
19
+ const rangeMatch = dteTarget.match(/^(\d+)_to_(\d+)_days$/);
20
+ if (rangeMatch) {
21
+ return { minDays: parseInt(rangeMatch[1], 10), maxDays: parseInt(rangeMatch[2], 10) };
22
+ }
23
+ const plusMatch = dteTarget.match(/^(\d+)_plus_days$/);
24
+ if (plusMatch) {
25
+ const min = parseInt(plusMatch[1], 10);
26
+ return { minDays: min, maxDays: Math.max(min, 1095) };
27
+ }
28
+ return null;
29
+ }