{"id":48071782,"url":"https://github.com/snowfluke/sentimeter","last_synced_at":"2026-04-04T14:43:40.095Z","repository":{"id":336053385,"uuid":"1148062689","full_name":"snowfluke/sentimeter","owner":"snowfluke","description":"A sentiment-based stock trading bot for the Indonesian Stock Exchange (IHSG/IDX). Sentimeter crawls financial news from Indonesian portals, extracts stock tickers using AI, fetches market data, and generates daily buy recommendations with entry prices, stop losses, and target prices.","archived":false,"fork":false,"pushed_at":"2026-03-05T03:16:24.000Z","size":1659,"stargazers_count":11,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-05T08:37:15.802Z","etag":null,"topics":["ai","antigravity","fundamental","gemini","idx","ihsg","sentiment","stockpick","technical","trading"],"latest_commit_sha":null,"homepage":"https://sentimeter.my.id","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/snowfluke.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["snowfluke","amaruki"]}},"created_at":"2026-02-02T14:34:46.000Z","updated_at":"2026-03-05T03:16:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/snowfluke/sentimeter","commit_stats":null,"previous_names":["snowfluke/sentimeter"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/snowfluke/sentimeter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowfluke%2Fsentimeter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowfluke%2Fsentimeter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowfluke%2Fsentimeter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowfluke%2Fsentimeter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/snowfluke","download_url":"https://codeload.github.com/snowfluke/sentimeter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowfluke%2Fsentimeter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31403459,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","antigravity","fundamental","gemini","idx","ihsg","sentiment","stockpick","technical","trading"],"created_at":"2026-04-04T14:43:39.955Z","updated_at":"2026-04-04T14:43:40.075Z","avatar_url":"https://github.com/snowfluke.png","language":"TypeScript","readme":"# Sentimeter\n\nA sentiment-based stock trading bot for the Indonesian Stock Exchange (IHSG/IDX). Sentimeter crawls financial news from Indonesian portals, extracts stock tickers using AI, fetches market data, and generates daily buy recommendations with entry prices, stop losses, and target prices.\n\n![Sentimeter 1](/assets/sentimeter-1.png)\n![Sentimeter 2](/assets/sentimeter-2.png)\n\n## Table of Contents\n\n- [Features](#features)\n- [Architecture](#architecture)\n- [Prerequisites](#prerequisites)\n- [Installation](#installation)\n- [Configuration](#configuration)\n- [Usage](#usage)\n- [API Reference](#api-reference)\n- [Project Structure](#project-structure)\n- [How It Works](#how-it-works)\n- [Development](#development)\n\n## Features\n\n- News Crawling: Scrapes 14 Indonesian financial news portals (Kontan, Bisnis, CNBC Indonesia, etc.)\n- AI-Powered Analysis: Uses Google Gemini to extract stock tickers and analyze sentiment\n- Market Data: Fetches real-time quotes and historical prices from Yahoo Finance\n- Technical Analysis: Calculates SMA, ATR, support/resistance levels, and trend detection\n- Recommendation Engine: Generates buy signals with entry price, stop loss, target price, and max hold days\n- Prediction Tracking: Monitors active positions and updates status (pending, entry_hit, target_hit, sl_hit, expired)\n- Scheduled Jobs: Runs automatically twice daily (morning before market open, evening after close)\n- REST API: Exposes recommendations and history via HTTP endpoints\n- React Dashboard: Professional UI for viewing recommendations and tracking performance\n- Live Log Streaming: Real-time analysis progress via Server-Sent Events (SSE)\n- Scheduler Control: Toggle auto-scheduler on/off from the dashboard\n- Docker Support: Multi-stage Dockerfile for production deployment\n\n## Architecture\n\n```\n                                    +------------------+\n                                    |   React Frontend |\n                                    |   (port 3000)    |\n                                    +--------+---------+\n                                             |\n                                             v\n+------------------+              +----------+---------+\n|  News Portals    |              |    REST API        |\n|  (14 sources)    +-------------\u003e|    (port 3001)     |\n+------------------+              +----------+---------+\n                                             |\n        +------------------------------------+------------------------------------+\n        |                    |                    |                    |          |\n        v                    v                    v                    v          v\n+-------+------+    +--------+-------+    +------+-------+    +-------+------+   |\n|   Crawler    |    |    Analyzer    |    |  Market Data |    |   Prediction |   |\n|              |    |    (Gemini)    |    | (Yahoo Fin)  |    |    Tracker   |   |\n+--------------+    +----------------+    +--------------+    +--------------+   |\n                                                                                 |\n                                          +--------------------------------------+\n                                          |\n                                          v\n                                  +-------+--------+\n                                  |    SQLite      |\n                                  |   Database     |\n                                  +----------------+\n```\n\n## Analysis Strategy\n\nThe analysis pipeline runs in 5 sequential steps:\n\n```\n+==============================================================================+\n|                          STEP 1: NEWS CRAWLING                               |\n+==============================================================================+\n|                                                                              |\n|  +-------------+  +-------------+  +-------------+       +-------------+     |\n|  |    CNBC     |  |   Bisnis    |  |   Kontan    |  ...  |  IDX Channel|     |\n|  |  Indonesia  |  |   Market    |  |             |       |             |     |\n|  +------+------+  +------+------+  +------+------+       +------+------+     |\n|         |                |                |                     |            |\n|         v                v                v                     v            |\n|  +------+------+  +------+------+  +------+------+       +------+------+     |\n|  | Fetch HTML  |  | Fetch HTML  |  | Fetch HTML  |       | Fetch HTML  |     |\n|  | Parse Links |  | Parse Links |  | Parse Links |       | Parse Links |     |\n|  +------+------+  +------+------+  +------+------+       +------+------+     |\n|         |                |                |                     |            |\n|         +----------------+----------------+---------------------+            |\n|                                    |                                         |\n|                                    v                                         |\n|                          +---------+---------+                               |\n|                          | Deduplicate by    |                               |\n|                          | Content Hash      |                               |\n|                          +---------+---------+                               |\n|                                    |                                         |\n|                                    v                                         |\n|                          +---------+---------+                               |\n|                          | Save to SQLite    |                               |\n|                          | (news_articles)   |                               |\n|                          +-------------------+                               |\n|                                                                              |\n+==============================================================================+\n                                     |\n                                     v\n+==============================================================================+\n|                       STEP 2: TICKER EXTRACTION                              |\n+==============================================================================+\n|                                                                              |\n|  +-----------------+                                                         |\n|  | Load Recent     |     Filter: Last 24 hours                               |\n|  | News Articles   |     Limit: 200 chars content                            |\n|  +--------+--------+                                                         |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+--------+                                                         |\n|  | Batch Articles  |     10 articles per batch                               |\n|  | for LLM         |     (avoid quota limits)                                |\n|  +--------+--------+                                                         |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+-------------------+                                              |\n|  |     Gemini LLM Prompt      |                                              |\n|  |-----------------------------|                                             |\n|  | \"Extract Indonesian stock   |                                             |\n|  |  tickers (4-letter codes)   |                                             |\n|  |  with sentiment \u0026 relevance\"|                                             |\n|  +--------+-------------------+                                              |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+--------+                                                         |\n|  | JSON Response   |     { ticker, sentiment, relevance, reason }            |\n|  | Parse \u0026 Merge   |                                                         |\n|  +--------+--------+                                                         |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+--------+                                                         |\n|  | Aggregate by    |     Combine duplicates, avg sentiment                   |\n|  | Ticker Symbol   |                                                         |\n|  +-----------------+                                                         |\n|                                                                              |\n+==============================================================================+\n                                     |\n                                     v\n+==============================================================================+\n|                       STEP 3: FILTER TOP TICKERS                             |\n+==============================================================================+\n|                                                                              |\n|  +-------------------+                                                       |\n|  | All Extracted     |     Example: 50+ tickers                              |\n|  | Tickers           |                                                       |\n|  +--------+----------+                                                       |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+----------+                                                       |\n|  | Filter:           |     sentiment \u003e 0.2 (positive bias)                   |\n|  | Positive Sentiment|                                                       |\n|  +--------+----------+                                                       |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+----------+                                                       |\n|  | Sort by:          |     1. Relevance (desc)                               |\n|  | Relevance + Sent  |     2. Sentiment (desc)                               |\n|  +--------+----------+                                                       |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+----------+                                                       |\n|  | Take Top 10       |     Example: BBCA, TLKM, BMRI...                      |\n|  | Tickers           |                                                       |\n|  +-------------------+                                                       |\n|                                                                              |\n+==============================================================================+\n                                     |\n                                     v\n+==============================================================================+\n|                    STEP 4: MARKET DATA \u0026 ANALYSIS                            |\n+==============================================================================+\n|                                                                              |\n|  For each ticker (max 10):                                                   |\n|                                                                              |\n|  +-------------------+     +-------------------+     +-------------------+    |\n|  | Yahoo Finance     |     | Yahoo Finance     |     | Yahoo Finance     |   |\n|  | Quote API         |     | Fundamentals      |     | Historical Prices |   |\n|  | (.JK suffix)      |     | (P/E, P/B, ROE)   |     | (3 months OHLCV)  |   |\n|  +--------+----------+     +--------+----------+     +--------+----------+   |\n|           |                         |                         |              |\n|           +-------------------------+-------------------------+              |\n|                                     |                                        |\n|                                     v                                        |\n|                    +----------------+----------------+                       |\n|                    |     Technical Analysis          |                       |\n|                    |----------------------------------|                      |\n|                    | - SMA 20, 50, 200               |                       |\n|                    | - ATR 14 (volatility)           |                       |\n|                    | - Support/Resistance levels     |                       |\n|                    | - Trend direction               |                       |\n|                    +----------------+----------------+                       |\n|                                     |                                        |\n|                                     v                                        |\n|                    +----------------+----------------+                       |\n|                    |     Gemini LLM Analysis         |                       |\n|                    |----------------------------------|                      |\n|                    | Input:                          |                       |\n|                    | - News sentiment                |                       |\n|                    | - Fundamentals (P/E, ROE, etc) |                       |\n|                    | - Technical indicators          |                       |\n|                    | - Current price \u0026 trend         |                       |\n|                    |                                  |                       |\n|                    | Output:                          |                       |\n|                    | - Action: BUY / HOLD / AVOID    |                       |\n|                    | - Entry Price                   |                       |\n|                    | - Stop Loss                     |                       |\n|                    | - Target Price                  |                       |\n|                    | - Max Hold Days                 |                       |\n|                    | - Scores (0-100 each)           |                       |\n|                    +----------------+----------------+                       |\n|                                     |                                        |\n|                                     v                                        |\n|                    +----------------+----------------+                       |\n|                    | Filter: Score \u003e= 65 \u0026 BUY       |                       |\n|                    +----------------+----------------+                       |\n|                                     |                                        |\n|                                     v                                        |\n|                    +----------------+----------------+                       |\n|                    | Save to SQLite                  |                       |\n|                    | (recommendations table)         |                       |\n|                    | Max 5 per run                   |                       |\n|                    +---------------------------------+                       |\n|                                                                              |\n+==============================================================================+\n                                     |\n                                     v\n+==============================================================================+\n|                    STEP 5: UPDATE PREDICTIONS                                |\n+==============================================================================+\n|                                                                              |\n|  +-------------------+                                                       |\n|  | Load Active       |     status = 'pending' OR 'entry_hit'                 |\n|  | Predictions       |                                                       |\n|  +--------+----------+                                                       |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+----------+                                                       |\n|  | Fetch Current     |     Yahoo Finance real-time quote                     |\n|  | Price for Each    |                                                       |\n|  +--------+----------+                                                       |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+-------------------------------+                                  |\n|  |           Status Check Logic           |                                  |\n|  |-----------------------------------------|                                 |\n|  |                                         |                                 |\n|  |  pending:                               |                                 |\n|  |    price \u003c= entry  --\u003e  entry_hit       |                                 |\n|  |                                         |                                 |\n|  |  entry_hit:                             |                                 |\n|  |    price \u003e= target --\u003e  target_hit      |                                 |\n|  |    price \u003c= stop   --\u003e  sl_hit          |                                 |\n|  |    days \u003e max      --\u003e  expired         |                                 |\n|  |                                         |                                 |\n|  +--------+-------------------------------+                                  |\n|           |                                                                  |\n|           v                                                                  |\n|  +--------+----------+                                                       |\n|  | Update SQLite     |     Record exit_date, exit_price, final status       |\n|  | (recommendations) |                                                       |\n|  +-------------------+                                                       |\n|                                                                              |\n+==============================================================================+\n                                     |\n                                     v\n                          +----------+----------+\n                          |   JOB COMPLETE      |\n                          |---------------------|\n                          | - Articles crawled  |\n                          | - Tickers extracted |\n                          | - Recommendations   |\n                          | - Predictions updated|\n                          +---------------------+\n```\n\n## Prerequisites\n\n- Bun v1.0 or later (https://bun.sh)\n- Antigravity Manager running locally (OpenAI-compatible LLM proxy)\n\n## Installation\n\n1. Clone the repository:\n\n```bash\ngit clone https://github.com/yourusername/sentimeter.git\ncd sentimeter\n```\n\n2. Install backend dependencies:\n\n```bash\nbun install\n```\n\n3. Install frontend dependencies:\n\n```bash\nbun install --cwd web\n```\n\n4. Initialize the database:\n\n```bash\nbun run src/lib/database/migrate.ts\n```\n\n5. Start Antigravity Manager:\n\nhttps://github.com/lbjlaq/Antigravity-Manager\n\n## Configuration\n\nCreate a `.env` file in the project root:\n\n```env\n# Antigravity Manager (OpenAI-compatible proxy)\nANTIGRAVITY_BASE_URL=http://127.0.0.1:8045\nANTIGRAVITY_API_KEY=your_antigravity_api_key_here\nANTIGRAVITY_MODEL=gemini-3-flash\n\n# Optional: API server port (default: 3001)\nPORT=3001\n```\n\n### Antigravity Manager Setup\n\nSentimeter uses Antigravity Manager as an LLM proxy to avoid quota issues with direct API calls. The proxy provides an OpenAI-compatible API that routes requests to Gemini models.\n\n1. Install and start Antigravity Manager (from Google's Antigravity IDE)\n2. Get your API key from the Antigravity Manager dashboard\n3. Configure the environment variables above\n4. Verify connection:\n\n```bash\n# Test the LLM client\nbun test src/lib/analyzer/llm-client.test.ts\n```\n\n## Usage\n\n### Running the API Server\n\n```bash\nbun run src/api/index.ts\n```\n\nThe API will be available at http://localhost:3001\n\n### Running the Frontend\n\n```bash\ncd web\nbun run dev\n```\n\nThe dashboard will be available at http://localhost:3000\n\n### Running Daily Analysis Manually\n\n```bash\nbun run src/jobs/daily-analysis.ts\n```\n\nThis will:\n\n1. Crawl all news portals\n2. Extract tickers using Gemini AI\n3. Fetch market data from Yahoo Finance\n4. Generate recommendations\n5. Update existing prediction statuses\n\n### Running the Scheduler\n\n```bash\nbun run src/jobs/scheduler.ts\n```\n\nThe scheduler runs the analysis automatically at:\n\n- Morning: 7:30 WIB (before market open)\n- Evening: 15:30 WIB (after market close)\n\n## API Reference\n\n### GET /health\n\nHealth check endpoint.\n\nResponse:\n\n```json\n{\n  \"status\": \"ok\",\n  \"service\": \"sentimeter\",\n  \"timestamp\": \"2024-01-15T10:30:00.000Z\"\n}\n```\n\n### GET /api/recommendations\n\nGet today's stock recommendations and active positions.\n\nQuery Parameters:\n\n- date (optional): Specific date in YYYY-MM-DD format\n\nResponse:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"date\": \"2024-01-15\",\n    \"schedule\": \"morning\",\n    \"generatedAt\": \"2024-01-15T07:30:00.000Z\",\n    \"recommendations\": [\n      {\n        \"ticker\": \"BBCA\",\n        \"companyName\": \"Bank Central Asia Tbk\",\n        \"action\": \"BUY\",\n        \"entryPrice\": 9500,\n        \"stopLoss\": 9200,\n        \"targetPrice\": 10200,\n        \"maxHoldDays\": 14,\n        \"overallScore\": 78.5,\n        \"analysisSummary\": \"Strong earnings beat with positive sentiment...\"\n      }\n    ],\n    \"activePositions\": [],\n    \"summary\": {\n      \"totalActive\": 3,\n      \"totalPending\": 2,\n      \"winRate\": 65.5,\n      \"avgReturn\": 4.2\n    }\n  }\n}\n```\n\n### GET /api/history\n\nGet historical recommendations with pagination and filters.\n\nQuery Parameters:\n\n- page (default: 1)\n- pageSize (default: 20, max: 100)\n- ticker (optional): Filter by ticker\n- status (optional): pending, entry_hit, target_hit, sl_hit, expired\n- startDate (optional): YYYY-MM-DD\n- endDate (optional): YYYY-MM-DD\n\nResponse:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"items\": [],\n    \"pagination\": {\n      \"page\": 1,\n      \"pageSize\": 20,\n      \"total\": 150,\n      \"totalPages\": 8\n    },\n    \"stats\": {\n      \"totalRecommendations\": 150,\n      \"winRate\": 62.3,\n      \"avgReturn\": 3.8,\n      \"bestPick\": { \"ticker\": \"TLKM\", \"returnPct\": 15.2 },\n      \"worstPick\": { \"ticker\": \"ASII\", \"returnPct\": -8.1 }\n    }\n  }\n}\n```\n\n### POST /api/refresh\n\nTrigger a manual analysis refresh.\n\nResponse:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"triggered\": true,\n    \"schedule\": \"morning\",\n    \"jobId\": 42,\n    \"message\": \"morning analysis triggered. Job ID: 42.\"\n  }\n}\n```\n\n## Project Structure\n\n```\nsentimeter/\n├── src/\n│   ├── api/                    # REST API server\n│   │   ├── index.ts            # Server entry point\n│   │   ├── types.ts            # API response types\n│   │   ├── middleware/\n│   │   │   └── cors.ts         # CORS handling\n│   │   └── routes/\n│   │       ├── recommendations.ts\n│   │       ├── history.ts\n│   │       └── refresh.ts\n│   │\n│   ├── jobs/                   # Scheduled jobs\n│   │   ├── daily-analysis.ts   # Main analysis pipeline\n│   │   └── scheduler.ts        # Cron-like scheduler\n│   │\n│   └── lib/                    # Core libraries\n│       ├── analyzer/           # Gemini LLM integration\n│       │   ├── types.ts\n│       │   ├── gemini-client.ts\n│       │   ├── ticker-extractor.ts\n│       │   └── stock-analyzer.ts\n│       │\n│       ├── crawler/            # News scraping\n│       │   ├── types.ts\n│       │   ├── portal-configs.ts\n│       │   ├── fetcher.ts\n│       │   ├── parser.ts\n│       │   ├── deduplicator.ts\n│       │   └── orchestrator.ts\n│       │\n│       ├── database/           # SQLite with Bun\n│       │   ├── types.ts\n│       │   ├── schema.ts\n│       │   ├── queries.ts\n│       │   └── migrate.ts\n│       │\n│       ├── market-data/        # Yahoo Finance\n│       │   ├── types.ts\n│       │   ├── yahoo-client.ts\n│       │   ├── fundamental.ts\n│       │   └── technical.ts\n│       │\n│       └── prediction-tracker/ # Position tracking\n│           ├── types.ts\n│           ├── status-checker.ts\n│           └── updater.ts\n│\n├── web/                        # React frontend\n│   ├── src/\n│   │   ├── components/         # UI components\n│   │   ├── pages/              # Dashboard, History\n│   │   ├── lib/                # API client, hooks\n│   │   └── App.tsx\n│   ├── package.json\n│   └── vite.config.ts\n│\n├── data/                       # SQLite database files\n├── .env                        # Environment variables\n├── package.json\n└── tsconfig.json\n```\n\n## How It Works\n\n### 1. News Crawling\n\nThe crawler fetches articles from 14 Indonesian financial news portals:\n\n- Kontan, Bisnis Indonesia, CNBC Indonesia\n- Detik Finance, Kompas Money, Liputan6 Bisnis\n- Tempo Bisnis, Tribun Bisnis, Okezone Economy\n- IDN Times Business, Kumparan Bisnis\n- Investor Daily, Market Bisnis, IDX Channel\n\nEach portal has custom CSS selectors for extracting article titles, content, and dates. The crawler uses rate limiting (1.5-2s delays) to avoid being blocked.\n\n### 2. Ticker Extraction\n\nArticles are sent to Google Gemini with a prompt asking it to:\n\n- Extract Indonesian stock tickers (4-letter codes like BBCA, TLKM)\n- Rate sentiment from -1 (bearish) to 1 (bullish)\n- Rate relevance from 0 to 1\n- Provide reasoning for each ticker\n\n### 3. Market Data Fetching\n\nFor each extracted ticker, the system fetches:\n\n- Quote: Current price, volume, change\n- Fundamentals: P/E, P/B, ROE, debt-to-equity, dividend yield\n- Price History: 3 months of OHLCV data\n\nIndonesian stocks use the .JK suffix on Yahoo Finance (e.g., BBCA.JK).\n\n### 4. Technical Analysis\n\nFrom price history, the system calculates:\n\n- Simple Moving Averages (SMA 20, 50, 200)\n- Average True Range (ATR 14)\n- Support and resistance levels\n- Trend direction and strength\n\n### 5. Recommendation Generation\n\nGemini analyzes all data and generates:\n\n- Action: BUY, HOLD, or AVOID\n- Entry Price: Suggested buy price\n- Stop Loss: Maximum loss threshold (typically 3-5% below entry)\n- Target Price: Profit target (typically 5-10% above entry)\n- Max Hold Days: Position duration limit (typically 7-21 days)\n- Scores: Sentiment (0-100), Fundamental (0-100), Technical (0-100), Overall (0-100)\n\nOnly recommendations with overall score \u003e= 65 and action = BUY are saved.\n\n### 6. Prediction Tracking\n\nActive predictions are monitored for status changes:\n\n- pending: Waiting for price to hit entry level\n- entry_hit: Position opened, monitoring for target/stop\n- target_hit: Target price reached, closed with profit\n- sl_hit: Stop loss hit, closed with loss\n- expired: Max hold days exceeded, closed at market\n\n## Development\n\n### Type Checking\n\n```bash\n# Backend\nbunx tsc --noEmit\n\n# Frontend\nbunx tsc --noEmit --project web/tsconfig.json\n\n```\n## Production Deployment\n\n```bash\nbun run build\n\npm2 start ecosystem.config.cjs --env production\npm2 save\n```\n\n### Adding a New News Portal\n\n1. Add configuration to src/lib/crawler/portal-configs.ts:\n\n```typescript\n{\n  name: \"portal-name\",\n  baseUrl: \"https://example.com/finance\",\n  articleLinkSelector: \"a.article-link\",\n  titleSelector: \"h1.title\",\n  contentSelector: \"div.article-body\",\n  dateSelector: \"time.published\",\n  removeSelectors: [\".ads\", \".related\"],\n  delayMs: 2000,\n}\n```\n\n2. Add parser logic if needed in src/lib/crawler/parser.ts.\n\n### Database Schema\n\nThe SQLite database contains these tables:\n\n- news_articles: Crawled news with content hash for deduplication\n- news_tickers: Extracted tickers with sentiment scores\n- recommendations: Generated buy signals with price targets\n- stock_fundamentals: Cached company data\n- price_history: Historical OHLCV data\n- job_executions: Job run history and status\n\n### Environment Variables\n\n| Variable             | Required | Description                                                     |\n| -------------------- | -------- | --------------------------------------------------------------- |\n| ANTIGRAVITY_BASE_URL | Yes      | Antigravity Manager API URL (default: http://127.0.0.1:8045/v1) |\n| ANTIGRAVITY_API_KEY  | Yes      | Antigravity Manager API key                                     |\n| ANTIGRAVITY_MODEL    | No       | Model to use (default: gemini-2.0-flash)                        |\n| PORT                 | No       | API server port (default: 3001)                                 |\n\n## Disclaimer\n\nThis software is for educational purposes only. Stock trading involves significant risk of loss. The recommendations generated by this system should not be considered financial advice. Always do your own research and consult with a qualified financial advisor before making investment decisions.\n\n## License\n\nMIT\n","funding_links":["https://github.com/sponsors/snowfluke","https://github.com/sponsors/amaruki"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowfluke%2Fsentimeter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsnowfluke%2Fsentimeter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowfluke%2Fsentimeter/lists"}