Commit Graph
32 Commits
Author SHA1 Message Date
buzzromainandClaude Opus 5 d7bf401cc7 fix(onchain-wallets): call transfers transfers, and drop the address swap (#3153)
* fix(onchain-wallets): call transfers transfers, and drop the address swap

Three things reported from real use.

**A transfer was announced as a purchase.** A movement was imported with
`activity_label: "Buy"/"Sell"` and named "Buy 1.5 FAKE" — but coins arriving at
an address were not bought there, and nothing here knows whether they were ever
bought at all. The trade shape stays, because it is what carries quantity and
cost basis in this ledger, but the label is now "Transfer" and the name is the
one the movement already had while it was unpriced. The old wording also made
the same event rename itself the day a price turned up for it.

Worth pairing with the change to trades/_header.html.erb, which until now read
the amount's sign and would still say "Buy" whatever the label.

**Changing a tracked address is gone.** It repointed the rows at a new address
while keeping their accounts, holdings and history — so trades reconstructed
from address A stayed under an account presented as address B. Its own help
text said so out loud: "The accounts, holdings and history stay as they are."
Removing the address and adding the new one is not just simpler, it is the only
one of the two that is honest, because it takes the old history with the old
address.

**The buttons follow the app's conventions now.** Actions that repeat per row
belong in a menu here — accounts/_account.html.erb renders its own that way —
not in a row of labelled buttons, which is what a provider panel does when it
has a single connection to act on. So the per-address actions are a menu, the
per-asset disconnect is icon-only, and the accounts-page card gains the actions
menu every other provider card already had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(onchain-wallets): give a tracked asset its icon on the accounts page

Account#logo_url asks its provider adapter for one, and ours answered nothing:
there is no institution behind a self-custody wallet, and nothing attaches a
file, so every tracked asset showed a blank where every other account shows an
icon. Fixing Security#crypto_base_asset covered the holdings list, which reads
the security directly — this is the other path, and it went through the adapter.

Built from the symbol rather than looked up. The accounts page renders one of
these per account, so resolving a Security each would be a query per row, and
the symbol is all Brandfetch's crypto endpoint needs. It answers nil without a
client id, which is the same nothing the page shows today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(onchain-wallets): follow the moved banner and the menu in the browser

The system suite still expected the settings panel to carry the read-only
reassurance, and to find "Review tokens" as a visible button. Both moved in the
previous commit: the banner into the linking modal, where the question it
answers is actually asked, and the per-address actions into a menu, as repeated
row actions are rendered everywhere else in this app.

Caught by CI rather than by me — the per-branch checks I ran covered
`bin/rails test` and not `test:system`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(onchain-wallets): pin the reassurance to the frame it moved into

The assertion proved the banner was somewhere on the page, which is exactly
what the change does not claim: the point is where it lives. Now it asserts the
text is absent from the settings panel and present inside the modal, so the
test fails if the banner drifts back or never arrives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(onchain-wallets): rename movements imported before transfers had a name

Review on #3153.

Wallets synced before this change keep their `Buy`/`Sell` labels and their
"Buy 1.5 shares of CRYPTO:BTC" wording, and nothing was rewriting them:
`perform_sync` returns early when no address changed on chain, and the
repair pass only ever looked at display-only `Transaction` rows. A cold
address would have shown the old wording indefinitely — which for a wallet
nobody touches is most of them.

The repair now relabels this processor's own trades too, scoped to its
`external_id` prefix and to `source: SOURCE` so a trade the user entered by
hand is never renamed. It runs from `perform_post_sync`, which is the pass
that already runs for every linked asset rather than only the changed ones.
Idempotent, so a nightly sync does not rewrite the same rows forever.

Separately: `Security.brandfetch_crypto_url` interpolated the symbol
straight into a URL path, and `Onchain::AssetSymbol.canonical` only upcases
and trims. An on-chain token can be called whatever its deployer chose, so
a slash pointed the path elsewhere on the CDN and a hash pushed the client
id into a fragment Brandfetch never sees. Guarded in the helper rather than
at the call site — six callers reach it from four providers.

Also from review: the icon test saves and restores
`Setting.brand_fetch_client_id` instead of hard-coding nil in its `ensure`,
which was erasing whatever the suite had configured.

The "one query" claim in a repair test's name was never asserted, and this
change adds a second query. Renamed to what it actually checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye

* test(onchain): drop the last change_address test with its feature

#3182 added a `change_address` test while this branch was removing the
feature it exercises. The rebase kept both, leaving a test calling a route
this branch deletes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 21:07:58 +02:00
buzzromainandClaude Opus 5 721fc394ec fix(securities): give prefixed crypto tickers their logo back (#3151)
Security#crypto_base_asset only stripped a fiat suffix — "BTCUSD" gave "BTC" —
so it answered nil for the "CRYPTO:BTC" form, which is the form every crypto
integration writes: the on-chain wallets, Kraken, CoinStats and Binance all
store the prefix. display_logo_url feeds that nil to the crypto branch, so none
of those securities carried a logo at all.

Provider::BinancePublic already parses every shape it accepts — the pair form,
the prefixed pair, the bare base asset and the USD stablecoins — so this
delegates instead of growing a second parser next to it. The parsing moves to a
class method for that; the private instance method stays as a delegator, so its
own callers and their tests are untouched.

Verified across the four documented forms: CRYPTO:BTC and BTCUSD both give BTC,
CRYPTO:ETH gives ETH, CRYPTO:USDT gives USDT.

A logo still needs BRAND_FETCH_CLIENT_ID configured — this fixes the parsing,
not the hosting requirement.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 06:12:24 +02:00
Artem DanilovandClaude 9de9a23ce2 feat(prices): add T-Invest (T-Bank) securities + brand-logo provider (#2408)
Adds Provider::TinkoffInvest, a token-based securities provider built on the
public T-Invest REST gateway (invest-public-api.tinkoff.ru/rest). It serves
prices for Russian instruments (shares, ETF/БПИФ, bonds) and, crucially, brand
logos via the T-Invest CDN — the authoritative logo source for MOEX
instruments, which ISS (MoexPublic) does not provide.

- Registry: register `tinkoff_invest` under the :securities concept; token via
  ENV TINKOFF_INVEST_API_KEY or encrypted Setting.tinkoff_invest_api_key.
- Logos independent of the price provider: Security#import_brand_logo consults
  T-Invest for a logo whenever a token is configured (after the price-provider
  metadata fetch, so it never short-circuits website_url backfill). Gated on
  token presence, not the securities checklist.
- display_logo_url: with no website domain, a stored provider logo (T-Invest)
  now beats the ticker-only Brandfetch lettermark; when a domain exists,
  Brandfetch still wins (unchanged).
- MoexPublic no longer reports moex.com as the issuer website — it's the
  exchange, not the issuer, and would make Brandfetch render the exchange logo
  for every instrument and shadow the real brand logo.
- Prices: GetCandles (daily, paged) + GetLastPrices; Quotation units+nano/1e9;
  bonds priced as percent-of-par x nominal (missing nominal raises, not 0).
- Settings: encrypted token field (always shown) + provider checkbox + en locale.
- Tests for search/info/logo-url/prices/bond/incomplete-candle and display logic.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-19 17:07:09 +02:00
dripsmvcp f7df709e6d fix(snaptrade): import non-primary-currency cash as cash holdings (#1979)
* fix(snaptrade): import non-primary-currency cash as cash holdings

Fixes #1809.

SnaptradeAccount#upsert_balances! picked a single cash entry (account
currency -> USD -> first) and stored only that in cash_balance; every
other currency's cash was discarded. A moomoo Canada account with CAD
$500 + USD $1000 imported only the CAD.

Persist the full balances snapshot (new raw_balances_payload column) and
surface each non-primary-currency cash entry as a synthetic per-currency
cash holding (Security.cash_for(account, currency:)), mirroring the
existing cash-security pattern. The primary currency stays in
cash_balance. HoldingsProcessor now also runs for cash-only balances, and
the Processor invokes it when there are holdings OR non-primary cash.
Cash holdings use a stable external_id so repeated syncs update rather
than duplicate.

* fix(snaptrade): encrypt raw_balances_payload and drop cash amount from log

Addresses PR #1979 review: Codex P1 (encrypt the newly persisted balances snapshot at rest, matching the other raw provider payloads) and CodeRabbit nitpick (do not log monetary amounts at info level).

* refactor(snaptrade): extract primary_cash_entry and harden balances test

PR #1979 review: extract the shared account-currency->USD->first cash selection into a private helper (CodeRabbit DRY nitpick); reorder the upsert_balances! test so the primary currency is not first, proving dig(:currency,:code) resolves it on string-keyed payloads rather than the entries.first fallback (jjmata).
2026-05-30 23:29:37 +02:00
soky srm dcebda05de Move back to brandfetch (#1427)
* Move back to brandfetch

* Update security.rb

* Update security.rb
2026-04-10 17:42:16 +02:00
soky srm 0aca297e9c Add binance security provider for crypto (#1424)
* Binance as securities provider

* Disable twelve data crypto results

* Add logo support and new currency pairs

* FIX importer fallback

* Add price clamping and optiimize retrieval

* Review

* Update adding-a-securities-provider.md

* day gap miss fix

* New fixes

* Brandfetch doesn't support crypto. add new CDN

* Update _investment_performance.html.erb
2026-04-10 15:43:22 +02:00
soky srm 7908f7d8a4 Expand financial providers (#1407)
* Initial implementation

* Tiingo fixes

* Adds 2 providers, remove 2

* Add  extra checks

* FIX a big hotwire race condition

// Fix hotwire_combobox race condition: when typing quickly, a slow response for
// an early query (e.g. "A") can overwrite the correct results for the final query
// (e.g. "AAPL"). We abort the previous in-flight request whenever a new one fires,
// so stale Turbo Stream responses never reach the DOM.

* pipelock

* Update price_test.rb

* Reviews

* i8n

* fixes

* fixes

* Update tiingo.rb

* fixes

* Improvements

* Big revamp

* optimisations

* Update 20260408151837_add_offline_reason_to_securities.rb

* Add missing tests, fixes

* small rank tests

* FIX tests

* Update show.html.erb

* Update resolver.rb

* Update usd_converter.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update _yahoo_finance_settings.html.erb
2026-04-09 18:33:59 +02:00
Serge LandClaude Haiku 4.5 ab9b97639b Record dividends and interest as Trades in investment accounts (#1311)
* Record dividends and interest as Trades in investment accounts

All investment income (dividends and interest) is now modeled as a
Trade with qty: 0 and price: 0, keeping security_id NOT NULL on trades
intact. Dividends require a security; interest falls back to a
per-account synthetic cash security (kind: "cash", offline: true) when
none is selected, matching how brokerages handle uninvested cash
internally.

- Add `kind` column to securities ("standard" | "cash") with DB check
  constraint; `Security.cash_for(account)` lazily finds or creates the
  synthetic cash security; `scope :standard` excludes synthetic
  securities from user-facing pickers
- Trade::CreateForm: new `dividend` type (security required); `interest`
  now creates a Trade instead of a Transaction
- Trade form: Dividend and Interest in the type dropdown with a security
  combobox (required for dividend, optional for interest)
- transactions table: untouched

* UI fixes

* HealthChecker — both scopes now chain .standard to exclude cash securities from provider health checks.

DB query moved to model — Account#traded_standard_securities in app/models/account.rb, view uses account.traded_standard_securities.

DRY income creation — create_income_trade(sec:, label:, name:) extracted as shared private method; create_dividend_income and create_interest_income delegate to it.

show.html.erb blocks merged — single unless trade.qty.zero? block covers qty/price/fee fields.

Test extended — assert_response :unprocessable_entity added after the assert_no_difference block.

* Hide cash account ticker from no-security trade detail

* Fix CodeRabbit review issues from PR #1311

- Remove duplicate YAML keys in translation files (de, es, fr)
- Add error handling for security resolution in create_dividend_income
- Extract income trade check to reduce duplication in header template

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* Include holdings in dividend/interest security picker

The security picker for dividend/interest trades should include all securities
in holdings, not just those with trade history. This fixes the issue where
accounts with imported holdings (e.g., SimpleFIN) but no trades would have an
empty picker and be unable to record dividends.

Uses UNION to combine securities from both trades and holdings.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* scope picker to holdings only (a trade creates a holding anyway)

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-29 10:08:54 +02:00
MkDev11andmkdev11 eeff4edbea Add warning for TwelveData plan-restricted tickers (#803)
* Add warning for TwelveData plan-restricted tickers

Fixes #800

- Add Security::PlanRestrictionTracker concern using Rails cache
- Detect plan upgrade errors during Security::Price::Importer sync
- Display amber warning on /settings/hosting with affected tickers
- Include unit tests for the new functionality

* Scope plan restriction cache by provider

Addresses review feedback:
- Cache key now includes provider name to support multiple data providers
- Methods now require provider parameter for proper scoping
- Added tests for provider-scoped restrictions
- Added documentation explaining instance-level API key architecture

* Fix RuboCop array bracket spacing

* Fix empty array bracket spacing

* Move plan upgrade detection to Provider::TwelveData

* Fix provider scoping tests to use direct cache writes

---------

Co-authored-by: mkdev11 <jaysmth689+github@users.noreply.github.com>
2026-01-27 15:45:50 +01:00
soky srmandJuan José Mata abab66675c Implement a setting to retrieve high res logos (#725)
* Implement a setting to retrieve high res logos

* Update _brand_fetch_settings.html.erb

* Add fallback for stock tickers also to use Brandfetch

* Update security.rb

* Update toggle logic for high-res logos setting

Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Update security.rb

* Update security.rb

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-01-21 17:16:51 +01:00
LPWandluckyPipewrench 8f04955e72 Move exchange MIC codes to config/exchanges.yml (#708)
* Move exchange MIC code mappings to `exchanges.yml` for better maintainability.

* fix: Use YAML.safe_load_file for exchange config

Addresses CodeRabbit security concern - YAML.load_file can deserialize
arbitrary Ruby objects. safe_load_file restricts to basic types only.

---------

Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
2026-01-19 19:28:16 +01:00
LPWandluckyPipewrench 237035c8d4 Improve convert-to-trade security selection with search-first UX (#703)
* Enhance security handling logic:
- Prioritize user's country in sorting securities and country codes.
- Add comprehensive mapping for MIC codes to user-friendly exchange names.
- Revamp combobox to consistently pull from a provider when available.
- Improve handling of custom ticker and exchange input fields.

* Localize securities combobox display and exchange labels.

---------

Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
2026-01-19 15:49:51 +01:00
soky srm 76dc91377c Merchants improvements (#594)
* FIX logos

* Implement merchant mods

* FIX confirm issue

* FIX linter

* Add recently seen merchants to re-add if needed

* Update merge.html.erb

* FIX do security check

* Add error handling for update failures.
2026-01-09 19:38:04 +01:00
Juan José Mata 54bc37a651 Remove Synth Finance references (#47)
* Remove Synth Finance integration

* Linter noise

* Fix failing (old) test, use it for Twelve Data

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
2025-08-02 00:28:55 +02:00
Zach Gollwitzer e4ee06c9f6 Security resolver and health checker (#2281)
* Setup health check

* Security health checker cron

* Use resolver throughout codebase

* Use resolver for trade builder

* Add security health checks to schedule

* Handle no provider

* Lint fixes
2025-05-22 12:43:24 -04:00
Josh Pigford a7438e5c78 Add country_code attribute to Security model and related classes
* Update Security model to include country_code in the data definition.
* Modify Provider::SecurityConcept to define country_code for security.
* Enhance Provider::Synth to extract country_code from security data.
* Update Security::Provided to include country_code when creating security instances.
* Adjust Security::SynthComboboxOption to add country_code as an attribute.
* Revise combobox_security partial to conditionally display country flag and code.
2025-05-22 09:45:08 -05:00
Zach Gollwitzer d8e058d7c6 Handle case sensitive values when creating securities 2025-05-08 14:31:43 -04:00
Zach Gollwitzer e657c40d19 Account:: namespace simplifications and cleanup (#2110)
* Flatten Holding model

* Flatten balance model

* Entries domain renames

* Fix valuations reference

* Fix trades stream

* Fix brakeman warnings

* Fix tests

* Replace existing entryable type references in DB
2025-04-14 11:40:34 -04:00
Zach Gollwitzer 29f445d75e Fix security search 2025-03-28 17:34:29 -04:00
Zach Gollwitzer f65b93a352 Data provider simplification, tests, and documentation (#1997)
* Ignore env.test from source control

* Simplification of providers interface

* Synth tests

* Update money to use new find rates method

* Remove unused issues code

* Additional issue feature removals

* Update price data fetching and tests

* Update documentation for providers

* Security test fixes

* Fix self host test

* Update synth usage data access

* Remove AI pr schema changes
2025-03-17 11:54:53 -04:00
Zach Gollwitzer fa0248056d Show UI warning to user when they need provider data but have not setup Synth yet (#1926)
* Simplify provider concerns

* Update tests

* Add UI warning for missing Synth key if family requires external data
2025-02-28 11:35:10 -05:00
Zach Gollwitzer 9138bd2b76 Allow offline trade tickers (#1925) 2025-02-28 09:34:14 -05:00
David Anyatonwu 32ef6ca154 Add exchange and currency fields to trade imports (#1822)
* Add exchange and currency fields to trade imports

* Add exchange_operating_mic support for trade imports - Added required columns and updated models

* refactor: remove exchange and currency columns

* fix: consolidate import schema and remove redundant columns

* feat: Enhance trade import with exchange_operating_mic support

* Revert changes to existing migration

* Simplify migration to use change method

* Restore previously deleted migration

* Remove unused import_col_labels method

* Update schema.rb after running migrations

* Update trade_import.rb and fix schema.rb with db:migrate:reset

* fix: improve trade import security creation

---------

Signed-off-by: David Anyatonwu <51977119+onyedikachi-david@users.noreply.github.com>
2025-02-24 10:00:24 -05:00
Josh Pigford 68d7cb5de6 Enhance security information retrieval and handling (#1826)
* Enhance security information retrieval and handling

- Add support for operating MIC codes in security info fetching
- Update security uniqueness validation to handle unknown securities
- Improve security creation and update logic in Plaid investment sync
- Update combobox and view components to handle operating MIC codes
- Add unknown flag for securities with incomplete information

* Update schema.rb

* Refactor the need for mic codes

* Don't fetch prices unless a security has the necessary mic code

* Deduplication

* Lint

* Update Securities and Plaid Investment Sync

- Modify PlaidInvestmentSync to return plaid_security for USD cash
- Add non-null constraint to Securities ticker column
- Update Securities fixture to use exchange_operating_mic instead of exchange_mic

---------

Signed-off-by: Josh Pigford <josh@joshpigford.com>
2025-02-11 10:40:30 -06:00
Zach Gollwitzer c3248cd796 Improve account transaction, trade, and valuation editing and sync experience (#1506)
* Consolidate entry controller logic

* Transaction builder

* Update trades controller to use new params

* Load account charts in turbo frames, fix PG overflow

* Consolidate tests

* Tests passing

* Remove unused code

* Add client side trade form validations
2024-11-27 16:01:50 -05:00
Josh Pigford cd91e66618 Initial pass at Synth-based ticker selection (#1392)
* Initial pass at Synth-based ticker selection

* Update _tickers.turbo_stream.erb

* Functional combobox display

* A few cleanup steps

* Linter

* Prevent long strings

* Another step towards functional combobox

* Deprecated files

* Custom Combobox implementation

* Lint

* Test suite fixes

* Lint

* Make direct use of mic codes

* Update splits

* Update trades_test.rb
2024-10-30 09:23:44 -04:00
Josh Pigford 490f44589e First pass at security price reference (#1388)
* First pass at security price reference

* Data cleanup

* Synth security fetching does better with a mic_code

* Update test suite

😭

* Update schema.rb

* Update generator.rb
2024-10-29 15:37:59 -04:00
Josh Pigford 7d8028b505 Stock filter (#1376)
* Initial pass at stock filtering

* Rough in filter

* Cleaning up security listing

* Tweak to search function

* Combobox tweaks

* Clean up search query

* Update trades test with combobox

* Update securities.yml
2024-10-28 15:49:19 -04:00
Josh Pigford 45935db5f3 Remove dependency on stock exchange table (#1368) 2024-10-25 13:09:02 -05:00
Zach Gollwitzer 4bfe47540d Basic trade and holdings view (#1271)
* Add trade view

* Lint fix

* Fix stale placeholder variable

* Add holding view
2024-10-09 14:59:18 -04:00
Zach Gollwitzer 453a54e5e6 Add security prices provider (Synth integration) (#1039)
* User tickers as primary lookup symbol instead of isin

* Add security price provider

* Fetch security prices in bulk to improve sync performance

* Fetch prices in bulk, better mocking for tests
2024-08-01 19:43:23 -04:00
Zach Gollwitzer 47523f64c2 Investment Portfolio Sync (#974)
* Add investment portfolio models

* Add portfolio to demo data

* Setup initial tests

* Rough sketch of sync logic

* Clean up trade sync logic

* Add trade validation

* Integrate trades into sync process
2024-07-16 09:26:49 -04:00