Changelog

What changed in Portfolio Analytics, newest first. Each entry has a link of its own, and the same entries are in an Atom feed.

Pro opens

An optional paid plan with more alerts (new dividends, big daily moves, 52-week highs and lows), a weekly or monthly report by email, Compare with up to five symbols and saved comparisons, a yearly statement for your accountant, a read-only link to one portfolio, two benchmarks at once or any symbol as a benchmark, a rebalance calculator, and dividend dates and alerts in your own calendar app. What was free that day stayed in the free plan at the launch. Accounts from before Pro are early members for a year. Members can also earn Pro days, for example by inviting a friend.

Idle CPU, fixed

The app no longer animates anything while it sits idle, and it redraws a chart only when a figure in it has changed. A tab in the background stops its timers until it is seen again.

A new contact address

The site, the privacy policy and the Terms now give [email protected] as the address to write to. The privacy policy and the Terms are dated 9 November 2026, and the date they take effect is unchanged.

Portfolio groups

Several portfolios can be put in a group and viewed together, with the group's combined figures beside each portfolio's own. Groups are free.

Annualized return across portfolios

The All Portfolios card on the Overview shows each portfolio's annualized return and one for all of them together. The return is now worked out in one currency when a portfolio's money moved in more than one.

The privacy policy names Cloudflare Turnstile

Create account and Forgot password are protected by Cloudflare Turnstile, which checks that a person is using the form, and the privacy policy now says so. Forgot password also sends one address at most one reset email a minute and three a day.

One font

Every page now uses Public Sans alone, for figures and tickers too.

Android 1.20

The Android app's new version supports the Cloudflare Turnstile check on Create account and Forgot password.

Starter portfolios

A new portfolio can start from one of three templates, European dividends, World ETF core or Growth basket, as well as from the demo portfolio.

Two analytics services in the privacy policy

Yandex Metrica runs on the public pages after you accept analytics cookies, and Cloudflare Web Analytics counts page views without cookies. The privacy policy names both.

Compare in the menu

Compare has its own place in the menu, under Research after Markets, and a row on the phone's More screen.

Languages, easier to cite

llms.txt now names the 29 languages by their own names with the code the site uses, gives one sentence about the product in each of them, and says at the top that every page reads in all 29 on the same URL.

The bell keeps a week

Notifications in the app are deleted automatically after 7 days, read or not. Event alerts are kept 90 days so the calendar feed can still show them, and billing notices are kept until the account is deleted. The privacy policy says so in its retention table.

Faster deletion

An account a member asks to delete is now wiped 24 hours after the request instead of 7 days, and signing in within those 24 hours still cancels it. An account whose email address is never verified is deleted 7 days after sign-up, without an email to the unconfirmed address. The Terms (11.1, 11.3) and the privacy policy say so.

New address

Portfolio Analytics now lives at https://www.portanalytic.com. The address it had before, portfolio.ceciltan.com, redirects to it page for page, so an older link or citation still arrives at the same page.

Our own translations

The Google Translate element is gone. Each of the 29 languages now has its own translation file, written for this product, hosted on this site and applied in the browser, so no page text is sent to a translation service. The administration screens stay in English. The privacy policy, the FAQ and this file say so.

A personal import template within 24 hours

A broker file that will not import is no longer a dead end: the import dialog offers to send it, and so does Settings under Import help, at any time. The site owner builds an import template for that file within 24 hours, the member is told in the app and by email, and the same file then imports like any other. The owner sees waiting files first, counted against the 24 hours. A sent file is readable only by the owner, kept until the template is ready and deleted 7 days after, 30 days at most, as the privacy policy now says. Alongside: an import the server refuses opens the same help instead of a passing error, the help no longer claims a file never leaves the computer (a copy is kept for 7 days to diagnose failures, as the privacy policy has said since 2026-09-13), and the public pages now name all 32 recognized brokers instead of two.

One design

Members chose the new design, so the previous one is retired: /classic now leads to the app, the switch in the header is gone, and so is the administrators' report of who used which. Nothing about anyone's data changes. Also: a member who is already signed in and opens the site goes straight into the app instead of stopping at the landing page, and Stock detail's period row keeps to one line on a phone by leaving out the least-used periods.

Both designs under one roof, and imports that do not repeat themselves

The previous design is no longer a separate application: it is served by this one at /classic, sharing the API, the database and the session, with a button in each header moving between them and the choice kept on the account rather than in the browser. Nobody is moved without asking, and nobody has to stay. One sign-in now serves both, so the handoff that used to carry a session between two separate applications is no longer needed here. It still stands for the older deployment, with the hole it opened closed: the ticket it mints is a credential, and the link carrying it can no longer be pointed at an arbitrary host. Importing the same broker statement twice no longer doubles a portfolio: every import is compared against what is already held and writes only what is new, counting rather than presence, so two identical fills on one day survive while a re-imported year does not repeat. The result says how many rows were added and how many were already there. Smaller, in the same period: Stock detail's symbol box became the same search-and-pick the transaction form uses. The transaction filter row gives its accent to Add transaction rather than to whichever filter is showing. A deleted account now receives a written confirmation of what was removed. A new account is seeded with five watchlist symbols instead of eleven, because a worked example is not a starting portfolio, and two translation faults were fixed, the Allocation total being translated into a phrase four times its length and spilling across the donut, and the donut's tooltip being the one piece of chart text a translator cannot reach, which now takes its wording from the legend beside it and wraps to the canvas it is painted in. Also: the statement file itself is now kept for a week. Imports are parsed in the browser, so a file that would not parse used to leave nothing behind but a report that it had not worked. The file is now stored when it is chosen, alongside what became of it, and deleted automatically after seven days. It is readable only by the site owner, goes immediately with a deleted account, and is never written to the request log. Recorded in the privacy policy, in both the data-collected list and the retention table.

The v3 redesign

Every screen of the app was rebuilt in a new design: a sidebar grouped into Portfolio, Income, Research and Planning, a gold three-bar mark with a new favicon and app icons, and the public pages in the same palette. Screens were renamed to say what they hold: Common became Overview, Diversification became Holdings & Allocation, Growth became Performance, AI Prediction became AI Screener, and Users became Administration. A search box in the header (Ctrl+K or Cmd+K) opens any symbol. The Overview gained period pills from 1W to ALL that switch the KPI cards to that range's figures, an Unrealized G/L card, Portfolio Goals, an all-time return banner and the next dividend. Holdings & Allocation has Allocation, Holdings and Map views. Performance has Growth, Benchmark and Risk views: a plain verdict against a benchmark you pick, contributions against market return, monthly returns, an all-time tile per holding, and Year by year with a dividends column. Markets became a quote board with the day's line on each card, Most held by members as a share of members, and currencies and commodities. The chart and research cards open from any symbol. Dividends became one card with the 12-month forecast after tax, the yields, the dividend tax setting and a payment calendar whose months name the companies behind them. Metrics shows the best and worst movers as bars. The Watchlist gained P/E and sell-target columns. Independence puts the assumptions beside the answer with three what-ifs. The AI Screener is a rules card, the signal counts and the screen beside Recently cheaper. Administration lists members with their portfolios, symbols and last 30 days of activity. Every figure explains itself on hover, focus or tap, long lists show ten rows and scroll under a sticky header, Transactions gained a search box, sign-in is two panels with Continue with Google, and Settings is its own screen. Not carried into the new screens: the X-Ray Sankey, the country and asset-class allocation views, the chart / heatmap / details switch for holdings performance, the Market Indices sparkline panel (and with it the US 30-year yield), the Largest companies growing sales screen and the Independence per-symbol return table. Every capture on this site was retaken from the new design. Under the hood: the database pool fails fast and caps each query, a busy database answers 503 and the app retries reads, auto-update runs at most once per portfolio every four minutes, and the dashboard reuses a build when nothing it reads has changed (an All Portfolios refresh went from 53 queries to 2). Images and the favicon are now cache-busted by content hash, like scripts and styles.

Broker statement import

, verified against real exports. Upload an XTB account statement (.xlsx) or an Interactive Brokers activity statement (.csv) exactly as the broker exports it and the Import modal recognizes and converts it automatically: nothing to map, a full preview before anything is written. XTB files are unzipped and parsed entirely in the browser. Buys and sells take their shares and price from XTB's own trade comments (partial fills included), dividends arrive with withholding tax folded in as the fee, France's financial-transaction tax lands on the purchase it was charged for, Romanian dividend tax folds into the dividend it names, Nordic class shares map to Yahoo's dashed tickers (NOVOB.DK becomes NOVO-B.CO), and the portfolio adopts the statement's account currency. IBKR imports take dated trades with real commissions, map each symbol by the statement's own listing-exchange table (Paris, Xetra, Frankfurt and US listings all land on their true Yahoo symbols), skip forex, crypto and subscription-rights rows with a note, and adopt the base currency. Uploading IBKR's PortfolioAnalyst report (which contains no dated trades) shows an in-modal explanation of which report to download instead, including IBKR's 365-day statement limit. Long statements upload in slices under one confirmation, the Import button freezes immediately so a double click cannot import twice, and disabled buttons now look disabled app-wide. In the same period: every portfolio gained a currency picker (create and edit), a mixed-currency portfolio now reports its combined totals in its own currency instead of preferring USD, and the app updates itself after a deploy: the page reloads once when a new version takes control, and re-checks whenever the app returns to the foreground, so nobody needs to close and reopen it twice.

Per-stock research cards

on the Markets tab, following whatever symbol the chart holds: a Dividends card (consecutive-years growth streak, payment frequency, payout ratio, most recent payment and a per-share-by-year bar chart covering every payment on record, split-adjusted, with the running year shown but never counted toward the streak), a Business Quality card (revenue trend and CAGR, gross/operating/net margin and ROE history per fiscal year, debt/equity, debt/EBITDA and current ratio, each scored Meets / Borderline / Misses against thresholds printed on the card), and an interactive DCF fair-value calculator seeded from reported free cash flow, share count and net debt, recomputing live as the reader edits growth, WACC, terminal growth and the 5- or 10-year horizon. Funds and ETFs say honestly that they have no statements. Negative free cash flow refuses a DCF rather than inventing one. Cross-currency listings convert or say no rate exists. The dashboard gained a Markets pulse card (S&P 500, Nasdaq 100, Dow, Russell 2000, Gold, Brent, Bitcoin, EUR/USD, one cached request for all eight) and a Most held by members card: anonymous distinct-user counts of the ten most widely held symbols, minimum two members per symbol so no individual's holding is exposed, with the starter portfolio every account is seeded with excluded so the list reflects real buying. Brent, Bitcoin and EUR/USD joined the Market Indices sparkline grid. Under the hood in the same period: prices now reach the screen on their own (the backend refreshes each symbol once for everyone. The page follows with a silent conditional poll about once a minute, and the "45 prices updated" popup is gone). A closed exchange is no longer polled outside its own trading hours or on weekends while crypto keeps refreshing around the clock. The Day Change column shows the current trading day only, reading zero when a market has not traded today. The holdings table lists sold-out positions after the open ones so its Realized G/L total finally matches the dashboard, the yearly table and the All Portfolios view. Self-service backup arrived in Settings (download everything as one JSON, restore it yourself, merge-only). The Independence path chart's past-year bars and tooltips compare the assumed return against what each year actually delivered, with a worked example under the chart, and the monthly-returns chart and path chart both survive legacy history snapshots that carried no value, instead of showing 0% months and a fake −100%.

2026 design refresh applied across the app and the site

The app moved to a dual-theme token design (Instrument Sans + IBM Plex Mono). The public pages default to dark with a theme toggle in the nav, shared with the app's own toggle. The Overview gained a hero row (portfolio names selector with a portfolio-count pill, the big total value with a today-change chip, a refreshed-ago stamp, Import CSV and Add transaction buttons), a quick-numbers strip (all-time total return incl. dividends, money-weighted IRR vs the NASDAQ 100's 1-year return, today's top gainer) and an Allocation card whose donut shows the biggest positions plus one folded "others" slice, with the position count and currency count in the hole. The Watchlist gained a sidebar with the recent-alerts feed and a "Set a target" editor, one signed Distance column, Notify and Status columns. The Independence tab became a planner (path chart, coverage donut, what-if rows, 0-50% return slider). The AI Prediction tab was restructured: a "N symbols checked" summary donut with the buy candidates in its centre, a compact recently-cheaper list, and a Holdings / Passes now / Recently cheaper switcher over the scored tables, plus a per-holding revenue-growth chart coloured by signal. A Settings page (opened from the user menu) now fronts the existing profile, 2FA, password, data-export and delete flows behind a sectioned sidebar. The Cash tab's All Portfolios view now lists every portfolio's entries instead of asking the reader to pick one. Every marketing capture on this site was retaken from the refreshed app.

Price refresh made shared rather than per-user

Every signed-in browser used to fetch every one of its symbols independently every five minutes, so five people holding the same stock caused five identical market-data requests, and each one pulled five years of daily history when only the latest price was needed. Quotes are now fetched once and shared: whoever refreshes first pays for it and everyone else reads the result, simultaneous refreshes share a single request, and the full five-year history is pulled only when a symbol is new or once a day, since it exists to backfill dividends. Measured with five people online and ten holdings, one refresh cycle went from 50 requests and 6.59 MB to 10 requests and 0.03 MB. The All Portfolios view also now loads every portfolio's transactions in one request instead of one per portfolio, and a sector lookup that had been failing on every refresh since Yahoo began requiring authentication was replaced with the working, cached one.

Fixed the Diversification tab re-querying Yahoo for sector and country data every few minutes, forever

It treated "has no sector" as "not looked up yet", but a fund or ETF has no sector by nature, so those holdings never stopped counting as missing. Lookups are now remembered whether or not they found anything, funds are labelled as such instead of falling through as uncategorised, and a symbol that genuinely cannot be classified is retried monthly rather than on every page load. Also fixed the audit list endpoints returning a server error instead of the first page when the page number was not a number.

New Largest companies growing sales table at the top of the AI Prediction tab, ranking a tracked universe of the world's biggest listed companies by market value and keeping only those growing revenue past a chosen threshold, with filters for count, threshold, per-year or total growth, period and region

Building it surfaced a currency defect that also affected the existing screen: London quotes in pence and the code treated "GBp" as "GBP", overstating every UK company a hundredfold, which put HSBC and AstraZeneca at the top of a market-cap ranking and skewed their price-to-sales and price-to-book ratios. Quotes in a minor unit (London pence, Johannesburg cents, Tel Aviv agorot) are now converted to the major unit where the quote enters the system.

Dividend tax is now set per account

on the Dividends tab, starting from the administrator's 30% default. One rate for everybody was wrong by construction: withholding follows the investor's tax residence and treaty position. Saving a new rate re-derives the withholding already recorded against that account's dividends, so the whole history moves rather than just what gets added next, and every dependent figure follows. Dividend fees entered by hand, and those imported as real figures from a broker CSV, are marked and deliberately excluded, so a rate change cannot overwrite genuine per-payment withholding. The app reports how many rows it changed and how many it left alone. The Dividends tab now shows after-tax yield and projected income, with the gross figure alongside, which was not possible while the app had no per-investor rate.

New Independence tab after AI Prediction: the portfolio you need before its returns cover your monthly expenses, how far the current holdings get you, and a per-symbol return table over the full life of every position, closed ones included

The target counts the market value of the holdings only, because cash does not earn the assumed return. Separately, P/E is now shown for stocks whose trading currency differs from their reporting currency. It used to be blanked for all of them, which emptied the column for most non-US listings (Novo Nordisk trades in USD and reports in DKK, BYD trades in EUR and reports in CNY). The earnings figure is now converted into the trading currency instead, and the ratio is only withheld when no exchange rate is available or the company is loss-making. The same applies to price-to-sales and price-to-book.

The /demo walkthrough now carries a numbered "what you are looking at, top to bottom" guide beside every capture, naming each block of the screen in the order it appears, plus cross-links between related screens

Structured data added for the page (ImageGallery with a caption per screen, BreadcrumbList), and Open Graph / Twitter cards added to /demo, /about, /privacy and /terms, which previously shared as a blank card. The WebApplication screenshot list and the image sitemap were repointed from the old cropped section images to the full-page captures the pages actually render, and the FAQ schema was re-synced with the visible FAQ after the landing rewrite.

Landing page rebuilt around a conversion audit

New headline ("Every holding, every currency, one honest number"), the live instrument rail moved to second on the page, "How it works" rebuilt as a numbered three-step sequence with a realistic effort estimate, a new "Check the maths" section working a position out by hand and naming the three reasons a broker statement can differ, a comparison table against paid trackers, and a security argument moved out of the About page onto the landing page where the buyer sees it. The lead-capture panel now really sends: subscribing emails the CSV starter template, and every email carries a signed one-click unsubscribe (RFC 8058 List-Unsubscribe-Post) with no stored token.

Translation extended to the whole site and tied to the account

The signed-in dashboard is now translated too, which required serving /app without inline-script hash pinning (the Google widget injects an inline script, and per CSP3 a listed hash makes 'unsafe-inline' a no-op, so the two cannot coexist). This was an explicit product decision with two consequences, both documented in the privacy policy: /app loses that layer of defence in depth, and the app's interface text is sent to Google. Protection inside the app was widened to match: instrument and portfolio names by selector, plus a content rule that stamps any leaf element carrying a digit and almost no letters, so an unclassed <td> holding a currency amount cannot slip through. ECharts containers are stamped whole because their tooltips are DOM built faster than a MutationObserver can react. The chosen language is stored on the user (users.language) and read back through GET/PUT /api/i18n/preference, so it follows the account to any browser. The account value wins over the browser's copy, except for a choice made while the request was still in flight.

Free translation added

A globe button with country flags renders the public pages in 29 languages using the Google Translate element, driven by our own switcher. The widget script loads only when a reader opens the menu. Value and identity elements are marked translate="no" so tickers, currency codes, percentages, amounts, portfolio names and the product name survive both this translator and the browsers' own. The hero headline, which is split into one span per word for its animation, is merged into a single sentence before translating so it is not translated word by word. The desktop nav folds into the burger menu automatically when translated labels no longer fit. Reflected in the WebApplication schema (availableLanguage), the PWA manifest and service-worker precache, ai:languages metadata, the privacy policy and this file.

An earlier build of this feature used the browser's on-device Translator API. It was replaced because per-text-node translation cannot produce a sentence: the hero came back as word salad and one-word nav labels were translated in the wrong sense ("Features" became "facial features" in Russian).

Landing page: instrument ticker is now a single continuous live line driven by real Yahoo Finance quotes through a public, allow-listed quote endpoint (refreshes periodically)

Feature grid expanded to nine cards (added "Benchmark vs the market"). "How it works" condensed to three steps. Hero gained four more live KPI chips (Total Return, Yield on Cost, Today's P&L, vs NASDAQ 100). Accessibility and performance pass: corrected heading order, explicit image dimensions, asynchronous web-font loading, deferred analytics off the critical path, and minified landing CSS.