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.
- Ingests news from MarketAux API for configured crypto tickers (e.g. BTC)
- Deduplicates using cosine similarity on embeddings β both within the current batch and against the last 24h in the vector DB
- 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)
- Snapshots price from Binance perpetual futures at ingestion time
- Stores enriched vectors in Qdrant Cloud with full metadata
- Backfills realized price deltas (1h, 24h, 7d, 30d) via a separate cron-triggered endpoint
| 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 |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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) β
βββββββββββββββ
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)
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
{
"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 |
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]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=...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 pytestcd 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| 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 |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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) β
ββββββββββββββββ ββββββββββββββββ
- User selects ticker (e.g.,
BTCUSDT) and timeframe (e.g.,1d) - Frontend fetches klines directly from Binance Futures public API
- Chart renders candlesticks and subscribes to visible range changes
- When visible range changes (scroll/zoom), frontend fetches news from backend for that time window
- News items are aggregated per candle:
- Color = sentiment (red = bearish, green = bullish)
- Size = impact (1 = smallest circle, 3 = largest)
- Impact slider filters out news below the threshold (client-side re-aggregation)
- Click on a marker shows popup with news items sorted by impact (most impactful first)
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
}# 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/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
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.