fix(income): погашения облигаций попадают в календарь выплат

У облигаций из ленты операций не было ни даты погашения, ни номинала, а погашение строилось именно от них. moex_payouts дописывает оба поля из паспорта бумаги (только пустые). Аналитика берёт дату погашения из графика номинала, если поля нет, считает сумму по номиналу накануне, а последний шаг амортизации не учитывает дважды.
This commit is contained in:
Dmitry
2026-09-20 10:46:27 +03:00
parent d2df86ce33
commit 6a15371960
5 changed files with 177 additions and 7 deletions
@@ -286,6 +286,20 @@ def mock_iss(mock_http) -> None:
),
)
)
mock_http.get(url__startswith=f"{ISS}/securities/RU000A.json").mock(
return_value=httpx.Response(
200,
json=block(
"description",
["name", "title", "value"],
[
["MATDATE", "Дата погашения", "2028-05-05"],
["FACEVALUE", "Номинальная стоимость", "750"],
["FACEUNIT", "Валюта номинала", "SUR"],
],
),
)
)
mock_http.get(url__startswith=f"{ISS}/securities/SBER/dividends").mock(
return_value=httpx.Response(
200,
@@ -298,6 +312,37 @@ def mock_iss(mock_http) -> None:
)
async def test_a_bond_run_fills_the_maturity_and_nominal_the_instrument_lacks(
app, mock_http, run_sync
):
"""The redemption on the income calendar is built from these two fields."""
instrument_id = await seed(AssetClass.bond, "RU000A")
mock_iss(mock_http)
await run_sync(MoexPayoutsSource(), settings=Settings())
(instrument,) = await rows_of(Instrument, id=instrument_id)
assert instrument.maturity_date == date(2028, 5, 5)
assert instrument.nominal == D(750)
assert instrument.nominal_currency == "RUB"
async def test_a_value_already_on_the_instrument_is_not_overwritten(app, mock_http, run_sync):
instrument_id = await seed(AssetClass.bond, "RU000A")
async with get_sessionmaker()() as session:
instrument = await session.get(Instrument, instrument_id)
assert instrument is not None
instrument.nominal = D(1000)
await session.commit()
mock_iss(mock_http)
await run_sync(MoexPayoutsSource(), settings=Settings())
(instrument,) = await rows_of(Instrument, id=instrument_id)
assert instrument.nominal == D(1000) # what was there stays, whatever MOEX says today
assert instrument.maturity_date == date(2028, 5, 5) # only the missing field is filled
async def test_a_bond_run_writes_coupons_and_the_nominal_schedule(app, mock_http, run_sync):
instrument_id = await seed(AssetClass.bond, "RU000A")
mock_iss(mock_http)