Криптовалюту теперь можно разделять на отдельные портфели (например,
долгосрочные инвестиции, накопления на недвижимость, спекулятивные
монеты) вместо одного общего списка — по тому же принципу, что уже
есть у счетов (Account): у портфеля есть имя, цветная метка и
архивация, каждая монета обязательно принадлежит одному портфелю,
один и тот же токен можно держать в нескольких портфелях сразу.
Matches CoinMarketCap's flow: clicking anywhere on a coin's row in the
holdings table opens its transaction history (previously only a dedicated
icon button did). The table's own History icon is gone since the row now
does that job; the remaining "+"/delete icons get stopPropagation so they
don't also trigger the row click.
Adds the missing piece: transactions could be viewed and deleted but not
edited. New PATCH /crypto/transactions/{id} (partial update, never calls
CoinGecko, same oversell guard as creating a sell) backs a pencil button on
each history row that opens CryptoTransactionModal pre-filled — the same
dialog used for "+", now doing double duty as create/edit.
86/86 backend tests passing (4 new). Verified live against the real API:
create, edit quantity/note, 404 on unknown id, cleanup — real user data
untouched throughout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY
Adds the analytics half of the CoinMarketCap reference: a value-over-time
chart (GET /crypto/history, forward-filled from AssetValuation like Net
Worth's own series but scoped to crypto assets), an allocation breakdown by
coin, and stat tiles for all-time profit, total invested (cost basis), and
best/worst performer.
Deliberately no "24h" range — history is only as dense as the sync cadence
(button or once/day), so a 24h chart would be one or two points, not a
smooth intraday line. Cost basis / all-time profit / best-worst performer
aren't separate endpoints: they're trivial to derive client-side from
fields already in GET /crypto/holdings, so no redundant backend logic.
Allocation card follows the dataviz skill's categorical rule: top 7 coins
by value get a fixed-order color from the app's already-validated 8-slot
ramp, an 8th+ folds into "Other" rather than generating a new hue.
82/82 backend tests passing (3 new for /crypto/history). Verified live
against the real CoinGecko API — and caught a real holding the user had
already added directly through the UI between messages, which the smoke
test correctly left untouched while cleaning up only its own test data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY
Replaces the plain quantity field with a CryptoTransaction log (same
"record events, derive the total" shape as Goal/GoalContribution): quantity
and average buy price are now computed via weighted-average cost — a buy
blends into the running average, a sell reduces quantity but leaves the
average cost of what's still held unchanged.
Switches price fetching from /simple/price to CoinGecko's /coins/markets
(still one batched call for every coin), which also returns 1h/24h/7d %
change in the same request. Old PATCH /crypto/holdings/{id} is replaced by
POST .../transactions (buy/sell, rejects selling more than held), GET
.../transactions (history), and DELETE /crypto/transactions/{id}.
Frontend: CryptoHoldingsTable replaces the simple list with a real table
(Name/Price/1h%/24h%/7d%/Holdings/Avg Buy Price/Profit-Loss/Actions),
horizontally scrollable on mobile rather than dropping columns. New
CryptoTransactionModal (buy/sell) and CryptoTransactionHistoryModal.
Backend: 79/79 tests passing (13 in test_crypto.py, up from 9). Also
verified live end-to-end against the real CoinGecko API with the user's own
key — create, buy, sell, oversell rejection, history, manual refresh, all
against real BTC/ETH prices in EUR — test data cleaned up after.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY
Track individual coins (identity + quantity) in a dedicated tab, backed by
CoinGecko's Demo API (AURUM_COINGECKO_API_KEY). A holding is just another
Asset under the hood (asset_class=crypto) with its value written as a
regular AssetValuation — the existing Net Worth aggregation needs zero
changes to pick it up.
Prices never refresh in the background (no scheduler in this stack, same
reasoning as recurring transactions): a "Refresh prices" button forces a
sync, and GET /crypto/holdings lazily re-syncs at most once every 24h.
CoinGecko outages leave existing values untouched (error_key: "unreachable")
instead of failing the request. Backup/restore now carries CryptoHolding
rows too, so a restore doesn't silently strip a holding's coin identity.
Backend: 75/75 tests passing (9 new), including a real identity-map
staleness bug the tests caught in the raw-Core-upsert + ORM re-read pattern,
fixed via populate_existing=True.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY