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.
- package/README.md +110 -87
- package/assets/logo.svg +187 -0
- package/dist/analysts/orchestrator.js +1 -2
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +38 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +34 -5
- package/dist/config.js +29 -8
- package/dist/config.js.map +1 -1
- package/dist/infra/browser.d.ts +10 -0
- package/dist/infra/browser.js +1 -0
- package/dist/infra/browser.js.map +1 -1
- package/dist/infra/cache.d.ts +4 -0
- package/dist/infra/cache.js +4 -0
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/native-dependencies.d.ts +1 -0
- package/dist/infra/native-dependencies.js +10 -0
- package/dist/infra/native-dependencies.js.map +1 -0
- package/dist/infra/node-version.d.ts +2 -0
- package/dist/infra/node-version.js +23 -0
- package/dist/infra/node-version.js.map +1 -0
- package/dist/infra/rate-limiter.js +6 -0
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/sqlite.js +42 -4
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/storage.d.ts +6 -0
- package/dist/memory/storage.js +3 -3
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/tool-defaults.d.ts +8 -0
- package/dist/memory/tool-defaults.js +59 -0
- package/dist/memory/tool-defaults.js.map +1 -0
- package/dist/onboarding/connect.d.ts +35 -0
- package/dist/onboarding/connect.js +118 -0
- package/dist/onboarding/connect.js.map +1 -0
- package/dist/onboarding/credential-interceptor.d.ts +44 -0
- package/dist/onboarding/credential-interceptor.js +72 -0
- package/dist/onboarding/credential-interceptor.js.map +1 -0
- package/dist/onboarding/degradation-accumulator.d.ts +21 -0
- package/dist/onboarding/degradation-accumulator.js +55 -0
- package/dist/onboarding/degradation-accumulator.js.map +1 -0
- package/dist/onboarding/prompt-user.d.ts +23 -0
- package/dist/onboarding/prompt-user.js +61 -0
- package/dist/onboarding/prompt-user.js.map +1 -0
- package/dist/onboarding/providers.d.ts +116 -0
- package/dist/onboarding/providers.js +239 -0
- package/dist/onboarding/providers.js.map +1 -0
- package/dist/onboarding/state.d.ts +31 -2
- package/dist/onboarding/state.js +141 -13
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.d.ts +34 -0
- package/dist/onboarding/tool-helpers.js +80 -0
- package/dist/onboarding/tool-helpers.js.map +1 -0
- package/dist/onboarding/tool-tags.d.ts +37 -0
- package/dist/onboarding/tool-tags.js +149 -0
- package/dist/onboarding/tool-tags.js.map +1 -0
- package/dist/onboarding/validation.d.ts +19 -0
- package/dist/onboarding/validation.js +117 -0
- package/dist/onboarding/validation.js.map +1 -0
- package/dist/pi/opencandle-extension.d.ts +7 -1
- package/dist/pi/opencandle-extension.js +488 -13
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session-storage.d.ts +2 -0
- package/dist/pi/session-storage.js +5 -0
- package/dist/pi/session-storage.js.map +1 -0
- package/dist/pi/session.d.ts +4 -1
- package/dist/pi/session.js +25 -3
- package/dist/pi/session.js.map +1 -1
- package/dist/pi/setup.d.ts +1 -2
- package/dist/pi/setup.js +67 -120
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tool-adapter.d.ts +2 -2
- package/dist/pi/tool-adapter.js +14 -1
- package/dist/pi/tool-adapter.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +22 -0
- package/dist/prompts/context-builder.js +47 -11
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/disclaimer.d.ts +6 -0
- package/dist/prompts/disclaimer.js +9 -0
- package/dist/prompts/disclaimer.js.map +1 -0
- package/dist/prompts/workflow-prompts.d.ts +8 -0
- package/dist/prompts/workflow-prompts.js +39 -5
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.js +20 -1
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/exa-search.d.ts +39 -0
- package/dist/providers/exa-search.js +276 -0
- package/dist/providers/exa-search.js.map +1 -0
- package/dist/providers/finnhub.d.ts +17 -0
- package/dist/providers/finnhub.js +94 -0
- package/dist/providers/finnhub.js.map +1 -0
- package/dist/providers/fred.js +13 -1
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/provider-credential-error.d.ts +8 -0
- package/dist/providers/provider-credential-error.js +22 -0
- package/dist/providers/provider-credential-error.js.map +1 -0
- package/dist/providers/reddit.d.ts +8 -0
- package/dist/providers/reddit.js +36 -9
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/twitter.js +2 -8
- package/dist/providers/twitter.js.map +1 -1
- package/dist/providers/web-search.d.ts +17 -0
- package/dist/providers/web-search.js +224 -0
- package/dist/providers/web-search.js.map +1 -0
- package/dist/providers/wrap-provider.d.ts +7 -0
- package/dist/providers/wrap-provider.js +15 -0
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.js +70 -33
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.js +22 -0
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.js +1 -1
- package/dist/routing/defaults.js.map +1 -1
- package/dist/routing/index.d.ts +4 -0
- package/dist/routing/index.js +3 -0
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/router-llm-client.d.ts +11 -0
- package/dist/routing/router-llm-client.js +42 -0
- package/dist/routing/router-llm-client.js.map +1 -0
- package/dist/routing/router-prompt.d.ts +2 -0
- package/dist/routing/router-prompt.js +138 -0
- package/dist/routing/router-prompt.js.map +1 -0
- package/dist/routing/router-types.d.ts +62 -0
- package/dist/routing/router-types.js +2 -0
- package/dist/routing/router-types.js.map +1 -0
- package/dist/routing/router.d.ts +10 -0
- package/dist/routing/router.js +194 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/runtime/session-coordinator.d.ts +63 -4
- package/dist/runtime/session-coordinator.js +155 -4
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/tool-defaults-wrapper.d.ts +3 -0
- package/dist/runtime/tool-defaults-wrapper.js +25 -0
- package/dist/runtime/tool-defaults-wrapper.js.map +1 -0
- package/dist/sentiment/adapters/finnhub.d.ts +7 -0
- package/dist/sentiment/adapters/finnhub.js +39 -0
- package/dist/sentiment/adapters/finnhub.js.map +1 -0
- package/dist/sentiment/adapters/reddit.d.ts +11 -0
- package/dist/sentiment/adapters/reddit.js +54 -0
- package/dist/sentiment/adapters/reddit.js.map +1 -0
- package/dist/sentiment/adapters/twitter.d.ts +9 -0
- package/dist/sentiment/adapters/twitter.js +32 -0
- package/dist/sentiment/adapters/twitter.js.map +1 -0
- package/dist/sentiment/adapters/web.d.ts +9 -0
- package/dist/sentiment/adapters/web.js +40 -0
- package/dist/sentiment/adapters/web.js.map +1 -0
- package/dist/sentiment/index.d.ts +16 -0
- package/dist/sentiment/index.js +44 -0
- package/dist/sentiment/index.js.map +1 -0
- package/dist/sentiment/keywords.d.ts +2 -0
- package/dist/sentiment/keywords.js +9 -0
- package/dist/sentiment/keywords.js.map +1 -0
- package/dist/sentiment/pipeline.d.ts +9 -0
- package/dist/sentiment/pipeline.js +57 -0
- package/dist/sentiment/pipeline.js.map +1 -0
- package/dist/sentiment/scorer.d.ts +9 -0
- package/dist/sentiment/scorer.js +64 -0
- package/dist/sentiment/scorer.js.map +1 -0
- package/dist/sentiment/store.d.ts +24 -0
- package/dist/sentiment/store.js +182 -0
- package/dist/sentiment/store.js.map +1 -0
- package/dist/sentiment/trends.d.ts +13 -0
- package/dist/sentiment/trends.js +73 -0
- package/dist/sentiment/trends.js.map +1 -0
- package/dist/sentiment/types.d.ts +66 -0
- package/dist/sentiment/types.js +54 -0
- package/dist/sentiment/types.js.map +1 -0
- package/dist/system-prompt.js +29 -13
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +4 -4
- package/dist/tools/fundamentals/company-overview.d.ts +4 -2
- package/dist/tools/fundamentals/company-overview.js +27 -27
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.d.ts +1 -1
- package/dist/tools/fundamentals/comps.js +45 -45
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.d.ts +1 -1
- package/dist/tools/fundamentals/dcf.js +82 -82
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.d.ts +4 -2
- package/dist/tools/fundamentals/earnings.js +25 -25
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.d.ts +4 -2
- package/dist/tools/fundamentals/financials.js +23 -23
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.d.ts +1 -1
- package/dist/tools/index.d.ts +28 -1
- package/dist/tools/index.js +35 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.d.ts +1 -1
- package/dist/tools/interaction/ask-user.js +28 -64
- package/dist/tools/interaction/ask-user.js.map +1 -1
- package/dist/tools/interaction/twitter-login.d.ts +1 -1
- package/dist/tools/macro/fear-greed.d.ts +1 -1
- package/dist/tools/macro/fred-data.d.ts +4 -2
- package/dist/tools/macro/fred-data.js +26 -26
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.d.ts +1 -1
- package/dist/tools/market/crypto-price.d.ts +1 -1
- package/dist/tools/market/search-ticker.d.ts +1 -1
- package/dist/tools/market/stock-history.d.ts +1 -1
- package/dist/tools/market/stock-quote.d.ts +1 -1
- package/dist/tools/options/option-chain.d.ts +1 -1
- package/dist/tools/options/option-chain.js +4 -1
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/predictions.d.ts +1 -1
- package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
- package/dist/tools/portfolio/tracker.d.ts +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +1 -1
- package/dist/tools/sentiment/reddit-sentiment.d.ts +4 -2
- package/dist/tools/sentiment/reddit-sentiment.js +107 -22
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +7 -0
- package/dist/tools/sentiment/sentiment-summary.js +230 -0
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -0
- package/dist/tools/sentiment/sentiment-trend.d.ts +22 -0
- package/dist/tools/sentiment/sentiment-trend.js +39 -0
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -0
- package/dist/tools/sentiment/twitter-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js +17 -0
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/web-search.d.ts +11 -0
- package/dist/tools/sentiment/web-search.js +115 -0
- package/dist/tools/sentiment/web-search.js.map +1 -0
- package/dist/tools/sentiment/web-sentiment.d.ts +8 -0
- package/dist/tools/sentiment/web-sentiment.js +66 -0
- package/dist/tools/sentiment/web-sentiment.js.map +1 -0
- package/dist/tools/technical/backtest.d.ts +1 -1
- package/dist/tools/technical/indicators.d.ts +1 -1
- package/dist/tools/technical/indicators.js +7 -1
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/sentiment.d.ts +21 -0
- package/dist/workflows/options-screener.js +7 -2
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.js +3 -3
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/background-quotes.ts +31 -0
- package/gui/server/chat-event-adapter.ts +142 -0
- package/gui/server/invoke-tool.ts +89 -0
- package/gui/server/live-chat-event-adapter.ts +181 -0
- package/gui/server/model-setup.ts +100 -0
- package/gui/server/package.json +5 -0
- package/gui/server/projector.ts +212 -0
- package/gui/server/server.ts +592 -0
- package/gui/server/session-actions.ts +31 -0
- package/gui/server/tool-metadata.ts +88 -0
- package/gui/server/websocket.ts +128 -0
- package/gui/server/writer-lock.ts +118 -0
- package/gui/shared/chat-events.ts +118 -0
- package/gui/shared/event-reducer.ts +186 -0
- package/gui/web/dist/assets/CatalogOverlay-D1ImSJTe.js +1 -0
- package/gui/web/dist/assets/index-DBrWq43L.css +1 -0
- package/gui/web/dist/assets/index-RflHaj0y.js +67 -0
- package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
- package/gui/web/dist/index.html +17 -0
- package/package.json +62 -20
- package/src/analysts/contracts.ts +189 -0
- package/src/analysts/orchestrator.ts +300 -0
- package/src/cli.ts +205 -0
- package/src/config.ts +161 -0
- package/src/index.ts +5 -0
- package/src/infra/browser.ts +111 -0
- package/src/infra/cache.ts +103 -0
- package/src/infra/http-client.ts +68 -0
- package/src/infra/index.ts +18 -0
- package/src/infra/native-dependencies.ts +12 -0
- package/src/infra/node-version.ts +24 -0
- package/src/infra/open-url.ts +28 -0
- package/src/infra/opencandle-paths.ts +64 -0
- package/src/infra/rate-limiter.ts +64 -0
- package/src/memory/index.ts +10 -0
- package/src/memory/manager.ts +159 -0
- package/src/memory/preference-extractor.ts +106 -0
- package/src/memory/retrieval.ts +70 -0
- package/src/memory/sqlite.ts +172 -0
- package/src/memory/storage.ts +204 -0
- package/src/memory/tool-defaults.ts +87 -0
- package/src/memory/types.ts +67 -0
- package/src/onboarding/connect.ts +184 -0
- package/src/onboarding/credential-interceptor.ts +134 -0
- package/src/onboarding/degradation-accumulator.ts +79 -0
- package/src/onboarding/prompt-user.ts +85 -0
- package/src/onboarding/providers.ts +315 -0
- package/src/onboarding/state.ts +218 -0
- package/src/onboarding/tool-helpers.ts +111 -0
- package/src/onboarding/tool-tags.ts +201 -0
- package/src/onboarding/validation.ts +158 -0
- package/src/pi/opencandle-extension.ts +724 -0
- package/src/pi/session-storage.ts +5 -0
- package/src/pi/session.ts +81 -0
- package/src/pi/setup.ts +371 -0
- package/src/pi/tool-adapter.ts +36 -0
- package/src/prompts/context-builder.ts +204 -0
- package/src/prompts/disclaimer.ts +9 -0
- package/src/prompts/sections.ts +46 -0
- package/src/prompts/workflow-prompts.ts +279 -0
- package/src/providers/alpha-vantage.ts +292 -0
- package/src/providers/coingecko.ts +96 -0
- package/src/providers/exa-search.ts +373 -0
- package/src/providers/fear-greed.ts +45 -0
- package/src/providers/finnhub.ts +124 -0
- package/src/providers/fred.ts +83 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/provider-credential-error.ts +23 -0
- package/src/providers/reddit.ts +151 -0
- package/src/providers/sec-edgar.ts +96 -0
- package/src/providers/twitter.ts +173 -0
- package/src/providers/web-search.ts +293 -0
- package/src/providers/with-fallback.ts +41 -0
- package/src/providers/wrap-provider.ts +64 -0
- package/src/providers/yahoo-finance.ts +367 -0
- package/src/routing/classify-intent.ts +194 -0
- package/src/routing/defaults.ts +29 -0
- package/src/routing/entity-extractor.ts +140 -0
- package/src/routing/index.ts +26 -0
- package/src/routing/router-llm-client.ts +51 -0
- package/src/routing/router-prompt.ts +159 -0
- package/src/routing/router-types.ts +66 -0
- package/src/routing/router.ts +213 -0
- package/src/routing/slot-resolver.ts +152 -0
- package/src/routing/types.ts +63 -0
- package/src/runtime/evidence.ts +77 -0
- package/src/runtime/index.ts +55 -0
- package/src/runtime/prompt-step.ts +75 -0
- package/src/runtime/provider-ids.ts +15 -0
- package/src/runtime/provider-tracker.ts +40 -0
- package/src/runtime/run-context.ts +22 -0
- package/src/runtime/session-coordinator.ts +406 -0
- package/src/runtime/tool-defaults-wrapper.ts +35 -0
- package/src/runtime/validation.ts +214 -0
- package/src/runtime/workflow-events.ts +75 -0
- package/src/runtime/workflow-runner.ts +188 -0
- package/src/runtime/workflow-types.ts +102 -0
- package/src/sentiment/adapters/finnhub.ts +44 -0
- package/src/sentiment/adapters/reddit.ts +65 -0
- package/src/sentiment/adapters/twitter.ts +36 -0
- package/src/sentiment/adapters/web.ts +44 -0
- package/src/sentiment/index.ts +58 -0
- package/src/sentiment/keywords.ts +9 -0
- package/src/sentiment/pipeline.ts +68 -0
- package/src/sentiment/scorer.ts +78 -0
- package/src/sentiment/store.ts +260 -0
- package/src/sentiment/trends.ts +90 -0
- package/src/sentiment/types.ts +108 -0
- package/src/system-prompt.ts +115 -0
- package/src/tool-kit.ts +68 -0
- package/src/tools/AGENTS.md +36 -0
- package/src/tools/fundamentals/company-overview.ts +54 -0
- package/src/tools/fundamentals/comps.ts +156 -0
- package/src/tools/fundamentals/dcf.ts +267 -0
- package/src/tools/fundamentals/earnings.ts +47 -0
- package/src/tools/fundamentals/financials.ts +54 -0
- package/src/tools/fundamentals/sec-filings.ts +61 -0
- package/src/tools/index.ts +88 -0
- package/src/tools/interaction/ask-user.ts +81 -0
- package/src/tools/interaction/twitter-login.ts +93 -0
- package/src/tools/macro/fear-greed.ts +41 -0
- package/src/tools/macro/fred-data.ts +54 -0
- package/src/tools/market/crypto-history.ts +51 -0
- package/src/tools/market/crypto-price.ts +53 -0
- package/src/tools/market/search-ticker.ts +53 -0
- package/src/tools/market/stock-history.ts +79 -0
- package/src/tools/market/stock-quote.ts +64 -0
- package/src/tools/options/greeks.ts +82 -0
- package/src/tools/options/option-chain.ts +91 -0
- package/src/tools/portfolio/correlation.ts +162 -0
- package/src/tools/portfolio/predictions.ts +253 -0
- package/src/tools/portfolio/risk-analysis.ts +134 -0
- package/src/tools/portfolio/tracker.ts +147 -0
- package/src/tools/portfolio/watchlist.ts +153 -0
- package/src/tools/sentiment/reddit-sentiment.ts +164 -0
- package/src/tools/sentiment/sentiment-summary.ts +256 -0
- package/src/tools/sentiment/sentiment-trend.ts +58 -0
- package/src/tools/sentiment/twitter-sentiment.ts +96 -0
- package/src/tools/sentiment/web-search.ts +150 -0
- package/src/tools/sentiment/web-sentiment.ts +76 -0
- package/src/tools/technical/backtest.ts +246 -0
- package/src/tools/technical/indicators.ts +258 -0
- package/src/types/fundamentals.ts +46 -0
- package/src/types/index.ts +20 -0
- package/src/types/macro.ts +27 -0
- package/src/types/market.ts +43 -0
- package/src/types/options.ts +35 -0
- package/src/types/portfolio.ts +41 -0
- package/src/types/sentiment.ts +70 -0
- package/src/workflows/compare-assets.ts +39 -0
- package/src/workflows/index.ts +4 -0
- package/src/workflows/options-screener.ts +49 -0
- package/src/workflows/portfolio-builder.ts +52 -0
- package/src/workflows/types.ts +4 -0
- package/dist/tools/sentiment/news-sentiment.d.ts +0 -7
- package/dist/tools/sentiment/news-sentiment.js +0 -55
- package/dist/tools/sentiment/news-sentiment.js.map +0 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export class RedditAdapter {
|
|
3
|
+
source = "reddit";
|
|
4
|
+
mapPostsToRecords(result, query) {
|
|
5
|
+
const fetchedAt = result.fetchedAt;
|
|
6
|
+
return result.posts.map((post) => ({
|
|
7
|
+
id: randomUUID(),
|
|
8
|
+
source: this.source,
|
|
9
|
+
sourceId: post.id,
|
|
10
|
+
query,
|
|
11
|
+
title: post.title,
|
|
12
|
+
text: post.selftext ? `${post.title}\n${post.selftext}` : post.title,
|
|
13
|
+
author: post.author,
|
|
14
|
+
url: post.url,
|
|
15
|
+
publishedAt: post.created,
|
|
16
|
+
fetchedAt,
|
|
17
|
+
engagement: {
|
|
18
|
+
score: post.score,
|
|
19
|
+
replies: post.comments,
|
|
20
|
+
shares: null,
|
|
21
|
+
views: null,
|
|
22
|
+
},
|
|
23
|
+
sentiment: { score: 0, confidence: 0, method: "keyword", tickers: [] },
|
|
24
|
+
metadata: { subreddit: result.subreddit },
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
mapCommentsToRecords(comments, parentId, subreddit, query) {
|
|
28
|
+
const fetchedAt = new Date().toISOString();
|
|
29
|
+
return comments.map((comment) => ({
|
|
30
|
+
id: randomUUID(),
|
|
31
|
+
source: this.source,
|
|
32
|
+
sourceId: comment.id,
|
|
33
|
+
query,
|
|
34
|
+
title: null,
|
|
35
|
+
text: comment.body,
|
|
36
|
+
author: comment.author,
|
|
37
|
+
url: comment.permalink,
|
|
38
|
+
publishedAt: null,
|
|
39
|
+
fetchedAt,
|
|
40
|
+
engagement: {
|
|
41
|
+
score: comment.score,
|
|
42
|
+
replies: null,
|
|
43
|
+
shares: null,
|
|
44
|
+
views: null,
|
|
45
|
+
},
|
|
46
|
+
sentiment: { score: 0, confidence: 0, method: "keyword", tickers: [] },
|
|
47
|
+
metadata: { isComment: true, parentId, subreddit },
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
async fetch(_query, _options) {
|
|
51
|
+
throw new Error("Use pipeline.run() instead of adapter.fetch() directly");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=reddit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reddit.js","sourceRoot":"","sources":["../../../src/sentiment/adapters/reddit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC,MAAM,OAAO,aAAa;IACf,MAAM,GAAG,QAAiB,CAAC;IAEpC,iBAAiB,CAAC,MAA6B,EAAE,KAAa;QAC5D,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;QACnC,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACjC,EAAE,EAAE,UAAU,EAAE;YAChB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,EAAE;YACjB,KAAK;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK;YACpE,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,WAAW,EAAE,IAAI,CAAC,OAAO;YACzB,SAAS;YACT,UAAU,EAAE;gBACV,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,IAAI;aACZ;YACD,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,SAAkB,EAAE,OAAO,EAAE,EAAE,EAAE;YAC/E,QAAQ,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE;SAC1C,CAAC,CAAC,CAAC;IACN,CAAC;IAED,oBAAoB,CAClB,QAAyB,EACzB,QAAgB,EAChB,SAAiB,EACjB,KAAa;QAEb,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAChC,EAAE,EAAE,UAAU,EAAE;YAChB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,OAAO,CAAC,EAAE;YACpB,KAAK;YACL,KAAK,EAAE,IAAI;YACX,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,EAAE,OAAO,CAAC,SAAS;YACtB,WAAW,EAAE,IAAI;YACjB,SAAS;YACT,UAAU,EAAE;gBACV,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,IAAI;aACZ;YACD,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,SAAkB,EAAE,OAAO,EAAE,EAAE,EAAE;YAC/E,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE;SACnD,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,QAA6B;QACvD,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SentinelRecord, SentimentAdapter } from "../types.js";
|
|
2
|
+
import type { TwitterSentimentResult } from "../../types/sentiment.js";
|
|
3
|
+
export declare class TwitterAdapter implements SentimentAdapter {
|
|
4
|
+
readonly source: "twitter";
|
|
5
|
+
mapToRecords(result: TwitterSentimentResult, query: string): SentinelRecord[];
|
|
6
|
+
fetch(_query: string, _options?: {
|
|
7
|
+
hours?: number;
|
|
8
|
+
}): Promise<SentinelRecord[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export class TwitterAdapter {
|
|
3
|
+
source = "twitter";
|
|
4
|
+
mapToRecords(result, query) {
|
|
5
|
+
const fetchedAt = result.fetchedAt;
|
|
6
|
+
return result.tweets.map((tweet) => ({
|
|
7
|
+
id: randomUUID(),
|
|
8
|
+
source: this.source,
|
|
9
|
+
sourceId: tweet.id,
|
|
10
|
+
query,
|
|
11
|
+
title: null,
|
|
12
|
+
text: tweet.text,
|
|
13
|
+
author: tweet.author,
|
|
14
|
+
url: tweet.url,
|
|
15
|
+
publishedAt: tweet.created,
|
|
16
|
+
fetchedAt,
|
|
17
|
+
engagement: {
|
|
18
|
+
score: tweet.likes,
|
|
19
|
+
replies: tweet.replies,
|
|
20
|
+
shares: tweet.retweets,
|
|
21
|
+
views: tweet.views,
|
|
22
|
+
},
|
|
23
|
+
sentiment: { score: 0, confidence: 0, method: "keyword", tickers: [] },
|
|
24
|
+
metadata: {},
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
async fetch(_query, _options) {
|
|
28
|
+
// Actual fetching is done by the pipeline via the provider
|
|
29
|
+
throw new Error("Use pipeline.run() instead of adapter.fetch() directly");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=twitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitter.js","sourceRoot":"","sources":["../../../src/sentiment/adapters/twitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,MAAM,OAAO,cAAc;IAChB,MAAM,GAAG,SAAkB,CAAC;IAErC,YAAY,CAAC,MAA8B,EAAE,KAAa;QACxD,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;QACnC,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACnC,EAAE,EAAE,UAAU,EAAE;YAChB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,KAAK,CAAC,EAAE;YAClB,KAAK;YACL,KAAK,EAAE,IAAI;YACX,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,WAAW,EAAE,KAAK,CAAC,OAAO;YAC1B,SAAS;YACT,UAAU,EAAE;gBACV,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,MAAM,EAAE,KAAK,CAAC,QAAQ;gBACtB,KAAK,EAAE,KAAK,CAAC,KAAK;aACnB;YACD,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,SAAkB,EAAE,OAAO,EAAE,EAAE,EAAE;YAC/E,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,QAA6B;QACvD,2DAA2D;QAC3D,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SentinelRecord, SentimentAdapter } from "../types.js";
|
|
2
|
+
import type { WebSearchEnvelope } from "../../types/sentiment.js";
|
|
3
|
+
export declare class WebAdapter implements SentimentAdapter {
|
|
4
|
+
readonly source: "web";
|
|
5
|
+
mapToRecords(envelope: WebSearchEnvelope, query: string): SentinelRecord[];
|
|
6
|
+
fetch(_query: string, _options?: {
|
|
7
|
+
hours?: number;
|
|
8
|
+
}): Promise<SentinelRecord[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
export class WebAdapter {
|
|
3
|
+
source = "web";
|
|
4
|
+
mapToRecords(envelope, query) {
|
|
5
|
+
const fetchedAt = envelope.fetchedAt;
|
|
6
|
+
return envelope.results.map((result) => ({
|
|
7
|
+
id: randomUUID(),
|
|
8
|
+
source: this.source,
|
|
9
|
+
sourceId: canonicalizeUrl(result.url),
|
|
10
|
+
query,
|
|
11
|
+
title: result.title,
|
|
12
|
+
text: result.snippet,
|
|
13
|
+
author: result.source,
|
|
14
|
+
url: result.url,
|
|
15
|
+
publishedAt: result.published,
|
|
16
|
+
fetchedAt,
|
|
17
|
+
engagement: {
|
|
18
|
+
score: 0,
|
|
19
|
+
replies: null,
|
|
20
|
+
shares: null,
|
|
21
|
+
views: null,
|
|
22
|
+
},
|
|
23
|
+
sentiment: { score: 0, confidence: 0, method: "keyword", tickers: [] },
|
|
24
|
+
metadata: { category: result.category, provider: envelope.provider },
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
async fetch(_query, _options) {
|
|
28
|
+
throw new Error("Use pipeline.run() instead of adapter.fetch() directly");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function canonicalizeUrl(url) {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = new URL(url);
|
|
34
|
+
return parsed.origin + parsed.pathname;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return url;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../../src/sentiment/adapters/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,MAAM,OAAO,UAAU;IACZ,MAAM,GAAG,KAAc,CAAC;IAEjC,YAAY,CAAC,QAA2B,EAAE,KAAa;QACrD,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QACrC,OAAO,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACvC,EAAE,EAAE,UAAU,EAAE;YAChB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC;YACrC,KAAK;YACL,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,OAAO;YACpB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,WAAW,EAAE,MAAM,CAAC,SAAS;YAC7B,SAAS;YACT,UAAU,EAAE;gBACV,KAAK,EAAE,CAAC;gBACR,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,IAAI;aACZ;YACD,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,SAAkB,EAAE,OAAO,EAAE,EAAE,EAAE;YAC/E,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE;SACrE,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,QAA6B;QACvD,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;CACF;AAED,SAAS,eAAe,CAAC,GAAW;IAClC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type { SentinelRecord, SentinelEngagement, SentinelSentiment, SentimentAdapter, ScorerOptions, TrendBucket, TrendResult, DivergenceResult, SentimentSummary, SentimentSource, } from "./types.js";
|
|
2
|
+
export { isSentinelRecord, SENTIMENT_SOURCES } from "./types.js";
|
|
3
|
+
export { SentimentStore } from "./store.js";
|
|
4
|
+
export { scoreRecords, keywordScore } from "./scorer.js";
|
|
5
|
+
export { SentimentPipeline } from "./pipeline.js";
|
|
6
|
+
export { renderSparkline, computeTrend, computeDivergence } from "./trends.js";
|
|
7
|
+
export { BULLISH_TERMS, BEARISH_TERMS } from "./keywords.js";
|
|
8
|
+
export { TwitterAdapter } from "./adapters/twitter.js";
|
|
9
|
+
export { RedditAdapter } from "./adapters/reddit.js";
|
|
10
|
+
export { WebAdapter } from "./adapters/web.js";
|
|
11
|
+
import { SentimentStore } from "./store.js";
|
|
12
|
+
import { SentimentPipeline } from "./pipeline.js";
|
|
13
|
+
export declare function getSentimentStore(): SentimentStore;
|
|
14
|
+
export declare function getSentimentPipeline(): SentimentPipeline;
|
|
15
|
+
/** For testing: reset singletons */
|
|
16
|
+
export declare function _resetSentimentSingletons(): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export { isSentinelRecord, SENTIMENT_SOURCES } from "./types.js";
|
|
2
|
+
export { SentimentStore } from "./store.js";
|
|
3
|
+
export { scoreRecords, keywordScore } from "./scorer.js";
|
|
4
|
+
export { SentimentPipeline } from "./pipeline.js";
|
|
5
|
+
export { renderSparkline, computeTrend, computeDivergence } from "./trends.js";
|
|
6
|
+
export { BULLISH_TERMS, BEARISH_TERMS } from "./keywords.js";
|
|
7
|
+
export { TwitterAdapter } from "./adapters/twitter.js";
|
|
8
|
+
export { RedditAdapter } from "./adapters/reddit.js";
|
|
9
|
+
export { WebAdapter } from "./adapters/web.js";
|
|
10
|
+
import { SentimentStore } from "./store.js";
|
|
11
|
+
import { SentimentPipeline } from "./pipeline.js";
|
|
12
|
+
import { getConfig } from "../config.js";
|
|
13
|
+
import { resolveOpenCandlePath } from "../infra/opencandle-paths.js";
|
|
14
|
+
let _pipeline = null;
|
|
15
|
+
let _store = null;
|
|
16
|
+
export function getSentimentStore() {
|
|
17
|
+
if (!_store) {
|
|
18
|
+
const dbPath = resolveOpenCandlePath("sentinel.db");
|
|
19
|
+
_store = new SentimentStore(dbPath);
|
|
20
|
+
const config = getConfig();
|
|
21
|
+
_store.prune(config.sentiment?.retentionDays ?? 30);
|
|
22
|
+
}
|
|
23
|
+
return _store;
|
|
24
|
+
}
|
|
25
|
+
export function getSentimentPipeline() {
|
|
26
|
+
if (!_pipeline) {
|
|
27
|
+
const store = getSentimentStore();
|
|
28
|
+
const config = getConfig();
|
|
29
|
+
_pipeline = new SentimentPipeline(store, config.sentiment);
|
|
30
|
+
}
|
|
31
|
+
return _pipeline;
|
|
32
|
+
}
|
|
33
|
+
/** For testing: reset singletons */
|
|
34
|
+
export function _resetSentimentSingletons() {
|
|
35
|
+
if (_store) {
|
|
36
|
+
try {
|
|
37
|
+
_store.close();
|
|
38
|
+
}
|
|
39
|
+
catch { /* ignore */ }
|
|
40
|
+
}
|
|
41
|
+
_pipeline = null;
|
|
42
|
+
_store = null;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sentiment/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE,IAAI,SAAS,GAA6B,IAAI,CAAC;AAC/C,IAAI,MAAM,GAA0B,IAAI,CAAC;AAEzC,MAAM,UAAU,iBAAiB;IAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;QACpD,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,SAAS,GAAG,IAAI,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,SAAU,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,yBAAyB;IACvC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAChD,CAAC;IACD,SAAS,GAAG,IAAI,CAAC;IACjB,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const BULLISH_TERMS: readonly ["moon", "buy", "undervalued", "breakout", "calls", "bullish", "rocket", "diamond hands", "accumulate", "dip buy", "long", "rip", "squeeze"];
|
|
2
|
+
export declare const BEARISH_TERMS: readonly ["crash", "overvalued", "sell", "puts", "bearish", "bubble", "dump", "short", "bagholding", "exit", "drill", "tank", "rug"];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const BULLISH_TERMS = [
|
|
2
|
+
"moon", "buy", "undervalued", "breakout", "calls", "bullish",
|
|
3
|
+
"rocket", "diamond hands", "accumulate", "dip buy", "long", "rip", "squeeze",
|
|
4
|
+
];
|
|
5
|
+
export const BEARISH_TERMS = [
|
|
6
|
+
"crash", "overvalued", "sell", "puts", "bearish", "bubble",
|
|
7
|
+
"dump", "short", "bagholding", "exit", "drill", "tank", "rug",
|
|
8
|
+
];
|
|
9
|
+
//# sourceMappingURL=keywords.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keywords.js","sourceRoot":"","sources":["../../src/sentiment/keywords.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS;IAC5D,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS;CACpE,CAAC;AAEX,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ;IAC1D,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK;CACrD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SentinelRecord, SentimentSummary } from "./types.js";
|
|
2
|
+
import type { SentimentConfig } from "../config.js";
|
|
3
|
+
import { SentimentStore } from "./store.js";
|
|
4
|
+
export declare class SentimentPipeline {
|
|
5
|
+
private store;
|
|
6
|
+
private config;
|
|
7
|
+
constructor(store: SentimentStore, config: SentimentConfig);
|
|
8
|
+
processRecords(records: SentinelRecord[], query: string): Promise<SentimentSummary>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { scoreRecords } from "./scorer.js";
|
|
2
|
+
import { computeTrend, computeDivergence } from "./trends.js";
|
|
3
|
+
export class SentimentPipeline {
|
|
4
|
+
store;
|
|
5
|
+
config;
|
|
6
|
+
constructor(store, config) {
|
|
7
|
+
this.store = store;
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
async processRecords(records, query) {
|
|
11
|
+
const warnings = [];
|
|
12
|
+
if (records.length === 0) {
|
|
13
|
+
return { fresh: [], trend: null, divergence: null, warnings };
|
|
14
|
+
}
|
|
15
|
+
// Check if store had prior data before this fetch
|
|
16
|
+
const priorSeries = this.store.getTimeSeries(query, { days: 30, bucketHours: 24 });
|
|
17
|
+
const hadPriorData = priorSeries.length >= 2;
|
|
18
|
+
// Score all records
|
|
19
|
+
const scored = scoreRecords(records);
|
|
20
|
+
// Insert into store
|
|
21
|
+
this.store.insert(scored);
|
|
22
|
+
// Compute trend from historical data (only if we had prior data)
|
|
23
|
+
let trend = null;
|
|
24
|
+
if (hadPriorData) {
|
|
25
|
+
const series = this.store.getTimeSeries(query, { days: 7, bucketHours: 24 });
|
|
26
|
+
if (series.length >= 2) {
|
|
27
|
+
trend = [computeTrend(series, "aggregate")];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Compute divergence from fresh records
|
|
31
|
+
let divergence = null;
|
|
32
|
+
const sourceGroups = groupBySource(scored);
|
|
33
|
+
const sourceStats = {};
|
|
34
|
+
for (const [source, recs] of Object.entries(sourceGroups)) {
|
|
35
|
+
// Exclude comments from divergence calculation
|
|
36
|
+
const postLevel = recs.filter((r) => !r.metadata.isComment);
|
|
37
|
+
if (postLevel.length > 0) {
|
|
38
|
+
const avg = postLevel.reduce((sum, r) => sum + r.sentiment.score, 0) / postLevel.length;
|
|
39
|
+
sourceStats[source] = { avg, count: postLevel.length };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (Object.keys(sourceStats).length >= 2) {
|
|
43
|
+
divergence = computeDivergence(sourceStats, this.config.divergenceThreshold);
|
|
44
|
+
}
|
|
45
|
+
return { fresh: scored, trend, divergence, warnings };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function groupBySource(records) {
|
|
49
|
+
const groups = {};
|
|
50
|
+
for (const r of records) {
|
|
51
|
+
if (!groups[r.source])
|
|
52
|
+
groups[r.source] = [];
|
|
53
|
+
groups[r.source].push(r);
|
|
54
|
+
}
|
|
55
|
+
return groups;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/sentiment/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SentinelRecord } from "./types.js";
|
|
2
|
+
interface ScoreResult {
|
|
3
|
+
score: number;
|
|
4
|
+
confidence: number;
|
|
5
|
+
tickers: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function keywordScore(record: SentinelRecord): ScoreResult;
|
|
8
|
+
export declare function scoreRecords(records: SentinelRecord[]): SentinelRecord[];
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { BULLISH_TERMS, BEARISH_TERMS } from "./keywords.js";
|
|
2
|
+
const TICKER_REGEX = /\$([A-Z]{1,5})\b/g;
|
|
3
|
+
export function keywordScore(record) {
|
|
4
|
+
const lower = record.text.toLowerCase();
|
|
5
|
+
const engagement = 1 + (record.engagement.score ?? 0);
|
|
6
|
+
let bullishWeight = 0;
|
|
7
|
+
let bearishWeight = 0;
|
|
8
|
+
let bullishCount = 0;
|
|
9
|
+
let bearishCount = 0;
|
|
10
|
+
for (const term of BULLISH_TERMS) {
|
|
11
|
+
if (lower.includes(term)) {
|
|
12
|
+
bullishCount++;
|
|
13
|
+
bullishWeight += engagement;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const term of BEARISH_TERMS) {
|
|
17
|
+
if (lower.includes(term)) {
|
|
18
|
+
bearishCount++;
|
|
19
|
+
bearishWeight += engagement;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const totalWeight = bullishWeight + bearishWeight;
|
|
23
|
+
const score = totalWeight === 0 ? 0 : (bullishWeight - bearishWeight) / totalWeight;
|
|
24
|
+
const totalMatches = bullishCount + bearishCount;
|
|
25
|
+
let confidence = 0;
|
|
26
|
+
if (totalMatches > 0) {
|
|
27
|
+
// Base confidence from keyword matches
|
|
28
|
+
confidence = Math.min(totalMatches / 5, 1) * 0.6;
|
|
29
|
+
// Boost for longer text
|
|
30
|
+
const textLength = record.text.length;
|
|
31
|
+
confidence += Math.min(textLength / 500, 1) * 0.3;
|
|
32
|
+
// Multiple matches boost
|
|
33
|
+
if (totalMatches >= 3)
|
|
34
|
+
confidence += 0.1;
|
|
35
|
+
// Twitter penalty
|
|
36
|
+
if (record.source === "twitter")
|
|
37
|
+
confidence -= 0.1;
|
|
38
|
+
// Clamp
|
|
39
|
+
confidence = Math.max(0, Math.min(1, confidence));
|
|
40
|
+
}
|
|
41
|
+
// Extract tickers
|
|
42
|
+
const tickers = [];
|
|
43
|
+
for (const match of record.text.matchAll(TICKER_REGEX)) {
|
|
44
|
+
if (!tickers.includes(match[1])) {
|
|
45
|
+
tickers.push(match[1]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { score, confidence, tickers };
|
|
49
|
+
}
|
|
50
|
+
export function scoreRecords(records) {
|
|
51
|
+
return records.map((record) => {
|
|
52
|
+
const result = keywordScore(record);
|
|
53
|
+
return {
|
|
54
|
+
...record,
|
|
55
|
+
sentiment: {
|
|
56
|
+
score: result.score,
|
|
57
|
+
confidence: result.confidence,
|
|
58
|
+
method: "keyword",
|
|
59
|
+
tickers: result.tickers,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.js","sourceRoot":"","sources":["../../src/sentiment/scorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SentinelRecord, SentimentSource, TrendBucket } from "./types.js";
|
|
2
|
+
export interface SearchOptions {
|
|
3
|
+
source?: SentimentSource;
|
|
4
|
+
since?: string;
|
|
5
|
+
until?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TimeSeriesOptions {
|
|
8
|
+
days: number;
|
|
9
|
+
bucketHours: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class SentimentStore {
|
|
12
|
+
private db;
|
|
13
|
+
constructor(pathOrMemory: string);
|
|
14
|
+
private initSchema;
|
|
15
|
+
getSchemaVersion(): number;
|
|
16
|
+
insert(records: SentinelRecord[]): void;
|
|
17
|
+
search(query: string, options?: SearchOptions): SentinelRecord[];
|
|
18
|
+
getByTicker(ticker: string, options?: {
|
|
19
|
+
since?: string;
|
|
20
|
+
}): SentinelRecord[];
|
|
21
|
+
getTimeSeries(query: string, options: TimeSeriesOptions): TrendBucket[];
|
|
22
|
+
prune(retentionDays: number): number;
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
const SCHEMA_VERSION = 1;
|
|
5
|
+
const CREATE_SCHEMA = `
|
|
6
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
7
|
+
version INTEGER NOT NULL
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE TABLE IF NOT EXISTS sentinel_records (
|
|
11
|
+
id TEXT NOT NULL,
|
|
12
|
+
source TEXT NOT NULL,
|
|
13
|
+
source_id TEXT NOT NULL,
|
|
14
|
+
query TEXT NOT NULL,
|
|
15
|
+
title TEXT,
|
|
16
|
+
text TEXT NOT NULL,
|
|
17
|
+
author TEXT,
|
|
18
|
+
url TEXT NOT NULL,
|
|
19
|
+
published_at TEXT,
|
|
20
|
+
fetched_at TEXT NOT NULL,
|
|
21
|
+
engagement_json TEXT NOT NULL,
|
|
22
|
+
sentiment_score REAL NOT NULL,
|
|
23
|
+
sentiment_confidence REAL NOT NULL,
|
|
24
|
+
sentiment_method TEXT NOT NULL,
|
|
25
|
+
tickers_json TEXT NOT NULL,
|
|
26
|
+
metadata_json TEXT NOT NULL,
|
|
27
|
+
UNIQUE(source, source_id, fetched_at)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_source ON sentinel_records(source);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_fetched_at ON sentinel_records(fetched_at);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_query ON sentinel_records(query);
|
|
33
|
+
|
|
34
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS sentinel_fts USING fts5(
|
|
35
|
+
text, title, author, query, source,
|
|
36
|
+
content=sentinel_records,
|
|
37
|
+
content_rowid=rowid
|
|
38
|
+
);
|
|
39
|
+
`;
|
|
40
|
+
const TRIGGERS = `
|
|
41
|
+
CREATE TRIGGER IF NOT EXISTS sentinel_ai AFTER INSERT ON sentinel_records BEGIN
|
|
42
|
+
INSERT INTO sentinel_fts(rowid, text, title, author, query, source)
|
|
43
|
+
VALUES (new.rowid, new.text, new.title, new.author, new.query, new.source);
|
|
44
|
+
END;
|
|
45
|
+
|
|
46
|
+
CREATE TRIGGER IF NOT EXISTS sentinel_ad AFTER DELETE ON sentinel_records BEGIN
|
|
47
|
+
INSERT INTO sentinel_fts(sentinel_fts, rowid, text, title, author, query, source)
|
|
48
|
+
VALUES ('delete', old.rowid, old.text, old.title, old.author, old.query, old.source);
|
|
49
|
+
END;
|
|
50
|
+
`;
|
|
51
|
+
export class SentimentStore {
|
|
52
|
+
db;
|
|
53
|
+
constructor(pathOrMemory) {
|
|
54
|
+
if (pathOrMemory !== ":memory:") {
|
|
55
|
+
mkdirSync(dirname(pathOrMemory), { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
this.db = new Database(pathOrMemory);
|
|
58
|
+
if (pathOrMemory !== ":memory:") {
|
|
59
|
+
this.db.pragma("journal_mode = WAL");
|
|
60
|
+
}
|
|
61
|
+
this.initSchema();
|
|
62
|
+
}
|
|
63
|
+
initSchema() {
|
|
64
|
+
this.db.exec(CREATE_SCHEMA);
|
|
65
|
+
this.db.exec(TRIGGERS);
|
|
66
|
+
const row = this.db.prepare("SELECT version FROM schema_version").get();
|
|
67
|
+
if (!row) {
|
|
68
|
+
this.db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(SCHEMA_VERSION);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
getSchemaVersion() {
|
|
72
|
+
const row = this.db.prepare("SELECT version FROM schema_version").get();
|
|
73
|
+
return row.version;
|
|
74
|
+
}
|
|
75
|
+
insert(records) {
|
|
76
|
+
const stmt = this.db.prepare(`
|
|
77
|
+
INSERT OR IGNORE INTO sentinel_records
|
|
78
|
+
(id, source, source_id, query, title, text, author, url,
|
|
79
|
+
published_at, fetched_at, engagement_json,
|
|
80
|
+
sentiment_score, sentiment_confidence, sentiment_method,
|
|
81
|
+
tickers_json, metadata_json)
|
|
82
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
83
|
+
`);
|
|
84
|
+
const tx = this.db.transaction((recs) => {
|
|
85
|
+
for (const r of recs) {
|
|
86
|
+
stmt.run(r.id, r.source, r.sourceId, r.query, r.title, r.text, r.author, r.url, r.publishedAt, r.fetchedAt, JSON.stringify(r.engagement), r.sentiment.score, r.sentiment.confidence, r.sentiment.method, JSON.stringify(r.sentiment.tickers), JSON.stringify(r.metadata));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
tx(records);
|
|
90
|
+
}
|
|
91
|
+
search(query, options) {
|
|
92
|
+
let sql = `
|
|
93
|
+
SELECT sr.* FROM sentinel_records sr
|
|
94
|
+
JOIN sentinel_fts fts ON sr.rowid = fts.rowid
|
|
95
|
+
WHERE sentinel_fts MATCH ?
|
|
96
|
+
`;
|
|
97
|
+
const params = [query];
|
|
98
|
+
if (options?.source) {
|
|
99
|
+
sql += " AND sr.source = ?";
|
|
100
|
+
params.push(options.source);
|
|
101
|
+
}
|
|
102
|
+
if (options?.since) {
|
|
103
|
+
sql += " AND sr.fetched_at >= ?";
|
|
104
|
+
params.push(options.since);
|
|
105
|
+
}
|
|
106
|
+
if (options?.until) {
|
|
107
|
+
sql += " AND sr.fetched_at <= ?";
|
|
108
|
+
params.push(options.until);
|
|
109
|
+
}
|
|
110
|
+
sql += " ORDER BY bm25(sentinel_fts) LIMIT 100";
|
|
111
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
112
|
+
return rows.map(rowToRecord);
|
|
113
|
+
}
|
|
114
|
+
getByTicker(ticker, options) {
|
|
115
|
+
let sql = `SELECT * FROM sentinel_records WHERE tickers_json LIKE ?`;
|
|
116
|
+
const pattern = `%"${ticker}"%`;
|
|
117
|
+
const params = [pattern];
|
|
118
|
+
if (options?.since) {
|
|
119
|
+
sql += " AND fetched_at >= ?";
|
|
120
|
+
params.push(options.since);
|
|
121
|
+
}
|
|
122
|
+
sql += " ORDER BY fetched_at DESC LIMIT 100";
|
|
123
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
124
|
+
return rows.map(rowToRecord);
|
|
125
|
+
}
|
|
126
|
+
getTimeSeries(query, options) {
|
|
127
|
+
const since = new Date();
|
|
128
|
+
since.setDate(since.getDate() - options.days);
|
|
129
|
+
const sinceStr = since.toISOString();
|
|
130
|
+
const bucketSeconds = options.bucketHours * 3600;
|
|
131
|
+
const sql = `
|
|
132
|
+
SELECT
|
|
133
|
+
(CAST(strftime('%s', fetched_at) AS INTEGER) / ?) * ? AS bucket_ts,
|
|
134
|
+
AVG(sentiment_score) AS avg_score,
|
|
135
|
+
COUNT(*) AS cnt
|
|
136
|
+
FROM sentinel_records
|
|
137
|
+
WHERE query = ? AND fetched_at >= ?
|
|
138
|
+
GROUP BY bucket_ts
|
|
139
|
+
ORDER BY bucket_ts
|
|
140
|
+
`;
|
|
141
|
+
const rows = this.db.prepare(sql).all(bucketSeconds, bucketSeconds, query, sinceStr);
|
|
142
|
+
return rows.map((r) => ({
|
|
143
|
+
timestamp: new Date(r.bucket_ts * 1000).toISOString(),
|
|
144
|
+
avgScore: r.avg_score,
|
|
145
|
+
count: r.cnt,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
prune(retentionDays) {
|
|
149
|
+
const cutoff = new Date();
|
|
150
|
+
cutoff.setDate(cutoff.getDate() - retentionDays);
|
|
151
|
+
const result = this.db
|
|
152
|
+
.prepare("DELETE FROM sentinel_records WHERE fetched_at < ?")
|
|
153
|
+
.run(cutoff.toISOString());
|
|
154
|
+
return result.changes;
|
|
155
|
+
}
|
|
156
|
+
close() {
|
|
157
|
+
this.db.close();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function rowToRecord(row) {
|
|
161
|
+
return {
|
|
162
|
+
id: row.id,
|
|
163
|
+
source: row.source,
|
|
164
|
+
sourceId: row.source_id,
|
|
165
|
+
query: row.query,
|
|
166
|
+
title: row.title,
|
|
167
|
+
text: row.text,
|
|
168
|
+
author: row.author,
|
|
169
|
+
url: row.url,
|
|
170
|
+
publishedAt: row.published_at,
|
|
171
|
+
fetchedAt: row.fetched_at,
|
|
172
|
+
engagement: JSON.parse(row.engagement_json),
|
|
173
|
+
sentiment: {
|
|
174
|
+
score: row.sentiment_score,
|
|
175
|
+
confidence: row.sentiment_confidence,
|
|
176
|
+
method: row.sentiment_method,
|
|
177
|
+
tickers: JSON.parse(row.tickers_json),
|
|
178
|
+
},
|
|
179
|
+
metadata: JSON.parse(row.metadata_json),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/sentiment/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TrendBucket, TrendResult, DivergenceResult, SentimentSource } from "./types.js";
|
|
2
|
+
export declare function renderSparkline(values: number[]): string;
|
|
3
|
+
export declare function computeTrend(buckets: TrendBucket[], source: SentimentSource | "aggregate"): TrendResult;
|
|
4
|
+
export interface SourceStats {
|
|
5
|
+
avg: number;
|
|
6
|
+
count: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function computeDivergence(sources: {
|
|
9
|
+
twitter?: SourceStats;
|
|
10
|
+
reddit?: SourceStats;
|
|
11
|
+
web?: SourceStats;
|
|
12
|
+
finnhub?: SourceStats;
|
|
13
|
+
}, threshold: number): DivergenceResult;
|