110 lines
3.9 KiB
Python
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)
|