ffback
v1.0.0
Published
Backend para análisis de noticias financieras con sentimiento
Downloads
6
Maintainers
Readme
FFBack - Financial Feed Backend
CLI to analyze financial news sentiment using finBERT and transformers.js - intended to be used within financialfeeling.com
Principal characteristics
- 200+ news articles from multiple verified RSS sources
- Auto scraping using Cheerio (25 articles with whole content)
- 8 RSS feed configurated by default: NY Times (Business/Tech/Economy), BBC (Business/World), Reddit (WorldNews/Tech/Stocks)
- Intelligent chunking of large articles in 512 char fragments
- Vectorial embeddings using all-MiniLM-L6-v2 for semantic search
- Sentiment analysis with finBERT model specialized in financial sentiment
- Full API REST to integrate easily with Next.js
- Intelligent cache with scraping metadata
- Automatic extraction of from article contents
- Ready for WebLLM! waiting to be implemented in the frontend to further improve the analysis with Qwen AI model (Qwen only working client-side)
Requirements
- Node.js >= 18.x
- npm o yarn
- 4GB+ RAM recommended for ML models
Try the CLI!
./analyze-ticker.sh {TICKER_NAME}Install
# Clone the repository
git clone <repository-url>
cd ffback
# Install dependencies
npm install
# Cp config file
cp .env.example .env
# Edit config (optional)
nano .env️Configuración
Edit .env file to customize:
# RSS Feeds - Financial News Sources (VERIFIED)
RSS_FEEDS=https://rss.nytimes.com/services/xml/rss/nyt/Business.xml,https://feeds.bbci.co.uk/news/business/rss.xml,https://feeds.bbci.co.uk/news/world/rss.xml,https://rss.nytimes.com/services/xml/rss/nyt/Technology.xml,https://rss.nytimes.com/services/xml/rss/nyt/Economy.xml,https://www.reddit.com/r/worldnews/.rss,https://www.reddit.com/r/technology/.rss,https://www.reddit.com/r/stocks/.rss
# Scraping Configuration
ENABLE_SCRAPING=true
SCRAPING_DELAY_MS=1200
SCRAPING_MAX_ARTICLES=25
# Cache config (in seconds)
CACHE_TTL=3600
# MAX chunk size for analysis
MAX_CHUNK_SIZE=512
OVERLAP_SIZE=50
# CORS
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001Using it
Development
npm run devProduction
npm run build
npm startOther comands
npm run lint # Ejecutar linter
npm test # Ejecutar testsAPI Endpoints
1. Health Check
GET /api/healthResponse:
{
"status": "ok",
"timestamp": "2025-10-12T...",
"uptime": 123.45
}2. Pipeline status
GET /api/statusResponse:
{
"isProcessing": false,
"lastUpdateTime": "2025-10-12T...",
"hasData": true,
"cacheStats": {
"keys": 5,
"hits": 120,
"misses": 10
},
"feeds": [...]
}3. Analyze Ticker sentiment
POST /api/analyze
Content-Type: application/json
{
"tickers": ["AAPL", "MSFT", "GOOGL"],
"timeRange": {
"start": "2025-10-01T00:00:00Z",
"end": "2025-10-12T23:59:59Z"
},
"sources": ["yahoo", "reuters"] // opcional
}Response:
{
"tickers": [
{
"ticker": "AAPL",
"sentiment": {
"label": "positive",
"score": 0.85,
"confidence": 0.92
},
"relatedArticles": [
{
"articleId": "abc123",
"title": "Apple Reports Strong Q4 Results",
"url": "https://...",
"publishedAt": "2025-10-11T...",
"relevanceScore": 0.95
}
],
"aggregatedSentiment": {
"overall": "positive",
"positiveCount": 15,
"negativeCount": 3,
"neutralCount": 2,
"averageScore": 0.78,
"confidence": 0.88
},
"timestamp": "2025-10-12T..."
}
],
"metadata": {
"totalArticlesAnalyzed": 150,
"processingTime": 2345,
"timestamp": "2025-10-12T..."
}
}4. Obtain articles
GET /api/articles?page=1&limit=50Response:
{
"articles": [...],
"pagination": {
"page": 1,
"limit": 50,
"total": 200,
"totalPages": 4
}
}5. Obtain articles by ticker
GET /api/articles/AAPLResponse:
{
"ticker": "AAPL",
"articles": [...],
"count": 25
}6. Obtain all the tickers
GET /api/tickersResponse:
{
"tickers": ["AAPL", "MSFT", "GOOGL", ...],
"count": 150
}7. Add custom Tickers
POST /api/tickers
Content-Type: application/json
{
"tickers": ["CUSTOM1", "CUSTOM2"]
}8. Refesh Pipeline
POST /api/refreshForce full data update.
Next.Js integration
1. Client API config
// lib/ffback-client.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api';
export async function analyzeTickers(tickers: string[]) {
const response = await fetch(`${API_BASE_URL}/analyze`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tickers }),
});
if (!response.ok) {
throw new Error('Failed to analyze tickers');
}
return response.json();
}
export async function getArticles(page = 1, limit = 50) {
const response = await fetch(`${API_BASE_URL}/articles?page=${page}&limit=${limit}`);
if (!response.ok) {
throw new Error('Failed to fetch articles');
}
return response.json();
}2. Example with React Component
// components/TickerAnalysis.tsx
'use client';
import { useState } from 'react';
import { analyzeTickers } from '@/lib/ffback-client';
export function TickerAnalysis() {
const [ticker, setTicker] = useState('');
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const handleAnalyze = async () => {
setLoading(true);
try {
const data = await analyzeTickers([ticker.toUpperCase()]);
setResult(data);
} catch (error) {
console.error('Analysis failed:', error);
} finally {
setLoading(false);
}
};
return (
<div className="p-4">
<input
type="text"
value={ticker}
onChange={(e) => setTicker(e.target.value)}
placeholder="Enter ticker (e.g., AAPL)"
className="border p-2 rounded"
/>
<button
onClick={handleAnalyze}
disabled={loading}
className="ml-2 bg-blue-500 text-white px-4 py-2 rounded"
>
{loading ? 'Analyzing...' : 'Analyze'}
</button>
{result && (
<div className="mt-4">
{/* Render sentiment results */}
</div>
)}
</div>
);
}3. WebLLM integration (Frontend)
Using light models like Qwen3B in the browser:
// lib/webllm-client.ts
import * as webllm from '@mlc-ai/web-llm';
let engine: webllm.MLCEngine | null = null;
export async function initializeWebLLM() {
if (!engine) {
engine = await webllm.CreateMLCEngine('Qwen2.5-0.5B-Instruct-q4f16_1-MLC');
}
return engine;
}
export async function analyzeWithLLM(tickerData: any, userQuery: string) {
const engine = await initializeWebLLM();
const prompt = `Given the following sentiment analysis for ${tickerData.ticker}:
${JSON.stringify(tickerData, null, 2)}
${userQuery}`;
const response = await engine.chat.completions.create({
messages: [{ role: 'user', content: prompt }],
});
return response.choices[0].message.content;
}️Architecture
┌─────────────────┐
│ RSS Feeds │
│ (Yahoo, Reuters)│
└────────┬────────┘
│
▼
┌─────────────────┐
│ RSS Fetcher │
│ Service │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Normalizer │
│ Service │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Chunking + │
│ Embedding │
└────────┬────────┘
│
▼
┌─────────────────┐
│ FinBERT │
│ Sentiment │
└────────┬────────┘
│
▼
┌─────────────────┐
│ API REST │
│ + Cache │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Next.js App │
│ + WebLLM │
└─────────────────┘Security aspects
- Helmet.js for security headers (CORS)
- Customizable CORS.
- Entry validation with Zod
- Rate limiting recommended for producción
Performance
- Smart caching with auto expiry
- Processing in chunks to improve efficency
- Cuantized AI models to reduce memory usage
- Periodic updating in the background
Testing
npm test