spec: refine performance rules from implementation decisions (WealthLedger/main#54)
This commit is contained in:
+37
-20
@@ -58,7 +58,7 @@ Issue: WealthLedger/main#54
|
||||
сбой индекса превращается в `INDEX_HISTORY_UNAVAILABLE` внутри
|
||||
`benchmark`/`reference`.
|
||||
- Числа: деньги — `MoneyDto` (RUB, десятичная строка, округление до копеек
|
||||
half-even); доли (`Rate`) — десятичная строка до 6 знаков, `0.151` = 15,1 %;
|
||||
half away from zero, как общий decimal helper); доли (`Rate`) — десятичная строка до 6 знаков, `0.151` = 15,1 %;
|
||||
точки `series` — десятичные RUB до копеек.
|
||||
- Идемпотентность: GET без побочных эффектов, кроме догрузки истории индекса.
|
||||
Её повтор безопасен (upsert по индексу и торговому дню).
|
||||
@@ -93,19 +93,22 @@ Issue: WealthLedger/main#54
|
||||
|
||||
### Переводы между счетами в `all`
|
||||
|
||||
T-Bank не передаёт в операции счёт-контрагента. Поэтому перевод
|
||||
определяется парой операций.
|
||||
T-Bank не передаёт в операции счёт-контрагента. Совпадение операций —
|
||||
лишь кандидат на перевод, а не подтверждение. Не объявлять кандидата
|
||||
внутренним потоком без независимого признака связи.
|
||||
|
||||
- **Деньги.** Вывод на счёте A и пополнение на счёте B (оба входят в
|
||||
результат) с равной суммой RUB и `occurredAt` в пределах 5 минут.
|
||||
Пара исключается из потоков. Без пары операция остаётся внешним потоком.
|
||||
- **Деньги.** Вывод на счёте A и пополнение на счёте B равной суммы RUB
|
||||
в пределах 5 минут — только кандидат. Пока нет подтверждения связи,
|
||||
оба счёта исключаются из расчётной части `all` с причиной
|
||||
`UNCONFIRMED_TRANSFER`. Без кандидата операция остаётся внешним потоком.
|
||||
- **Бумаги.** `OUTPUT_SECURITIES` / `TRANS_*` на счёте A и
|
||||
`INPUT_SECURITIES` / `TRANS_*` на счёте B с тем же инструментом и
|
||||
количеством в тот же день по Москве. Пара внутренняя. Без пары оба счёта
|
||||
получают `UNVALUED_SECURITIES_TRANSFER` и исключаются.
|
||||
количеством в тот же день — только кандидат; оба счёта исключаются.
|
||||
Для одиночного ввода или вывода применяется `UNVALUED_SECURITIES_TRANSFER`.
|
||||
|
||||
Правило парности нужно подтвердить на реальных данных при первом запуске
|
||||
(см. Assumptions).
|
||||
В текущем контракте T-Bank нет признака, подтверждающего пару. Полностью
|
||||
корректное включение счетов с переводами в `all` отложено до появления
|
||||
подтверждённой связи; не вычислять процент на предположении.
|
||||
|
||||
### Причины недоступности
|
||||
|
||||
@@ -118,6 +121,8 @@ T-Bank не передаёт в операции счёт-контрагента
|
||||
нет вычислимой стоимости (то же правило, что у `portfolio-value-history`).
|
||||
4. `NON_RUB_ACTIVITY`.
|
||||
5. `UNVALUED_SECURITIES_TRANSFER`.
|
||||
6. `UNCONFIRMED_TRANSFER` (только `all`, когда есть кандидат на внутренний
|
||||
перевод без доказанной связи).
|
||||
|
||||
Как причины попадают в ответ:
|
||||
|
||||
@@ -126,15 +131,22 @@ T-Bank не передаёт в операции счёт-контрагента
|
||||
- В `all` исключённые счета перечисляются в `excludedAccounts`, а
|
||||
`completeness = incomplete`. Если не осталось ни одного счёта, ответ
|
||||
`data: null`, а `meta.unavailable` содержит все счета.
|
||||
- `NO_SOLUTION` бывает только в `return` (пользователя или индекса) и не
|
||||
- `NO_SOLUTION` и `AMBIGUOUS_RETURN` бывают только в `return`
|
||||
(пользователя или индекса) и не
|
||||
исключает счёт.
|
||||
- Если даты последних полных оценок оставшихся счетов `all` в Москве
|
||||
различаются, сводка целиком `data: null` с причиной
|
||||
`ACCOUNT_VALUATION_DATES_DIFFER` в `meta.unavailable` для каждого
|
||||
затронутого счёта: суммы разных дат не складываются.
|
||||
- `INDEX_HISTORY_UNAVAILABLE` бывает только в `benchmark` / `reference`.
|
||||
|
||||
### Расчёт
|
||||
|
||||
- **Период.** `from` — `historyFrom` (в `all` — минимальный по счетам
|
||||
результата). `to` — дата по Москве самого раннего `fetchedAt` среди
|
||||
счетов результата. `days = to − from` в календарных днях.
|
||||
- **Период.** `from` — дата первого внешнего потока по Москве (решение
|
||||
Change Owner: не дата открытия счёта). Полнота истории по-прежнему
|
||||
проверяется от `historyFrom`. `to` — дата по Москве `fetchedAt`; для `all` она общая
|
||||
только когда все включённые счета оценены в один московский день.
|
||||
`days = to − from` в календарных днях.
|
||||
`basis = annual` при `days ≥ 365`, иначе `period`.
|
||||
- **Агрегаты.** `netInvested = Σ потоков`,
|
||||
`currentValue = Σ totalsByCurrency[RUB]`,
|
||||
@@ -143,8 +155,10 @@ T-Bank не передаёт в операции счёт-контрагента
|
||||
- **XIRR.** Найти r из `Σ CFᵢ / (1 + r)^((dᵢ − d₀) / 365) = 0`, где
|
||||
пополнение входит как `−`, вывод — как `+`, а `currentValue` — как `+` в
|
||||
дату `to`. d₀ — дата первого потока. Корень ищется на `(−0.9999, 100]`.
|
||||
- Нет смены знака или корень не найден с точностью |NPV| ≤ 0,01 ₽ →
|
||||
`NO_SOLUTION`.
|
||||
- Нет корня или корень не найден с точностью |NPV| ≤ 0,01 ₽ →
|
||||
`NO_SOLUTION`. Если решений более одного — `AMBIGUOUS_RETURN`,
|
||||
произвольный корень не выбирается. Смена знака на концах диапазона
|
||||
не доказывает единственность решения.
|
||||
- Нет потоков → `NO_SOLUTION`.
|
||||
- Для `basis = period` значение равно `(1 + r)^(days / 365) − 1`.
|
||||
- **Теневой портфель.** Для каждого потока берётся закрытие индекса на дату
|
||||
@@ -168,12 +182,15 @@ T-Bank не передаёт в операции счёт-контрагента
|
||||
|
||||
- Хранится (индекс, торговый день по Москве) → точное CLOSE, upsert.
|
||||
Нерабочие дни не хранятся.
|
||||
- **Свежесть.** Догрузка нужна, если последняя сохранённая дата меньше
|
||||
`to`, а последняя попытка была больше часа назад. На каждый индекс
|
||||
- **Свежесть.** Расширение диапазона назад загружается сразу; хвост до
|
||||
`to`, где закрытия ещё нет, перепроверяется не чаще раза в час после
|
||||
успешной проверки; повтор после неудачной попытки — не чаще раза в час.
|
||||
На каждый индекс
|
||||
одновременно идёт не больше одной догрузки, повторные чтения ждут её
|
||||
результат. Первая загрузка начинается с `from − 10 дней`, чтобы было
|
||||
закрытие для потока в выходной.
|
||||
- **Достаточное покрытие.** Есть закрытие `≤` даты первого потока и
|
||||
- **Достаточное покрытие.** Есть закрытие `≤` даты первого потока,
|
||||
подтверждённый диапазон доходит до даты последнего потока и
|
||||
последнее закрытие `≥ to − 10` календарных дней. 10 дней покрывают
|
||||
новогодние каникулы биржи. Иначе `INDEX_HISTORY_UNAVAILABLE`.
|
||||
- **Лицензия.** ISS бесплатно отдаёт данные для ознакомления. Приложение
|
||||
@@ -186,7 +203,7 @@ T-Bank не передаёт в операции счёт-контрагента
|
||||
| --- | --- | --- | --- |
|
||||
| Нет сессии / нет подключения / счёт не найден или недоступен | Доступ | 401 / 503 `TBANK_NOT_CONNECTED` / 404 | Существующие сценарии счёта |
|
||||
| Причина недоступности счёта | Бизнес | 200, `data: null` или `excludedAccounts` | Исчезает после синхронизации или изменения данных |
|
||||
| XIRR не сходится | Бизнес | 200, `return.unavailableReason = NO_SOLUTION` | Нет |
|
||||
| XIRR не сходится или неоднозначен | Бизнес | 200, `return.unavailableReason = NO_SOLUTION` либо `AMBIGUOUS_RETURN` | Нет |
|
||||
| MOEX недоступен, сохранённой истории хватает | Технический | 200, полный результат | Догрузка при следующем чтении (не чаще раза в час) |
|
||||
| MOEX недоступен, истории не хватает | Технический | 200, `benchmark.unavailableReason = INDEX_HISTORY_UNAVAILABLE`, `series: []` | То же |
|
||||
| Сбой хранилища или непредвиденная ошибка | Технический | 500 Problem Details | Повтор из UI; другие вкладки не затронуты |
|
||||
|
||||
+4
-1
@@ -50,7 +50,7 @@ components:
|
||||
description: Точное десятичное значение без экспоненты и незначащих нулей
|
||||
Rate:
|
||||
allOf: [{ $ref: '#/components/schemas/Decimal' }]
|
||||
description: Доля, не проценты. 0.151 = 15,1 %. Округление до 6 знаков, half-even.
|
||||
description: Доля, не проценты. 0.151 = 15,1 %. Округление до 6 знаков, half away from zero.
|
||||
Money:
|
||||
$ref: 'openapi.json#/components/schemas/MoneyDto'
|
||||
UnavailableReason:
|
||||
@@ -60,8 +60,11 @@ components:
|
||||
- OPERATIONS_HISTORY_INCOMPLETE
|
||||
- NON_RUB_ACTIVITY
|
||||
- UNVALUED_SECURITIES_TRANSFER
|
||||
- UNCONFIRMED_TRANSFER
|
||||
- ACCOUNT_VALUATION_DATES_DIFFER
|
||||
- PORTFOLIO_VALUATION_INCOMPLETE
|
||||
- NO_SOLUTION
|
||||
- AMBIGUOUS_RETURN
|
||||
- INDEX_HISTORY_UNAVAILABLE
|
||||
description: |
|
||||
Новые значения MAY добавляться; клиент MUST показывать неизвестное
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ sequenceDiagram
|
||||
UI-->>User: Существующие состояния ошибки счёта
|
||||
else Доступ подтверждён
|
||||
API->>Store: Граница истории, покрытие операций, последняя полная оценка Portfolio
|
||||
Note over API: По каждому счёту: причины недоступности<br/>(HISTORY_START_UNKNOWN, OPERATIONS_HISTORY_INCOMPLETE,<br/>NON_RUB_ACTIVITY, UNVALUED_SECURITIES_TRANSFER,<br/>PORTFOLIO_VALUATION_INCOMPLETE)
|
||||
Note over API: По каждому счёту: причины недоступности<br/>(HISTORY_START_UNKNOWN, OPERATIONS_HISTORY_INCOMPLETE,<br/>NON_RUB_ACTIVITY, UNVALUED_SECURITIES_TRANSFER,<br/>PORTFOLIO_VALUATION_INCOMPLETE, UNCONFIRMED_TRANSFER)<br/>all: ACCOUNT_VALUATION_DATES_DIFFER<br/>XIRR: NO_SOLUTION / AMBIGUOUS_RETURN
|
||||
alt Не осталось счетов для расчёта
|
||||
API-->>UI: 200 {data: null, meta.unavailable: [...]}
|
||||
UI-->>User: «Доходность недоступна» + причина
|
||||
|
||||
Reference in New Issue
Block a user