Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions app/market/pyth_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
No API key needed (Hermes is public).
"""
import time
import threading
from functools import wraps
from typing import Dict, List, Optional

import requests
import httpx

HERMES_URL = "https://hermes.pyth.network"
PRICE_TTL = 5 # seconds — Pyth publishes every ~2.5s

# Verified feed IDs from hermes.pyth.network/v2/price_feeds (Apr 2026)
PYTH_FEED_IDS: Dict[str, str] = {
Expand Down Expand Up @@ -77,6 +80,8 @@ def _parse_conf(price_info: dict) -> float:
class PythClient:
def __init__(self, timeout: int = 10):
self._timeout = timeout
self._client = httpx.Client(timeout=timeout)
self._cache: Dict[str, tuple[float, float]] = {}

def _feed_id(self, symbol: str) -> Optional[str]:
base = _normalize_base(symbol)
Expand All @@ -86,19 +91,26 @@ def is_supported(self, symbol: str) -> bool:
return self._feed_id(symbol) is not None

def get_price(self, symbol: str) -> Optional[float]:
"""Fetch current price with TTL cache (5s)."""
feed_id = self._feed_id(symbol)
if not feed_id:
return None
cache_key = f"price:{feed_id}"
now = time.monotonic()
cached = self._cache.get(cache_key)
if cached and (now - cached[0]) < PRICE_TTL:
return cached[1]
try:
r = requests.get(
r = self._client.get(
f"{HERMES_URL}/v2/updates/price/latest",
params={"ids[]": feed_id},
timeout=self._timeout,
)
r.raise_for_status()
parsed = r.json().get("parsed", [])
if parsed:
return _parse_price(parsed[0]["price"])
price = _parse_price(parsed[0]["price"])
self._cache[cache_key] = (now, price)
return price
except Exception as e:
print(f"[PYTH] price fetch failed for {symbol}: {e}")
return None
Expand All @@ -117,10 +129,9 @@ def get_price_with_confidence(self, symbol: str) -> Optional[dict]:
if not feed_id:
return None
try:
r = requests.get(
r = self._client.get(
f"{HERMES_URL}/v2/updates/price/latest",
params={"ids[]": feed_id},
timeout=self._timeout,
)
r.raise_for_status()
parsed = r.json().get("parsed", [])
Expand All @@ -146,10 +157,9 @@ def get_prices_batch(self, symbols: List[str]) -> Dict[str, float]:
if not ids:
return {}
try:
r = requests.get(
r = self._client.get(
f"{HERMES_URL}/v2/updates/price/latest",
params=[("ids[]", fid) for fid in ids],
timeout=self._timeout,
)
r.raise_for_status()
out: Dict[str, float] = {}
Expand Down