Files
All-in-one-Financial-Analysis/atlas-terminal/server/db/pg_portfolio_repo.py
T
shawnkim1997andClaude Opus 4.6 b2acda81ee feat: add Atlas Terminal — Next.js 14 + FastAPI full-stack migration
Complete migration from Streamlit to Next.js 14 App Router + FastAPI backend.

Frontend (Next.js 14):
- 10 pages: Overview, Research, Valuation, Technical, Markets, Earnings, News, Portfolio, Filings, Settings
- Terminal Noir dark theme with custom Tailwind config
- TradingView Lightweight Charts for candlestick/volume
- Valuation: DCF, Sensitivity Matrix, Monte Carlo, Tornado, Reverse DCF
- Financial Statements table with YoY growth badges and margin rows
- SEC EDGAR inline filing viewer with section tabs
- News split-view with iframe article embedding
- Technical Analysis with RSI, MACD, Bollinger, Fibonacci, Moving Averages
- Earnings beat/miss visualization
- AI Copilot chat panel with Gemini integration

Backend (FastAPI):
- 13 routers: market_data, financials, valuation, technical, earnings, insider, edgar, news, portfolio, analysis, chat, estimates, fx
- Services: DCF engine, Monte Carlo simulation, sensitivity analysis, risk metrics, SEC parser, technical indicators
- yfinance + yahooquery data sources with fallback pattern
- SQLite caching layer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 02:10:10 +00:00

111 lines
3.6 KiB
Python

"""
PostgreSQL portfolio repository — full CRUD for portfolio positions.
"""
from typing import Optional
from datetime import datetime, timezone
from server.db.pg_database import get_pg_pool
async def pg_get_all_positions() -> list[dict]:
"""Get all portfolio positions from PostgreSQL."""
pool = await get_pg_pool()
if not pool:
return []
async with pool.acquire() as conn:
rows = await conn.fetch(
"SELECT * FROM portfolio_positions ORDER BY updated_at DESC"
)
return [dict(r) for r in rows]
async def pg_add_position(
ticker: str,
name: str = "",
shares: float = 0.0,
avg_cost: float = 0.0,
currency: str = "USD",
broker: str = "",
) -> Optional[dict]:
"""Add a new position to PostgreSQL."""
pool = await get_pg_pool()
if not pool:
return None
async with pool.acquire() as conn:
row = await conn.fetchrow(
"""INSERT INTO portfolio_positions (ticker, name, shares, avg_cost, currency, broker)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *""",
ticker.upper(), name, shares, avg_cost, currency, broker,
)
return dict(row) if row else None
async def pg_update_position(position_id: int, **fields) -> Optional[dict]:
"""Update a position by ID."""
pool = await get_pg_pool()
if not pool:
return None
allowed = {"ticker", "name", "shares", "avg_cost", "currency", "broker"}
updates = {k: v for k, v in fields.items() if k in allowed}
if not updates:
return None
updates["updated_at"] = datetime.now(timezone.utc)
set_clauses = ", ".join(f"{k} = ${i+2}" for i, k in enumerate(updates.keys()))
values = [position_id] + list(updates.values())
async with pool.acquire() as conn:
row = await conn.fetchrow(
f"UPDATE portfolio_positions SET {set_clauses} WHERE id = $1 RETURNING *",
*values,
)
return dict(row) if row else None
async def pg_delete_position(position_id: int) -> bool:
"""Delete a position by ID."""
pool = await get_pg_pool()
if not pool:
return False
async with pool.acquire() as conn:
result = await conn.execute(
"DELETE FROM portfolio_positions WHERE id = $1", position_id
)
return result == "DELETE 1"
async def pg_get_position_by_ticker(ticker: str) -> Optional[dict]:
"""Get a position by ticker symbol."""
pool = await get_pg_pool()
if not pool:
return None
async with pool.acquire() as conn:
row = await conn.fetchrow(
"SELECT * FROM portfolio_positions WHERE ticker = $1", ticker.upper()
)
return dict(row) if row else None
async def pg_bulk_add_positions(positions: list[dict]) -> list[dict]:
"""Bulk add positions (from OCR screenshot)."""
pool = await get_pg_pool()
if not pool:
return []
results = []
async with pool.acquire() as conn:
async with conn.transaction():
for p in positions:
row = await conn.fetchrow(
"""INSERT INTO portfolio_positions (ticker, name, shares, avg_cost, currency, broker)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *""",
p.get("ticker", "").upper(),
p.get("name", ""),
float(p.get("shares", 0)),
float(p.get("avg_cost", 0)),
p.get("currency", "USD"),
p.get("broker", ""),
)
if row:
results.append(dict(row))
return results