fix(tinvest): не принимать отменённую заявку за сделку
GetOperationsByCursor отдаёт отменённые заявки наравне с исполненными, а отменённая — это не сделка, которая сорвалась, а сделка, которой не было. Приходит она с quantity_done = 0, количеством, которое заявка только ПРОСИЛА, в quantity, и без платежа вообще, — а _operation при нулевом quantity_done откатывался на quantity. Покупка читалась как бесплатное приобретение: позиция росла, деньги не убывали. Это и есть четыре из пяти известных расхождений derived с брокерским снапшотом, а не корпоративные действия, как записано в AGENTS.md: SIBN6P4 +5, HEAD +2, FIVE +2, MGNT −1. В сырых операциях ровно десять строк с state = OPERATION_STATE_CANCELED, и разбивка по бумагам сходится с расхождением ровно. Пятое, FIVE→X5, действительно редомициляция: зачисления X5 нет ни в одной операции ленты, лот невыводим из данных. Фильтрует только event. Сырые остаются как есть — это журнал того, что отдал источник, и их отсутствие было бы собственной загадкой. Отсутствие state вообще считается исполнением: старый payload без поля не должен молча превращаться в отмену. Перечитывание окна теперь ещё и удаляет отменённую заявку, импортированную до этой проверки, — иначе плохая строка осталась бы в леджере навсегда.
This commit is contained in:
@@ -43,6 +43,7 @@ class Item:
|
||||
description: str
|
||||
quantity: int
|
||||
quantity_done: int
|
||||
state: Any = None
|
||||
|
||||
|
||||
def item(**over) -> Item:
|
||||
@@ -126,3 +127,23 @@ def test_kind_falls_back_to_shape_when_unstated():
|
||||
maturity_date: datetime
|
||||
|
||||
assert _kind_of(Bond(datetime(2030, 1, 1, tzinfo=UTC))) == "bond"
|
||||
|
||||
|
||||
class State(enum.Enum):
|
||||
"""Stands in for the SDK's OperationState."""
|
||||
|
||||
OPERATION_STATE_EXECUTED = 1
|
||||
OPERATION_STATE_CANCELED = 2
|
||||
|
||||
|
||||
def test_state_is_carried_so_the_ledger_can_refuse_a_cancelled_order():
|
||||
"""A cancelled order arrives looking like a free acquisition — see `sync._is_executed`."""
|
||||
cancelled = op_of(state=State.OPERATION_STATE_CANCELED, quantity_done=0, quantity=2)
|
||||
assert cancelled.state == "OPERATION_STATE_CANCELED"
|
||||
# quantity still reads as what the order ASKED for, which is exactly the trap
|
||||
assert cancelled.quantity == Decimal(2)
|
||||
|
||||
|
||||
def test_state_is_empty_when_the_record_does_not_state_one():
|
||||
"""An older payload without the field must not be mistaken for a cancelled order."""
|
||||
assert op_of().state == ""
|
||||
|
||||
Reference in New Issue
Block a user