104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
from __future__ import annotations
|
||
|
||
from datetime import date
|
||
|
||
import streamlit as st
|
||
|
||
from app.services import fx_rates as fx_rates_service
|
||
from app.services import market_data as market_data_service
|
||
from app.services.dates import previous_business_day
|
||
from app.services.fx_rates import FxRatesUpdateResult
|
||
from app.services.market_data import MarketDataUpdateResult
|
||
from app.ui.common import as_table, call_db, current_user
|
||
|
||
_QUOTE_COLUMNS = {
|
||
"isin": "ISIN",
|
||
"name": "Наименование",
|
||
"board": "Режим",
|
||
"clean_price": "Чистая цена, %",
|
||
"clean_price_source": "Источник цены",
|
||
"yield_value": "Доходность, %",
|
||
"yield_source": "Источник доходности",
|
||
"duration_days": "Дюрация, дн.",
|
||
"accrued_interest": "НКД",
|
||
"face_value": "Номинал (тек.)",
|
||
"coupon_rate": "Купон, %",
|
||
"num_trades": "Сделок",
|
||
"volume": "Объём, шт.",
|
||
}
|
||
|
||
_RATE_COLUMNS = {
|
||
"rate_date": "Дата",
|
||
"nominal": "Номинал",
|
||
"rate": "Курс ЦБ",
|
||
"rate_per_unit": "За 1 CNY",
|
||
}
|
||
|
||
|
||
def render() -> None:
|
||
st.header("Рыночные данные")
|
||
|
||
on_date: date = st.date_input(
|
||
"Дата",
|
||
value=previous_business_day(),
|
||
max_value=date.today(),
|
||
format="DD.MM.YYYY",
|
||
)
|
||
|
||
# Внешние API — только по кнопке; ниже экран читает только БД.
|
||
quotes_col, fx_col = st.columns(2)
|
||
if quotes_col.button("Обновить котировки", use_container_width=True):
|
||
with st.spinner("Запрос котировок MOEX…"):
|
||
result = call_db(
|
||
market_data_service.update(on_date, current_user()),
|
||
"Не удалось обновить котировки",
|
||
)
|
||
if result is not None:
|
||
_show_market_result(result)
|
||
if fx_col.button("Обновить курс CNY", use_container_width=True):
|
||
with st.spinner("Запрос курса ЦБ…"):
|
||
fx_result = call_db(
|
||
fx_rates_service.update(on_date, current_user()),
|
||
"Не удалось обновить курс CNY",
|
||
)
|
||
if fx_result is not None:
|
||
_show_fx_result(fx_result)
|
||
|
||
st.subheader(f"Котировки на {on_date:%d.%m.%Y}")
|
||
quotes = call_db(
|
||
market_data_service.list_for_date(on_date), "Не удалось загрузить котировки"
|
||
)
|
||
if quotes:
|
||
st.dataframe(as_table(quotes, _QUOTE_COLUMNS), hide_index=True)
|
||
elif quotes is not None:
|
||
st.info("За эту дату котировок в базе нет.")
|
||
|
||
st.subheader("Курс CNY (последние)")
|
||
rates = call_db(fx_rates_service.list_recent(10), "Не удалось загрузить курсы")
|
||
if rates:
|
||
st.dataframe(as_table(rates, _RATE_COLUMNS), hide_index=True)
|
||
elif rates is not None:
|
||
st.info("Курсов CNY в базе пока нет.")
|
||
|
||
|
||
def _show_market_result(result: MarketDataUpdateResult) -> None:
|
||
summary = (
|
||
f"Обновлено: {result.updated}, без торгов: {result.no_trades}, "
|
||
f"ошибок: {result.errors}."
|
||
)
|
||
if result.errors:
|
||
st.error(summary)
|
||
elif result.no_trades:
|
||
st.warning(summary)
|
||
else:
|
||
st.success(summary)
|
||
with st.expander("Подробности по бумагам", expanded=bool(result.errors)):
|
||
for message in result.messages:
|
||
st.write(f"- {message}")
|
||
|
||
|
||
def _show_fx_result(result: FxRatesUpdateResult) -> None:
|
||
show = st.error if not result.ok else st.success
|
||
for message in result.messages:
|
||
show(message)
|