DRRK/app/repositories/market_data.py

110 lines
3.9 KiB
Python

from __future__ import annotations
from datetime import date
from decimal import Decimal
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import MarketData, MarketDataManual, Security
from app.integrations.moex import MarketQuote
from app.repositories._upsert import insert_for
class MarketDataRepository:
def __init__(self, session: AsyncSession) -> None:
self.session = session
async def upsert(self, security_id: int, quote: MarketQuote, user: str) -> None:
"""Повторная загрузка за ту же дату перезаписывает котировку
(created_* сохраняются, updated_*/loaded_at обновляются)."""
values = {
"board": quote.board,
"clean_price": quote.clean_price,
"clean_price_source": quote.clean_price_source,
"yield_value": quote.yield_value,
"yield_source": quote.yield_source,
"duration_days": quote.duration_days,
"accrued_interest": quote.accrued_interest,
"face_value": quote.face_value,
"coupon_rate": quote.coupon_percent,
"num_trades": quote.num_trades,
"volume": quote.volume,
"raw": quote.raw,
}
stmt = insert_for(self.session, MarketData.__table__).values(
security_id=security_id,
market_date=quote.trade_date,
created_by=user,
updated_by=user,
**values,
)
stmt = stmt.on_conflict_do_update(
index_elements=["security_id", "market_date"],
set_={
**{name: stmt.excluded[name] for name in values},
"loaded_at": func.now(),
"updated_at": func.now(),
"updated_by": user,
},
)
await self.session.execute(stmt)
async def list_for_date(
self, market_date: date
) -> list[tuple[MarketData, Security]]:
result = await self.session.execute(
select(MarketData, Security)
.join(Security, Security.id == MarketData.security_id)
.where(MarketData.market_date == market_date)
.order_by(Security.isin)
)
return [(row, security) for row, security in result.all()]
async def list_manual_for_date(
self, market_date: date
) -> list[tuple[MarketDataManual, Security]]:
result = await self.session.execute(
select(MarketDataManual, Security)
.join(Security, Security.id == MarketDataManual.security_id)
.where(MarketDataManual.market_date == market_date)
.order_by(Security.isin)
)
return [(row, security) for row, security in result.all()]
async def upsert_manual(
self,
*,
security_id: int,
market_date: date,
clean_price: Decimal | None,
yield_value: Decimal | None,
duration_days: Decimal | None,
reason: str,
user: str,
) -> None:
"""Правка на ту же дату заменяет все три значения: пустое поле —
снова действует значение MOEX."""
values = {
"clean_price": clean_price,
"yield_value": yield_value,
"duration_days": duration_days,
"reason": reason,
}
stmt = insert_for(self.session, MarketDataManual.__table__).values(
security_id=security_id,
market_date=market_date,
created_by=user,
updated_by=user,
**values,
)
stmt = stmt.on_conflict_do_update(
index_elements=["security_id", "market_date"],
set_={
**{name: stmt.excluded[name] for name in values},
"updated_at": func.now(),
"updated_by": user,
},
)
await self.session.execute(stmt)