124 lines
4.5 KiB
Python
124 lines
4.5 KiB
Python
from __future__ import annotations
|
||
|
||
import logging
|
||
|
||
import streamlit as st
|
||
|
||
from app.db.base import run_db
|
||
from app.services import securities as securities_service
|
||
from app.services.securities import SecurityPreview, SecurityServiceError
|
||
from app.ui.common import as_table, call_db, current_user
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
_PREVIEW_KEY = "security_preview"
|
||
|
||
_COLUMNS = {
|
||
"isin": "ISIN",
|
||
"secid": "SECID",
|
||
"name": "Наименование",
|
||
"currency": "Валюта",
|
||
"initial_face_value": "Номинал (перв.)",
|
||
"placement_date": "Начало торгов",
|
||
"maturity_date": "Погашение",
|
||
"coupon_rate": "Купон, %",
|
||
"coupon_frequency": "Купонов в год",
|
||
"is_active": "Обновлять",
|
||
}
|
||
|
||
|
||
def render() -> None:
|
||
st.header("Бумаги")
|
||
|
||
rows = call_db(
|
||
securities_service.list_securities(), "Не удалось загрузить список бумаг"
|
||
)
|
||
if rows is not None:
|
||
if rows:
|
||
st.dataframe(as_table(rows, _COLUMNS), hide_index=True)
|
||
else:
|
||
st.info("Бумаг пока нет — добавьте первую по ISIN.")
|
||
|
||
st.subheader("Добавить по ISIN")
|
||
_render_lookup_form()
|
||
preview = st.session_state.get(_PREVIEW_KEY)
|
||
if isinstance(preview, SecurityPreview):
|
||
_render_preview(preview)
|
||
|
||
|
||
def _render_lookup_form() -> None:
|
||
with st.form("security_lookup"):
|
||
isin = st.text_input("ISIN", placeholder="RU000A106169", max_chars=12)
|
||
submitted = st.form_submit_button("Найти на Мосбирже")
|
||
if not submitted:
|
||
return
|
||
|
||
st.session_state.pop(_PREVIEW_KEY, None)
|
||
try:
|
||
with st.spinner("Запрос к MOEX…"):
|
||
preview = run_db(securities_service.preview_by_isin(isin, current_user()))
|
||
except SecurityServiceError as exc:
|
||
st.error(str(exc))
|
||
return
|
||
except Exception as exc: # noqa: BLE001
|
||
logger.exception("Ошибка предпросмотра бумаги %s", isin)
|
||
st.error(f"Не удалось получить данные бумаги: {exc}")
|
||
return
|
||
st.session_state[_PREVIEW_KEY] = preview
|
||
|
||
|
||
def _render_preview(preview: SecurityPreview) -> None:
|
||
info = preview.info
|
||
with st.container(border=True):
|
||
st.markdown(f"**{info.short_name or info.secid}** — данные MOEX")
|
||
st.dataframe(
|
||
as_table(
|
||
[
|
||
{
|
||
"isin": preview.isin,
|
||
"secid": info.secid,
|
||
"currency": f"{preview.currency_code} ({info.face_unit})",
|
||
"initial_face_value": info.initial_face_value,
|
||
"face_value": info.face_value,
|
||
"issue_date": info.issue_date,
|
||
"mat_date": info.mat_date,
|
||
"coupon_percent": info.coupon_percent,
|
||
"coupon_frequency": info.coupon_frequency,
|
||
}
|
||
],
|
||
{
|
||
"isin": "ISIN",
|
||
"secid": "SECID",
|
||
"currency": "Валюта",
|
||
"initial_face_value": "Номинал (перв.)",
|
||
"face_value": "Номинал (тек.)",
|
||
"issue_date": "Начало торгов",
|
||
"mat_date": "Погашение",
|
||
"coupon_percent": "Купон, %",
|
||
"coupon_frequency": "Купонов в год",
|
||
},
|
||
),
|
||
hide_index=True,
|
||
)
|
||
save_col, cancel_col = st.columns(2)
|
||
if save_col.button("Сохранить", type="primary", use_container_width=True):
|
||
_save(preview)
|
||
if cancel_col.button("Отмена", use_container_width=True):
|
||
st.session_state.pop(_PREVIEW_KEY, None)
|
||
st.rerun()
|
||
|
||
|
||
def _save(preview: SecurityPreview) -> None:
|
||
try:
|
||
result = run_db(securities_service.save_preview(preview, current_user()))
|
||
except SecurityServiceError as exc:
|
||
st.error(str(exc))
|
||
return
|
||
except Exception as exc: # noqa: BLE001
|
||
logger.exception("Ошибка сохранения бумаги %s", preview.isin)
|
||
st.error(f"Не удалось сохранить бумагу: {exc}")
|
||
return
|
||
st.session_state.pop(_PREVIEW_KEY, None)
|
||
st.toast(f"Бумага {result.isin} ({result.name}) добавлена.")
|
||
st.rerun()
|