Skip to content

Track crypto held on exchanges and wallets by hand - #32

Merged
joaoguimaraespro merged 1 commit into
mainfrom
feat/manual-crypto
Oct 4, 2026
Merged

joaoguimaraespro merged 1 commit into
mainfrom
feat/manual-crypto

Conversation

@joaoguimaraespro

Copy link
Copy Markdown
Owner

Summary

Track crypto held on Binance, in a cold wallet or anywhere else, without connecting to any of them, the way Getquin does. On Portfolio → Add crypto you enter the coin (searchable by name or ticker), quantity, average buy price (€), where it is held (free text, with your existing locations suggested), an optional "held since" date and notes. The app fetches prices itself.

  • Data model: investments.manual_holdings and holding_rewards (EF migration). Each location is a read-only finance account of kind Broker (institution Manual (crypto)). The holding is the source of truth. Its position (DataSource.Manual), one opening lot (a buy plus the money put in) and its rewards are rewritten from it on every change. Portfolio, Accounts, Net worth and dashboards pick it up with no special cases. The same coin in two locations shows as one line with one holding per location. An emptied location is archived and hidden.
  • Prices: coin search uses the Yahoo search endpoint and keeps only crypto results. The listing is the EUR pair (BTC-EUR), else the USD pair (KAS-USD), and is cached in price_listings. USD closes are stored in EUR at the ECB rate of the day. Recent closes are re-downloaded at most every 15 minutes, when the portfolio is opened and by the 6-hourly snapshot job. Today's bar is the live price, and "Today" compares it with yesterday's close, weekends included. Price precision is widened to 12 decimals for coins worth fractions of a cent.
  • History: after any change, the location's snapshots are rebuilt from daily closes since "held since", so the chart never jumps on an edit.
  • Rewards: staking, Earn and airdrop rewards add coins at zero cost on the day received. They count as income, valued at that day's close, under "Dividends & rewards".
  • UI: a "manual" badge on the position, the location shown in the expanded holdings, and per-holding edit and delete with a rewards section. Field errors are friendly and translated (EN and PT-PT). No horizontal page scroll at 390px.

Privacy

Only the text typed in the coin search, the coin's listing symbol (e.g. BTC-EUR) and date ranges leave the server. Quantities, prices paid, locations and notes never do. No exchange or wallet is contacted.

Test plan

  • dotnet build: 0 warnings
  • Investments.Tests (59): domain valuation/P&L, reward valuation, 24/7 day change, Yahoo coin search, EUR→USD listing fallback, small-price precision (recorded fixtures)
  • Architecture.Tests (13)
  • Integration.Tests: ManualCryptoTests covers create, then positions, summary, net worth and rebuilt performance history with a fake price source, then edit, reward income, remove reward and delete. PortfolioTests lists the new write endpoints explicitly. Full suite: 202 passed. ImportTests.Hostile_cell_text_is_imported_as_inert_data fails when it runs after AiGatewayTests, which creates the same text; that ordering problem is in the existing tests, not this change.
  • web npm run build, ng test (51)
  • Playwright screenshots with a mocked API: 1280 light and 390 dark (page, add dialog, edit dialog, validation errors, expanded holdings)

Limitations

  • Average price is in EUR only.
  • Yahoo covers the major coins and many small ones, mostly as USD pairs only. A coin Yahoo doesn't list can't be added. With MARKET_DATA_PROVIDER=none, coins can't be added at all.
  • An edit rewrites history as if the current quantity had been held since "held since". There are no per-lot transactions.
  • Rewards count once as income and also sit in the unrealised gain, since their cost is zero. Total return counts them once.
  • The monthly and annual ledger reports don't include investment income (dividends weren't included before either).

Coins are entered on the portfolio page (coin, quantity, average buy price in
EUR, location, held since, notes) and priced from public Yahoo quotes: EUR pair,
else USD pair converted at the ECB rate. Each location is a read-only broker
account, so portfolio, accounts, net worth and dashboards include it. Prices
refresh at most every 15 minutes when the portfolio is opened and in the daily
snapshot job; today's change uses yesterday's close (24/7 market). History is
rebuilt from daily closes after each edit. Staking/Earn/airdrop rewards add coins
at zero cost and are reported as income next to dividends.
@joaoguimaraespro
joaoguimaraespro merged commit 150403c into main Oct 4, 2026
7 checks passed
@joaoguimaraespro
joaoguimaraespro deleted the feat/manual-crypto branch October 4, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants