feat(analytics): брокерские потоки по месяцам и ручные цены инструментов
GET /analytics/cashflow-broker читает metric_cash_flow_broker. POST
/instruments/{id}/prices апсертит price_manual по (instrument_id, d);
pricing/prices.py подмешивает его в ту же серию, что price_daily, с тем же
протягиванием и порогом устаревания.
This commit is contained in:
@@ -160,6 +160,68 @@ async def test_the_instrument_card_carries_lots_events_and_prices(
|
||||
assert body["prices"][0]["close"] == "110.0000000000"
|
||||
|
||||
|
||||
async def test_manual_price_fills_the_gap_and_shows_up_as_manual(
|
||||
client: AsyncClient, auth_headers: dict[str, str], portfolio: dict[str, int]
|
||||
):
|
||||
silent = portfolio["silent"]
|
||||
d = today_local().isoformat()
|
||||
|
||||
r = await client.post(
|
||||
f"/api/v1/instruments/{silent}/prices",
|
||||
headers=auth_headers,
|
||||
json={"d": d, "price": "1050", "currency": "RUB", "note": "ручная оценка"},
|
||||
)
|
||||
assert r.status_code == 201, r.text
|
||||
assert r.json()["price"] == "1050.0000000000"
|
||||
|
||||
r = await client.get(f"/api/v1/instruments/{silent}", headers=auth_headers)
|
||||
prices = r.json()["prices"]
|
||||
assert prices[-1] == {
|
||||
"d": d,
|
||||
"close": "1050.0000000000",
|
||||
"currency": "RUB",
|
||||
"accrued_interest": None,
|
||||
"source": "manual",
|
||||
}
|
||||
|
||||
# a second call for the same day replaces it rather than adding a row
|
||||
r = await client.post(
|
||||
f"/api/v1/instruments/{silent}/prices",
|
||||
headers=auth_headers,
|
||||
json={"d": d, "price": "1100", "currency": "RUB"},
|
||||
)
|
||||
assert r.status_code == 201, r.text
|
||||
r = await client.get(f"/api/v1/instruments/{silent}", headers=auth_headers)
|
||||
manual_prices = [p for p in r.json()["prices"] if p["source"] == "manual"]
|
||||
assert len(manual_prices) == 1
|
||||
assert manual_prices[0]["close"] == "1100.0000000000"
|
||||
|
||||
|
||||
async def test_manual_price_on_an_unknown_instrument_is_a_problem(
|
||||
client: AsyncClient, auth_headers: dict[str, str]
|
||||
):
|
||||
r = await client.post(
|
||||
"/api/v1/instruments/999999/prices",
|
||||
headers=auth_headers,
|
||||
json={"d": today_local().isoformat(), "price": "1", "currency": "RUB"},
|
||||
)
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
async def test_cashflow_broker_reports_the_months_money_moved(
|
||||
client: AsyncClient, auth_headers: dict[str, str], portfolio: dict[str, int]
|
||||
):
|
||||
r = await client.get("/api/v1/analytics/cashflow-broker", headers=auth_headers)
|
||||
assert r.status_code == 200, r.text
|
||||
rows = r.json()
|
||||
assert rows # the fixture's 20000 ₽ deposit lands in exactly one month
|
||||
total_deposits = sum(float(row["deposits_rub"]) for row in rows)
|
||||
total_withdrawals = sum(float(row["withdrawals_rub"]) for row in rows)
|
||||
assert total_deposits == 20000.0
|
||||
assert total_withdrawals == 0.0
|
||||
assert all(row["net_rub"] == row["deposits_rub"] for row in rows)
|
||||
|
||||
|
||||
async def test_events_can_be_filtered_down_to_the_external_flows(
|
||||
client: AsyncClient, auth_headers: dict[str, str], portfolio: dict[str, int]
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user