Files
All-in-one-Financial-Analysis/atlas-terminal/server/routers/crypto.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

140 lines
4.1 KiB
Python

"""Crypto router -- live cryptocurrency prices from Bithumb (KRW) and Binance (USD)."""
from typing import List
from fastapi import APIRouter, HTTPException
from server.models.schemas import CryptoPrice
router = APIRouter()
# Top 20 symbols tracked by default
TOP_SYMBOLS = [
"BTC", "ETH", "BNB", "XRP", "SOL", "ADA", "DOGE", "AVAX", "DOT", "MATIC",
"LINK", "SHIB", "TRX", "UNI", "ATOM", "LTC", "ETC", "XLM", "NEAR", "APT",
]
# Bithumb uses different ticker names for some coins
_BITHUMB_MAP = {
"MATIC": "MATIC",
"NEAR": "NEAR",
"APT": "APT",
}
def _fetch_binance_prices(symbols: List[str]) -> dict:
"""Fetch USD prices from Binance API for the given symbols."""
import requests
url = "https://api.binance.com/api/v3/ticker/price"
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
data = resp.json()
except Exception:
return {}
# Build lookup: symbol (no USDT suffix) -> price
prices = {}
lookup = {item["symbol"]: float(item["price"]) for item in data}
for sym in symbols:
key = f"{sym.upper()}USDT"
if key in lookup:
prices[sym.upper()] = lookup[key]
return prices
def _fetch_bithumb_prices(symbols: List[str]) -> dict:
"""Fetch KRW prices from Bithumb public API."""
import requests
prices = {}
for sym in symbols:
bithumb_sym = _BITHUMB_MAP.get(sym.upper(), sym.upper())
url = f"https://api.bithumb.com/public/ticker/{bithumb_sym}_KRW"
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
data = resp.json()
if data.get("status") == "0000":
closing = data.get("data", {}).get("closing_price")
if closing:
prices[sym.upper()] = float(closing)
except Exception:
continue
return prices
def _fetch_binance_24h_changes(symbols: List[str]) -> dict:
"""Fetch 24h percentage changes from Binance."""
import requests
url = "https://api.binance.com/api/v3/ticker/24hr"
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
data = resp.json()
except Exception:
return {}
changes = {}
lookup = {item["symbol"]: float(item.get("priceChangePercent", 0)) for item in data}
for sym in symbols:
key = f"{sym.upper()}USDT"
if key in lookup:
changes[sym.upper()] = lookup[key]
return changes
@router.get(
"/prices",
response_model=List[CryptoPrice],
summary="Top 20 crypto prices (Bithumb KRW + Binance USD)",
)
async def crypto_prices():
"""Return current prices for the top 20 cryptocurrencies.
USD prices come from Binance; KRW prices from Bithumb.
"""
try:
usd_prices = _fetch_binance_prices(TOP_SYMBOLS)
krw_prices = _fetch_bithumb_prices(TOP_SYMBOLS)
changes = _fetch_binance_24h_changes(TOP_SYMBOLS)
results: List[CryptoPrice] = []
for sym in TOP_SYMBOLS:
results.append(CryptoPrice(
symbol=sym,
name=sym,
price_usd=usd_prices.get(sym),
price_krw=krw_prices.get(sym),
change_24h_pct=changes.get(sym),
))
return results
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Crypto prices failed: {exc}") from exc
@router.get(
"/price/{symbol}",
response_model=CryptoPrice,
summary="Single crypto price",
)
async def crypto_price(symbol: str):
"""Return current price for a single cryptocurrency symbol."""
try:
sym = symbol.upper()
usd_prices = _fetch_binance_prices([sym])
krw_prices = _fetch_bithumb_prices([sym])
changes = _fetch_binance_24h_changes([sym])
return CryptoPrice(
symbol=sym,
name=sym,
price_usd=usd_prices.get(sym),
price_krw=krw_prices.get(sym),
change_24h_pct=changes.get(sym),
)
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Crypto price failed: {exc}") from exc