mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-19 13:28:07 +00:00
153 lines
5.0 KiB
Python
153 lines
5.0 KiB
Python
"""Crypto router -- live cryptocurrency prices from Bithumb (KRW) and Binance (USD)."""
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import List
|
|
|
|
import httpx
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
from server.models.schemas import CryptoPrice
|
|
|
|
router = APIRouter()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 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",
|
|
}
|
|
|
|
|
|
async def _fetch_binance_prices(client: httpx.AsyncClient, symbols: List[str]) -> dict:
|
|
"""Fetch USD prices from Binance API for the given symbols."""
|
|
url = "https://api.binance.com/api/v3/ticker/price"
|
|
try:
|
|
resp = await client.get(url, timeout=10)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
except (httpx.HTTPError, ValueError) as exc:
|
|
logger.warning("Binance prices fetch failed: %s", exc)
|
|
return {}
|
|
|
|
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
|
|
|
|
|
|
async def _fetch_bithumb_prices(client: httpx.AsyncClient, symbols: List[str]) -> dict:
|
|
"""Fetch KRW prices from Bithumb public API (parallel)."""
|
|
async def _one(sym: str):
|
|
bithumb_sym = _BITHUMB_MAP.get(sym.upper(), sym.upper())
|
|
url = f"https://api.bithumb.com/public/ticker/{bithumb_sym}_KRW"
|
|
try:
|
|
resp = await client.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:
|
|
return sym.upper(), float(closing)
|
|
except (httpx.HTTPError, ValueError) as exc:
|
|
logger.debug("Bithumb %s failed: %s", sym, exc)
|
|
return None
|
|
|
|
results = await asyncio.gather(*[_one(s) for s in symbols], return_exceptions=True)
|
|
prices = {}
|
|
for r in results:
|
|
if isinstance(r, tuple):
|
|
prices[r[0]] = r[1]
|
|
return prices
|
|
|
|
|
|
async def _fetch_binance_24h_changes(client: httpx.AsyncClient, symbols: List[str]) -> dict:
|
|
"""Fetch 24h percentage changes from Binance."""
|
|
url = "https://api.binance.com/api/v3/ticker/24hr"
|
|
try:
|
|
resp = await client.get(url, timeout=10)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
except (httpx.HTTPError, ValueError) as exc:
|
|
logger.warning("Binance 24h changes fetch failed: %s", exc)
|
|
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:
|
|
async with httpx.AsyncClient() as client:
|
|
usd_prices, krw_prices, changes = await asyncio.gather(
|
|
_fetch_binance_prices(client, TOP_SYMBOLS),
|
|
_fetch_bithumb_prices(client, TOP_SYMBOLS),
|
|
_fetch_binance_24h_changes(client, 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:
|
|
logger.exception("Crypto prices endpoint failed")
|
|
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()
|
|
async with httpx.AsyncClient() as client:
|
|
usd_prices, krw_prices, changes = await asyncio.gather(
|
|
_fetch_binance_prices(client, [sym]),
|
|
_fetch_bithumb_prices(client, [sym]),
|
|
_fetch_binance_24h_changes(client, [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:
|
|
logger.exception("Crypto price endpoint failed")
|
|
raise HTTPException(status_code=500, detail=f"Crypto price failed: {exc}") from exc
|