arbret/backend/services/price.py

68 lines
2.1 KiB
Python
Raw Normal View History

"""Price service for fetching and managing price history."""
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
from exceptions import ConflictError
from models import PriceHistory
from price_fetcher import PAIR_BTC_EUR, SOURCE_BITFINEX, fetch_btc_eur_price
from repositories.price import PriceRepository
PRICE_HISTORY_LIMIT = 20
class PriceService:
"""Service for price-related business logic."""
def __init__(self, db: AsyncSession):
self.db = db
self.price_repo = PriceRepository(db)
async def get_recent_prices(
self, limit: int = PRICE_HISTORY_LIMIT
) -> list[PriceHistory]:
"""
Get recent price history records.
Args:
limit: Maximum number of records to return (default: 20)
Returns:
List of PriceHistory records, most recent first
"""
return await self.price_repo.get_recent(limit)
async def fetch_and_store_price(self) -> PriceHistory:
"""
Fetch price from Bitfinex and store it in the database.
Handles duplicate timestamp conflicts by returning the existing record.
Returns:
PriceHistory record (newly created or existing if duplicate)
Raises:
ConflictError: If unable to fetch or store price after retries
"""
price_value, timestamp = await fetch_btc_eur_price()
record = PriceHistory(
source=SOURCE_BITFINEX,
pair=PAIR_BTC_EUR,
price=price_value,
timestamp=timestamp,
)
try:
return await self.price_repo.create(record)
except IntegrityError:
# Duplicate timestamp - return the existing record
await self.db.rollback()
existing_record = await self.price_repo.get_by_timestamp(
timestamp, SOURCE_BITFINEX, PAIR_BTC_EUR
)
if existing_record:
return existing_record
# This should not happen, but handle gracefully
raise ConflictError("Failed to fetch or store price") from None