Skip to content

bomzj/news-chart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

News Chart

Maps news events to price candlesticks to display market-response correlation. Crypto news analysis pipeline that ingests news, deduplicates via semantic vectors, analyzes sentiment with AI agents, snapshots price, and tracks realized price movements over time. NOTE: This MVP is 100% vibe-coded and support BTC only.

What It Does

  1. Ingests news from MarketAux API for configured crypto tickers (e.g. BTC)
  2. Deduplicates using cosine similarity on embeddings β€” both within the current batch and against the last 24h in the vector DB
  3. Analyzes & filters via a LangGraph StateGraph (conditional routing):
    • Junior analyst (GPT-5 Nano) evaluates each news item in parallel
    • Noise/unimportant news is discarded (never stored) β€” only price-driving news is kept
    • If confidence < threshold β†’ graph routes to Senior analyst (GPT-5 Mini)
  4. Snapshots price from Binance perpetual futures at ingestion time
  5. Stores enriched vectors in Qdrant Cloud with full metadata
  6. Backfills realized price deltas (1h, 24h, 7d, 30d) via a separate cron-triggered endpoint

Tech Stack

Component Technology
API Framework FastAPI
Agent Orchestration LangGraph
Vector Database Qdrant Cloud
LLM Provider Azure AI (GPT-5 Nano / Mini)
Embeddings Azure AI text-embedding-3-large (256 dims)
Price Data Binance USDβ“ˆ-M Futures API
News Source MarketAux API
Hosting Render (web service + cron jobs)
Language Python 3.14
Toolchain uv

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Render Platform                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                         β”‚
β”‚  β”‚ Cron: 10 min │────▢│ POST         β”‚                         β”‚
β”‚  β”‚ read-news    β”‚     β”‚ /api/read-newsβ”‚                         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜                         β”‚
β”‚                              β”‚                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”‚
β”‚  β”‚ Cron: 15 min │────▢│ POST          β”‚                        β”‚
β”‚  β”‚ update-pricesβ”‚     β”‚/api/update-   β”‚                        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚    prices     β”‚                        β”‚
β”‚                       β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
β”‚                              β”‚                                  β”‚
β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚         β”‚            FastAPI App                   β”‚            β”‚
β”‚         β”‚                                         β”‚            β”‚
β”‚         β”‚  news_pipeline/    β”‚   price_updater/   β”‚            β”‚
β”‚         β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚            β”‚
β”‚         β”‚  β”‚ fetch_news  β”‚   β”‚   β”‚ updater     β”‚  β”‚            β”‚
β”‚         β”‚  β”‚ dedup       β”‚   β”‚   β”‚ (delta calc)β”‚  β”‚            β”‚
β”‚         β”‚  β”‚ graph (LG)  β”‚   β”‚   β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β”‚            β”‚
β”‚         β”‚  β”‚ agents      β”‚   β”‚          β”‚         β”‚            β”‚
β”‚         β”‚  β”‚ store       β”‚   β”‚          β”‚         β”‚            β”‚
β”‚         β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜   β”‚          β”‚         β”‚            β”‚
β”‚         β”‚         β”‚          β”‚          β”‚         β”‚            β”‚
β”‚         β”‚  shared/β”‚          β”‚          β”‚         β”‚            β”‚
β”‚         β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”  β”‚            β”‚
β”‚         β”‚  β”‚ qdrant.py  embeddings.py  binance β”‚  β”‚            β”‚
β”‚         β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚            β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                    β”‚                β”‚
         β–Ό                    β–Ό                β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Qdrant Cloudβ”‚    β”‚  Azure AI    β”‚  β”‚  Binance    β”‚
  β”‚ (vectors +  β”‚    β”‚  (embed +    β”‚  β”‚  Futures    β”‚
  β”‚  metadata)  β”‚    β”‚   LLM)       β”‚  β”‚  API        β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β–²
         β”‚
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ MarketAux   β”‚
  β”‚ (news feed) β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Pipeline Flows

/api/read-news (every 10 minutes)

MarketAux API ──▢ Raw news articles (filtered by ticker)
       β”‚
       β–Ό
Extract full text from article URLs (trafilatura)
       β”‚
       β–Ό
Skip articles with unavailable URLs (403, 404, timeout β†’ dropped from pipeline)
       β”‚
       β–Ό
Condense oversized articles (>2000 chars) via GPT-5 Nano summarization
       β”‚
       β–Ό
Embed all articles (Azure AI batch) ──▢ 256-dim vectors
       β”‚
       β–Ό
Intra-batch dedup (pairwise cosine β‰₯ 0.90 β†’ keep first)
       β”‚
       β–Ό
RAG dedup vs Qdrant (last 24h, cosine β‰₯ 0.90 β†’ discard)
       β”‚
       β–Ό
Attach similar past news as context (with realized price data)
       β”‚
       β–Ό
LangGraph StateGraph (graph.py):
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ START β†’ junior_analyst (GPT-5 Nano)                     β”‚
  β”‚           β”‚                                             β”‚
  β”‚           β”œβ”€β”€ discard=true β†’ END (news dropped)         β”‚
  β”‚           β”œβ”€β”€ confidence β‰₯ 0.75 β†’ END (result kept)     β”‚
  β”‚           └── confidence < 0.75 β†’ senior_analyst        β”‚
  β”‚                                    (GPT-5 Mini) β†’ END   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  Batch: asyncio.gather over graph.ainvoke() per news item
       β”‚
       β–Ό
Fetch Binance mark price (BTCUSDT perpetual) β€” only for kept news
       β”‚
       β–Ό
Batch upsert to Qdrant (vector + full metadata payload)

/api/update-prices (every 15 minutes)

Query Qdrant: news with null realized_price_delta fields
  where published_at is old enough for each delta window
       β”‚
       β–Ό
Fetch historical prices from Binance klines at:
  published_at + 1h, +24h, +7d, +30d
       β”‚
       β–Ό
Calculate: (historical_price - price_at_ingestion) / price_at_ingestion
       β”‚
       β–Ό
Batch update Qdrant payloads with realized deltas

Data Model (Qdrant Payload)

{
  "ticker": "BTC",
  "source": "coindesk",
  "published_at": "2026-05-21T18:30:00Z",
  "news_summary": "The FED cuts rates by 50 bps, signaling aggressive easing...",
  "news_full_text": "Full article text...",
  "sentiment": "bullish",
  "impact": 3,
  "confidence": 0.78,
  "predicted_by_model": "gpt-5.4-nano",
  "price_at_ingestion": 68250.00,
  "realized_price_delta_pct_1h": null,
  "realized_price_delta_pct_24h": null,
  "realized_price_delta_pct_7d": null,
  "realized_price_delta_pct_30d": null
}

Sentiment: Binary β€” "bullish" (positive price pressure) or "bearish" (negative price pressure). No neutral β€” if news can't clearly drive price in either direction, it's discarded as noise and never stored.

Impact Scale (1-3):

Level Name Timeframe Examples
1 Notable Moves price for hours Coin listed on major exchange (Binance, Coinbase) Β· Significant token burn Β· Large whale movement Β· Successful protocol upgrade Β· Major partnership
2 High Moves price for days ETF approval/rejection Β· Major hack/exploit ($100M+) Β· Country bans/legalizes crypto Β· Institutional entry/exit (BlackRock, Fidelity) Β· Exchange withdrawal freeze
3 Extreme Moves entire market for weeks Fed rate decision Β· Major stablecoin depeg Β· Exchange collapse (FTX-level) Β· International regulatory crackdown Β· Global banking crisis

Configuration

config.yaml β€” App Settings

All tunable parameters live here. No restart needed for threshold changes (reload on next request).

tickers: [BTC]

dedup:
  cosine_threshold: 0.90    # similarity score to consider as duplicate
  lookback_hours: 24        # how far back to check for duplicates

agents:
  confidence_threshold: 0.75  # below this β†’ escalate to senior
  nano_deployment: gpt-5.4-nano
  mini_deployment: gpt-5.4-mini
  api_version: "2025-04-01-preview"
  reasoning_effort: high

embeddings:
  deployment: text-embedding-3-large
  dimensions: 256
  api_version: "2023-05-15"

pipeline:
  fetch_news_interval_minutes: 10
  max_full_text_chars: 2000    # articles longer than this get LLM-summarized

price_updater:
  deltas: [1h, 24h, 7d, 30d]

Environment Variables β€” Secrets Only

MARKETAUX_API_KEY=...
QDRANT_URL=https://your-cluster.cloud.qdrant.io
QDRANT_API_KEY=...
AZURE_AI_ENDPOINT=https://your-resource.cognitiveservices.azure.com
AZURE_AI_API_KEY=...

Local Development

Backend

cd backend

# Install dependencies with uv
uv sync

# Copy and fill in secrets
cp .env.example .env

# Run the API server
uv run start

# Trigger pipelines manually
curl -X POST http://localhost:8000/api/read-news
curl -X POST http://localhost:8000/api/update-prices

# Run tests
uv run pytest

Frontend

cd frontend

# Install dependencies
npm install

# Copy env (defaults to localhost:8000 backend)
cp .env.example .env.local

# Run dev server (http://localhost:3000)
npm run dev

# Build static export
npm run build

Frontend

Tech Stack

Component Technology
Framework Next.js 16 (App Router, static export)
Language TypeScript
Charts TradingView Lightweight Charts v5
Styling Tailwind CSS v4
Price Data Binance USDβ“ˆ-M Futures REST API (client-side)
News Data Backend FastAPI /api/news endpoint

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Browser (SPA)                          β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Toolbar: [Ticker] [1h|4h|1d|1w|1M] [Impact ━━━━]  β”‚  β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚
β”‚  β”‚                                                    β”‚  β”‚
β”‚  β”‚         TradingView Lightweight Chart              β”‚  β”‚
β”‚  β”‚         (candlestick + news markers)               β”‚  β”‚
β”‚  β”‚                                                    β”‚  β”‚
β”‚  β”‚              ● ●          ●    ●                   β”‚  β”‚
β”‚  β”‚         β•‘β•‘  β•‘β•‘ β•‘β•‘    β•‘β•‘  β•‘β•‘  β•‘β•‘ β•‘β•‘                 β”‚  β”‚
β”‚  β”‚        ●║║  β•‘β•‘ β•‘β•‘   ●║║  β•‘β•‘  β•‘β•‘ β•‘β•‘                 β”‚  β”‚
β”‚  β”‚                                                    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                β”‚
β”‚  β”‚ Popup (on click):    β”‚                                β”‚
β”‚  β”‚ β€’ 🟒 BTC surges...   β”‚                                β”‚
β”‚  β”‚ β€’ πŸ”΄ SEC warns...    β”‚                                β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                         β”‚
         β–Ό                         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Binance      β”‚         β”‚ FastAPI      β”‚
  β”‚ fapi/v1/     β”‚         β”‚ /api/news    β”‚
  β”‚ klines       β”‚         β”‚ (β†’ Qdrant)   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Flow

  1. User selects ticker (e.g., BTCUSDT) and timeframe (e.g., 1d)
  2. Frontend fetches klines directly from Binance Futures public API
  3. Chart renders candlesticks and subscribes to visible range changes
  4. When visible range changes (scroll/zoom), frontend fetches news from backend for that time window
  5. News items are aggregated per candle:
    • Color = sentiment (red = bearish, green = bullish)
    • Size = impact (1 = smallest circle, 3 = largest)
  6. Impact slider filters out news below the threshold (client-side re-aggregation)
  7. Click on a marker shows popup with news items sorted by impact (most impactful first)

Frontend API Endpoint

GET /api/news?ticker=BTC&from_ts=2026-01-01T00:00:00Z&to_ts=2026-05-28T00:00:00Z&min_impact=2

Response:

{
  "items": [
    {
      "published_at": "2026-05-21T18:30:00Z",
      "sentiment": "bullish",
      "impact": 3,
      "news_summary": "BTC ETF inflows hit record...",
      "confidence": 0.82,
      "predicted_by_model": "gpt-5.4-nano",
      "price_at_ingestion": 68250.00
    }
  ],
  "count": 42
}

Frontend Local Development

# Navigate to frontend
cd frontend

# Install dependencies
npm install

# Copy env file and configure API URL
cp .env.example .env.local

# Run dev server
npm run dev
# β†’ http://localhost:3000

# Build for production (static export)
npm run build
# Output: frontend/out/

Key Files

frontend/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ page.tsx          # Main page (ticker, timeframe, impact state)
β”‚   β”‚   β”œβ”€β”€ layout.tsx        # Root layout (light theme, monospace font)
β”‚   β”‚   └── globals.css       # Tailwind + CSS variables
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ Chart.tsx         # TradingView chart + markers + range subscription
β”‚   β”‚   β”œβ”€β”€ Toolbar.tsx       # Ticker input, timeframe buttons, impact slider
β”‚   β”‚   └── NewsPopup.tsx     # Hover/click popup with news bullet list
β”‚   └── lib/
β”‚       β”œβ”€β”€ types.ts          # Shared TypeScript types
β”‚       β”œβ”€β”€ klines.ts         # Binance futures klines fetcher
β”‚       β”œβ”€β”€ news.ts           # Backend news API fetcher
β”‚       └── aggregate.ts      # News aggregation, sentiment colors, impact sizing
β”œβ”€β”€ next.config.ts            # Static export config
β”œβ”€β”€ .env.example              # NEXT_PUBLIC_API_URL template
└── package.json

Deployment (Render)

The render.yaml at the repo root defines:

  • Web service: FastAPI app serving pipeline + chart API endpoints
  • Static site: Next.js frontend (static export to out/)
  • Cron job (15 min): hits /api/read-news
  • Cron job (60 min): hits /api/update-prices

Set all environment variables in the Render dashboard:

  • Backend: MARKETAUX_API_KEY, QDRANT_URL, QDRANT_API_KEY, AZURE_AI_ENDPOINT, AZURE_AI_API_KEY
  • Frontend: NEXT_PUBLIC_API_URL (set to the backend service's external URL)

The backend reads config.yaml from the repo for non-secret settings.

About

Visualize the exact moment news impacts price action, right on the chart.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors