The dashboard backend follows a Service Layer Architecture pattern for clean separation of concerns and maintainability.
βββββββββββββββββββββββββββββββββββββββββββ
β API Layer (Routers) β
β - HTTP endpoints β
β - Request/response handling β
β - Authentication β
βββββββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββββββββ
β Service Layer (Business Logic) β
β - Dashboard calculations β
β - Risk metrics β
β - External API integration β
βββββββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββββββββ
β Data Layer (Models & DB) β
β - SQLAlchemy models β
β - Database queries β
β - Data persistence β
βββββββββββββββββββββββββββββββββββββββββββ
Portfolio Model
- Stores portfolio snapshots with timestamp
- Tracks total value, cash, invested amounts
- Calculates returns and performance metrics
- Includes risk metrics (Sharpe, volatility, max drawdown)
Position Model
- Tracks open stock positions
- Real-time P/L calculation
- Sector and industry classification
- Links to company profiles
Trade Model
- Complete trade history
- Realized P/L tracking
- Buy/sell type identification
- Commission tracking
StockQuote Model
- Cached live market data
- Updates from Finnhub API
- 1-minute cache TTL
- Price, volume, change tracking
CompanyProfile Model
- Company metadata
- Logo, sector, industry
- Market cap, IPO date
- 24-hour cache TTL
Purpose: External API integration with intelligent caching
Features:
- Async HTTP client (httpx)
- Rate limiting (30 req/sec)
- Smart caching strategy
- Error handling and retries
- Batch operations
Key Methods:
async def get_quote(symbol: str) -> dict
async def get_company_profile(symbol: str) -> dict
async def update_cached_quote(db, symbol) -> StockQuote
async def batch_update_quotes(db, symbols) -> dictCaching Strategy:
- Quotes: 60 seconds (real-time trading)
- Profiles: 24 hours (static data)
- Database-backed cache
- Automatic expiration
Purpose: Business logic and calculations
Features:
- Portfolio metrics calculation
- Risk analysis (Sharpe, volatility, max drawdown)
- Trade statistics (win rate, avg profit)
- Position enrichment with live data
- Historical performance analysis
Key Methods:
async def calculate_portfolio_metrics(db, user_id) -> dict
async def get_positions_with_live_data(db, user_id) -> list
async def update_position_prices(db, position) -> Position
async def get_recent_trades(db, user_id, limit) -> listRisk Metrics Calculations:
Sharpe Ratio:
sharpe = (avg_return * 252) / (volatility * sqrt(252))Max Drawdown:
max_dd = max((peak - trough) / peak for all peaks)Volatility (Annualized):
volatility = stdev(daily_returns) * sqrt(252)Purpose: HTTP endpoint definitions
Authentication: All endpoints require JWT token
Endpoints:
| Endpoint | Method | Description | Cache |
|---|---|---|---|
/overview |
GET | Complete portfolio metrics | No |
/positions |
GET | All positions + live prices | No |
/trades |
GET | Trade history | No |
/quote/{symbol} |
GET | Live stock quote | 60s |
/profile/{symbol} |
GET | Company profile | 24h |
/performance |
GET | Historical time series | No |
Dependency Injection:
dashboard_service: DashboardService = Depends(get_dashboard_service)
current_user: User = Depends(get_current_user)
db: AsyncSession = Depends(get_session)1. Client sends GET /api/dashboard/overview
β
2. Router validates JWT token
β
3. Router calls DashboardService.calculate_portfolio_metrics()
β
4. Service queries all open positions from database
β
5. Service calls FinnhubService.batch_update_quotes()
β
6. Finnhub Service checks cache, fetches if expired
β
7. Service calculates:
- Total values (market value, cost basis)
- Returns (absolute and percentage)
- Risk metrics from historical snapshots
- Trade statistics
β
8. Service saves portfolio snapshot to database
β
9. Router returns JSON response to client
users (1) ββββ (many) portfolios
users (1) ββββ (many) positions
users (1) ββββ (many) trades
positions (many) ββββ (1) stock_quotes (symbol)
positions (many) ββββ (1) company_profiles (symbol)Performance-critical indexes:
positions.user_id- Fast user position lookuppositions.symbol- Quick symbol searchpositions.status- Filter open/closedtrades.user_id- User trade historytrades.symbol- Symbol trade historytrades.trade_date- Chronological sortingstock_quotes.symbol- Quote lookupstock_quotes.updated_at- Cache expiration check
All I/O operations use async/await:
async with db.begin():
result = await db.execute(query)
data = result.scalars().all()SQLAlchemy async pool configuration:
engine = create_async_engine(
DATABASE_URL,
pool_size=5,
max_overflow=10,
pool_pre_ping=True
)Fetch multiple quotes in one batch:
symbols = [p.symbol for p in positions]
quotes = await finnhub.batch_update_quotes(db, symbols)Database-backed cache:
- Store in
stock_quotestable - Check
updated_attimestamp - Return cached if fresh, fetch if stale
Benefits:
- Reduces API calls (costs)
- Faster response times
- Survives server restarts
- Shared across requests
try:
quote = await finnhub.get_quote(symbol)
except Exception as e:
logger.error(f"Finnhub error: {e}")
# Return cached data or default values
return cached_quote or default_quote- INFO: Successful operations, cache hits
- WARNING: API failures, degraded service
- ERROR: Database errors, critical failures
- DEBUG: Detailed request/response data
@router.get("/overview")
async def get_overview(
current_user: User = Depends(get_current_user)
):
# current_user automatically populated from JWT
# or 401 error if invalid/missing tokenUsing SQLAlchemy ORM (no raw SQL):
# Safe - parameterized query
result = await db.execute(
select(Position).where(Position.user_id == user_id)
)- bcrypt hashing with salt
- Never store plain text
- Automatic salt generation
Test individual components:
# Test Finnhub service
async def test_get_quote():
service = FinnhubService(api_key="test")
quote = await service.get_quote("AAPL")
assert quote["c"] > 0Test complete flows:
# Test dashboard endpoint
async def test_portfolio_overview():
response = await client.get(
"/api/dashboard/overview",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
assert "total_value" in response.json()["data"]Comprehensive logging for debugging:
logger.info(f"Updated quote for {symbol}: ${price}")
logger.error(f"API error: {e}", exc_info=True)- API response times
- Cache hit/miss rates
- Database query performance
- External API call frequency
- Error rates by endpoint
- Set strong JWT_SECRET
- Use production database
- Enable SSL for all connections
- Set CORS_ORIGINS to production domain
- Configure proper logging
- Set up monitoring/alerts
- Enable database backups
- Use environment variables
- Configure rate limiting
- Set up health checks
Database Connection Failed
- Check DATABASE_URL format
- Verify firewall allows connection
- Confirm SSL mode matches server
Finnhub API Errors
- Check API key is valid
- Verify rate limiting not exceeded
- Check internet connectivity
Authentication Fails
- Verify JWT_SECRET matches
- Check token expiration
- Confirm user exists in database
Slow Performance
- Check database indexes exist
- Verify caching is working
- Monitor external API latency
- Check connection pool size
For more details, see the main README.md or DASHBOARD_WORKING.md.