<!doctype html><html><head>
<title>Portfolio Analytics Lab</title>
<link rel="icon" type="image/png" href="/static/brand/portfolio_analytics_lab_icon.png">
<script src="/plotly.js" onerror="this.onerror=null;this.src='https://cdn.plot.ly/plotly-2.35.2.min.js';"></script>
<link rel="stylesheet" href="/static/css/app.css"></head><body>
<div class="top"><img class="brand-icon" src="/static/brand/portfolio_analytics_lab_icon.png" alt="Portfolio Analytics Lab icon"><div><h1>Portfolio Analytics Lab</h1><p>Local dashboard for Trading212 plus user-managed manual broker inputs.</p></div></div>
<div class="layout"><aside class="side">
<div class="card"><h3>Start / Settings</h3><p class="note">Try the demo first, or paste your Trading212 API credentials when you are ready. API keys are not saved by this local app.</p>
<button class="btn2" onclick="installDemoData()" style="width:100%;margin-bottom:10px">Try demo data</button>
<label>Restore local backup</label><select id="backup_restore_select"><option value="">No backups found yet</option></select>
<button class="btn2" onclick="restoreSelectedBackup()" style="width:100%;margin-top:8px;margin-bottom:10px">Restore selected backup</button>
<label>Trading212 API key</label><input id="api_key" type="password" placeholder="Paste your personal read-only API key">
<label>Trading212 API secret</label><input id="api_secret" type="password" placeholder="Paste secret only if your API key requires it">
<label>Data refresh mode</label><select id="data_mode"><option value="refresh_current_year" selected>Refresh current year only - updates 2026/current-year history and current positions; first run loads full history if no cache exists</option><option value="cache_only">Use local cached data only - no Trading212 calls</option><option value="full_refresh">Full refresh - reload all available history from Trading212, slower</option></select>
<label>FX conversion mode</label><select id="fx_mode"><option value="online_or_cache">Use latest FX if needed - recommended</option><option value="cache_only">Use cached FX only - best for offline testing</option><option value="off">Turn FX conversion off - raw/native values may be incomplete</option></select><p class="note">Local data persists in this app folder under <b>data/</b>, <b>output/</b>, and <b>mapping/</b>. Copy those folders when moving versions.</p>
<button onclick="runAnalysis()" style="width:100%">Run / Refresh Dashboard</button><p class="smallnote">Demo data is synthetic. Existing local folders are backed up before demo data is installed. Use Restore selected backup if you want to return to a previous local cache/manual-input state without another API refresh.</p></div>
<div class="card"><h3>Status</h3><div id="status" class="note">Waiting for first run.</div><br><div id="logs" class="logbox"></div><br><button class="btn2" onclick="loadData()">Load dashboard data</button></div>
<div class="card"><h3>User display settings</h3><p class="note">These settings are saved only in this browser on this computer. They do not change your data.</p><label>Accent color</label><select id="accent_color" onchange="saveUiSettings()"><option value="#3b82f6">Blue</option><option value="#8b5cf6">Purple</option><option value="#059669">Green</option><option value="#f97316">Orange</option><option value="#e11d48">Rose</option></select><label>Dashboard background</label><select id="theme_style" onchange="saveUiSettings()"><option value="navy">Navy dark</option><option value="black">Pure black</option><option value="graphite">Graphite</option><option value="purple">Deep purple</option><option value="light">Light clean</option><option value="paper">Warm paper</option><option value="sky">Light blue</option></select><label>Plot color palette</label><select id="plot_palette" onchange="saveUiSettings()"><option value="default">Default</option><option value="vibrant">Vibrant</option><option value="soft">Soft</option><option value="warm">Warm</option></select><label>Table height</label><select id="table_height" onchange="saveUiSettings()"><option value="340px">Compact</option><option value="420px" selected>Default</option><option value="560px">Tall</option></select><label>Chart source tables</label><select id="show_chart_sources" onchange="saveUiSettings()"><option value="hide" selected>Hidden by default</option><option value="show">Show below each plot</option></select><p class="note">Chart source tables are hidden by default. When shown, they are editable local what-if overrides for testing and transparency. For the local Windows test app, display settings use browser local storage, not user login.</p></div>
<div class="card"><h3>Contact / Community</h3><p class="note">Join the community for feedback, bug reports, setup questions and feature ideas.</p><div class="community-links"><a class="btn btn2" href="https://discord.gg/MV3jh5u9N" target="_blank" rel="noopener">Join Discord</a><a class="btn btn2" href="https://t.me/+r4th8iGgn5hhYmZk" target="_blank" rel="noopener">Join Telegram</a></div><p class="smallnote">When reporting an issue, mention the tab, what you clicked, and attach a screenshot if useful. Do not send API keys or private full exports unless you intentionally choose to.</p></div>
<div class="card"><h3>Buy me a coffee</h3><p class="note">If you found value using the tool, a coffee helps keep it going.</p><label>Donation option</label><select id="donation_option" onchange="renderDonationDetails()"><option value="">Select an option</option><option value="buymeacoffee">Buy Me a Coffee</option><option value="btc_lightning">BTC Lightning</option></select><div id="donation_details" class="explain" style="display:none;margin-top:12px"></div></div>
</aside><main class="main">
<div id="msg"></div>
<div class="tabs">
<button class="tabbtn active" onclick="tab('intro',this)">Intro</button><button class="tabbtn" onclick="tab('overview',this)">Overview</button><button class="tabbtn" onclick="tab('holdings',this)">Holdings</button><button class="tabbtn" onclick="tab('dividends',this)">Dividends</button><button class="tabbtn" onclick="tab('pnl',this)">PnL</button><button class="tabbtn" onclick="tab('forecast',this)">Forecasting</button><button class="tabbtn" onclick="tab('activity',this)">Activity</button><button class="tabbtn" onclick="tab('decision',this)">Decision Quality</button><button class="tabbtn" onclick="tab('self',this)">Self-Service</button><button class="tabbtn" onclick="tab('mapping',this)">Mapping</button><button class="tabbtn" onclick="tab('data_manager',this)">Data Manager</button><button class="tabbtn" onclick="tab('exports',this)">Exports</button>
</div>
<section id="intro" class="tab active"><div class="card"><h2>Welcome</h2><p class="note">Welcome to your local portfolio analytics lab. The goal is simple: connect your Trading212 data, optionally add other brokers through Data Manager, keep full control of your local files, and turn the available data into useful views for holdings, dividends, PnL, activity, forecasting and decision-quality review.</p></div><div class="two"><div class="card"><h3>What works now</h3><ul class="note"><li>Trading212 API refresh or local cache-only mode.</li><li>One-click synthetic demo data so you can explore the dashboard before using your own API key.</li><li>Manual broker inputs through Data Manager for transactions, dividends, cash flows, crypto rows and open positions.</li><li>Interactive charts with filters, sortable tables and scrollable table containers.</li><li>Overview KPIs, current holdings, dividends, PnL, activity, forecasting, decision-quality views and an automatic S&amp;P 500 benchmark cache.</li><li>Editable symbol mapping so you can enrich tickers with true ticker, asset type, sector, country, ETF/leverage flags and custom portfolio tags.</li><li>Excel/CSV exports, custom HTML reports, local display settings and transparent source tables when enabled.</li></ul></div><div class="card"><h3>First run checklist</h3><ol class="note"><li>If you want to explore first, click Try demo data in the sidebar. It uses fake portfolio data and needs no Trading212 API key.</li><li>When you are ready for your own portfolio, create a Trading212 API key with read access to Account data, Portfolio, Metadata, History, History - Orders, History - Dividends and History - Transactions.</li><li>Paste the API key and secret in the sidebar.</li><li>Choose Full refresh for the first real run, then click Run / Refresh Dashboard.</li><li>After a successful first run, use Refresh current year only for faster updates or cache-only mode when you do not want to call Trading212.</li><li>Use Data Manager to add other broker or crypto rows, then save and rebuild from local cache.</li></ol></div></div><div class="card"><h3>Important notes</h3><p class="note">This app is still a local testing version. Data is saved in the app folder under <b>data/</b>, <b>output/</b> and <b>mapping/</b>. Demo data is synthetic and is marked locally so a later Trading212 refresh can replace it cleanly. Gross dividends are not invented; dividend analysis uses received dividend data available from broker/API/cache/manual inputs. Trading212 history gives orders, dividends, cash transactions and current positions, but not exact historical month-end portfolio values for every holding. Because of that, the dashboard return line is a practical available-data proxy, not a perfect historical valuation engine, TWR or broker MWRR. The S&amp;P 500 benchmark is approximate and excludes S&amp;P dividends, EUR/USD FX, taxes and fees. Reported realized PnL is used only where broker or manual source data provides it, so older sells may appear without realized PnL until we add a proper recalculation engine.</p></div><div class="card"><h3>Coming later</h3><p class="note">Let's see.</p></div></section>
<section id="overview" class="tab"><div class="metrics" id="metrics"></div><div class="card"><h2>Benchmark process: what to expect</h2><p class="note">The benchmark module is automatic. On each dashboard run/rebuild, the app tries to update a local S&amp;P 500 daily-close cache from free public sources. If online fetching fails, it falls back to the last cached file. If no benchmark cache exists yet, benchmark lines are hidden and the status box explains why.</p><div class="explain"><b>How the S&amp;P 500 virtual portfolio is calculated:</b><br>1) The app summarizes your activity by month and calculates monthly net buy value = buys minus sells from Trading212 orders plus manual source inputs where available.<br>2) For each month, it virtually invests that same net amount into the S&amp;P 500 price index at the month-end close.<br>3) The virtual S&amp;P shares are carried forward and revalued each month using the next month-end close.<br>4) The result is an approximate answer to: What would my portfolio value look like if I had deployed the same cash-flow timing into the S&amp;P 500 instead?</div><p class="smallnote"><b>Important limitations:</b> this is a basic benchmark, not a professional performance report. It excludes S&amp;P dividends, EUR/USD FX conversion, taxes and fees. Trading212/API history does not provide exact month-end portfolio value for every past month, so the actual return line is an available-data proxy, not TWR, broker MWRR or true historical valuation.</p><div class="filters"><div><label>Benchmark start month</label><select id="benchmark_start_month" onchange="render()"><option value="">First available activity month</option></select><div class="smallnote">Changing this recalculates the visible S&amp;P simulation from that month onward. The actual line remains approximate because exact historical month-end portfolio values are not available yet.</div></div><div></div><div></div><div></div></div></div><div class="card"><h2>Portfolio cash-flow & benchmark timeline</h2><p class="plotnote">Use this chart to compare cash deployed, dividends received, and the simulated S&amp;P 500 portfolio value based on the same monthly net buy cash flows. If the S&amp;P line is missing, check the status box below the chart.</p><div class="filters"><div><label>Lines to show</label><select id="overview_timeline_lines" multiple size="4" onchange="render()"><option value="net_invested" selected>Cumulative net buy value</option><option value="dividends" selected>Cumulative dividends received</option><option value="sp500" selected>S&amp;P 500 virtual portfolio</option></select><div class="smallnote">Ctrl/Shift-click to select multiple lines. S&amp;P line uses monthly net buy value as virtual benchmark cash flow.</div></div><div></div><div></div><div></div></div><div id="chart_overview_timeline" class="chart"></div><div id="benchmark_status_box"></div><div id="src_overview_timeline"></div></div><div class="card"><h2>Portfolio return proxy vs S&amp;P 500 return line</h2><p class="plotnote">This chart starts from the selected benchmark month and shows approximate return percentage over time. The S&amp;P line uses the same monthly cash-flow timing as your portfolio. The portfolio line is a proxy built from available dividends, reported realized PnL where available, and current open PnL where available. It is not a true time-weighted return, broker MWRR or exact historical valuation.</p><div id="chart_benchmark_comparison" class="chart"></div><div id="tbl_benchmark_comparison"></div><div id="src_benchmark_comparison"></div></div><div class="card"><p class="plotnote">Top current positions by calculated market value in account currency.</p><div id="chart_overview" class="chart"></div><div id="src_overview_plot"></div></div><div class="card"><h2>LLM portfolio analysis prompt</h2><p class="note">Copy this to your preferred LLM.</p><textarea id="llm_prompt"></textarea><br><br><button class="btn2" onclick="copyEl('llm_prompt')">Copy prompt</button></div></section>
<section id="holdings" class="tab"><div class="card"><h2>Filters</h2><div class="filters"><div><label>Symbol contains</label><input id="f_symbol" oninput="render()" placeholder="Example: NIO; BABA; AAPL"></div><div><label>Asset type</label><select id="f_asset" onchange="render()"></select></div><div><label>Sector</label><select id="f_sector" onchange="render()"></select></div><div><label>Broker</label><select id="f_broker" onchange="render()"></select></div></div><div class="filters"><div><label>Top holdings to show in plot</label><select id="h_top_n" onchange="render()"><option value="10">Top 10</option><option value="20">Top 20</option><option value="30" selected>Top 30</option><option value="50">Top 50</option><option value="custom">Custom number</option><option value="0">All holdings</option></select></div><div><label>Custom top N</label><input id="h_top_custom" type="number" min="1" max="500" value="30" oninput="render()"></div><div></div><div></div></div><p class="smallnote">Symbol filter uses exact symbol matching. Examples: O matches only O, not Google. Use semicolon for multiple symbols, e.g. NIO; BABA; AAPL.</p></div><div class="card"><p class="plotnote">Filtered current holdings by market value. Use the filters above to isolate asset type, sector, broker or a custom top-N view.</p><div id="chart_holdings" class="chart"></div><div id="src_holdings_plot"></div></div><div class="card" id="tbl_holdings"></div></section>
<section id="dividends" class="tab"><div class="card"><p class="note">Dividend analysis uses received dividends from available broker/API data plus manual source inputs. Gross dividends are not calculated unless the source provides them clearly.</p><div class="filters"><div><label>Dividend symbol(s)</label><input id="div_symbol_filter" oninput="render()" placeholder="Exact: O; BMY; PSEC"></div><div><label>Dividend year</label><select id="div_year_filter" onchange="render()"></select></div><div><label>Broker</label><select id="div_broker_filter" onchange="render()"></select></div><div><label>Stocks to show in stock plot</label><select id="div_top_n" onchange="render()"><option value="10">Top 10</option><option value="20">Top 20</option><option value="30" selected>Top 30</option><option value="50">Top 50</option><option value="custom">Custom number</option><option value="0">All stocks</option></select></div></div><div class="filters"><div><label>Custom stocks to show</label><input id="div_top_custom" type="number" min="1" max="500" value="30" oninput="render()"></div><div></div><div></div><div></div></div><p class="smallnote">Symbol filter uses exact symbol matching. Example: O matches only O. Use semicolon for multiple symbols. When all brokers are selected, monthly/quarterly charts aggregate brokers into one value per period.</p></div><div class="two"><div class="card"><div id="chart_div_year" class="chart"></div><div id="src_div_year"></div></div><div class="card"><div id="chart_div_symbol" class="chart"></div><div id="src_div_symbol"></div></div></div><div class="two"><div class="card"><div id="chart_div_month" class="chart"></div><div id="src_div_month"></div></div><div class="card"><div id="chart_div_cumulative" class="chart"></div><div id="src_div_cumulative"></div></div></div><div class="card"><div id="chart_div_quarter_group" class="chart"></div><div id="src_div_quarter_group"></div></div><div class="card"><div id="chart_div_month_group" class="chart"></div><div id="src_div_month_group"></div></div><div class="card" id="tbl_dividends"></div></section>
<section id="pnl" class="tab"><div class="card"><p class="note">Current version focuses on price PnL, FX impact and total unrealized PnL. Use the filters below to review all brokers together or isolate one broker/source.</p><div class="filters"><div><label>PnL symbol(s)</label><input id="pnl_symbol" oninput="render()" placeholder="Exact: O; BMY; AAPL"></div><div><label>Asset type</label><select id="pnl_asset" onchange="render()"></select></div><div><label>Sector</label><select id="pnl_sector" onchange="render()"></select></div><div><label>Broker</label><select id="pnl_broker" onchange="render()"></select></div></div><div class="filters"><div><label>Symbols to show in PnL plots</label><select id="pnl_top_n" onchange="render()"><option value="10">Top 10</option><option value="20">Top 20</option><option value="30" selected>Top 30</option><option value="50">Top 50</option><option value="custom">Custom number</option><option value="0">All filtered symbols</option></select></div><div><label>Custom symbols to show</label><input id="pnl_top_custom" type="number" min="1" max="500" value="30" oninput="render()"></div><div></div><div></div></div><p class="smallnote">Symbol filter uses exact symbol matching and supports multiple terms separated by semicolon, comma or space.</p></div><div class="card"><div id="chart_price_pnl" class="chart"></div><div id="src_price_pnl"></div></div><div class="card"><div id="chart_fx_pnl" class="chart"></div><div id="src_fx_pnl"></div></div><div class="card"><div id="chart_total_pnl" class="chart"></div><div id="src_total_pnl"></div></div></section>
<section id="forecast" class="tab"><div class="card"><h2>Forecasting</h2><div class="explain"><b>How to read this tab:</b><br>Forecasts are scenario models, not predictions. The two method selectors create history-based lines from the data currently loaded from broker/API cache plus manual source inputs. You can also hide either history line from the same dropdown, or show extra comparison lines such as Base, Conservative, Optimistic and Manual user-defined. The method details below explain the selected historical formula, source data, caps and assumptions used.</div><br><div class="filters"><div><label>Portfolio history method</label><select id="portfolio_forecast_method" onchange="render()"><option value="p_tempered" selected>History annualized, tempered</option><option value="p_uncapped">History annualized, uncapped</option><option value="p_simple">History simple average per year</option><option value="hide">Hide from plot/table</option></select></div><div><label>Dividend history method</label><select id="dividend_forecast_method" onchange="render()"><option value="d_tempered" selected>Average complete-year growth, tempered</option><option value="d_uncapped">Average complete-year growth, uncapped</option><option value="d_cagr">Complete-year CAGR, tempered</option><option value="hide">Hide from plot/table</option></select></div><div><label>Extra comparison lines</label><select id="forecast_extra_scenarios" multiple size="5" onchange="render()"><option selected>Base</option><option>Conservative</option><option selected>Optimistic</option><option selected>Manual user-defined</option></select><div class="smallnote">Ctrl/Shift-click to select more than one extra line.</div></div><div><label>Projection years</label><input id="m_years" type="number" value="20" min="1" max="60" oninput="render()"></div></div><br><div class="filters"><div><label>Manual annual return %</label><input id="m_ret" type="number" value="7" step="0.1" oninput="render()"></div><div><label>Manual dividend growth %</label><input id="m_dg" type="number" value="4" step="0.1" oninput="render()"></div><div><label>Manual annual contribution EUR</label><input id="m_contrib" type="number" value="0" step="100" oninput="render()"></div><div></div></div><br><div id="forecast_method_details"></div></div><div class="card"><h3>Method assumptions</h3><div id="tbl_forecast_assumptions"></div></div><div class="card"><h3>Forecast computation notes</h3><div class="explain"><b>History-based portfolio line:</b> Year 0 starts from the current calculated holdings value. Each next row is calculated as <b>(previous portfolio value + previous annual dividend) x (1 + selected historical return assumption)</b>. No future cash contribution is assumed in the history method line.<br><br><b>History-based dividend line:</b> Year 0 starts from trailing-12-calendar-month dividends where available, otherwise the latest available annual dividend amount. Each next row is calculated as <b>previous annual dividend x (1 + selected historical dividend-growth assumption)</b>.<br><br><b>Extra comparison lines:</b> Base, Conservative and Optimistic use fixed assumptions. Manual user-defined uses the inputs above and can include future annual contributions. No external dividend history, analyst estimates or market-price forecasts are used.</div></div><div class="two"><div class="card"><p class="plotnote">Portfolio value forecast. Includes the selected portfolio history method unless hidden, plus any selected extra comparison lines.</p><div id="chart_forecast_value" class="chart"></div><div id="src_forecast_value"></div></div><div class="card"><p class="plotnote">Annual dividend forecast. Includes the selected dividend history method unless hidden, plus any selected extra comparison lines.</p><div id="chart_forecast_div" class="chart"></div><div id="src_forecast_div"></div></div></div><div class="card"><h3>Forecast calculation detail</h3><p class="note">Each row shows the selected historical method and any extra comparison lines, including assumptions and exact calculation steps.</p><div id="tbl_forecast_detail"></div></div></section>
<section id="activity" class="tab"><div class="card"><h2>Activity / Orders</h2><p class="note">This tab uses parsed broker order history plus manual source inputs where available. Realized PnL is only as good as the broker/manual data provides, so older sell orders may still be incomplete.</p><div class="filters"><div><label>Activity date from</label><input id="act_from" type="date" onchange="render()"></div><div><label>Activity date to</label><input id="act_to" type="date" onchange="render()"></div><div><label>Activity symbol(s)</label><input id="act_symbol" oninput="render()" placeholder="Exact: NIO; BABA; AAPL"></div><div><label>Broker</label><select id="act_broker" onchange="render()"></select></div></div><p class="smallnote">The date filter is applied to the yearly/monthly Activity charts and the orders table below. Symbol filter uses exact matching and supports multiple terms separated by semicolon, comma or space.</p></div><div class="two"><div class="card"><p class="plotnote">Net buy value by year: buys minus sells from broker order history plus manual source inputs where available.</p><div id="chart_net_buys_year" class="chart"></div><div id="src_net_buys_year"></div></div><div class="card"><p class="plotnote">Reported realized PnL by year, only where broker/manual source data provides realized PnL fields. Older sells may exist without reported realized PnL.</p><div id="chart_realized_pnl_year" class="chart"></div><div id="src_realized_pnl_year"></div></div></div><div class="card" id="activity_pnl_note"></div><div class="card"><p class="plotnote">Monthly net buy activity to see when cash was deployed or positions were reduced.</p><div id="chart_orders_month" class="chart"></div><div id="src_orders_month"></div></div><div class="card" id="tbl_activity"></div></section><section id="decision" class="tab"><div class="card"><h2>Decision Quality</h2><p class="note">Experimental decision-quality view using available broker data plus manual source inputs. It combines buys, sells, open PnL, reported realized PnL and dividends by symbol. This is not tax-grade realized PnL and cannot judge decisions where history is incomplete.</p><div class="filters"><div><label>Symbol(s)</label><input id="dq_symbol" oninput="render()" placeholder="Exact: NIO; BABA; AAPL"></div><div><label>Decision note contains</label><input id="dq_note" oninput="render()"></div><div><label>Broker</label><select id="dq_broker" onchange="render()"></select></div><div><label>Asset type</label><select id="dq_asset" onchange="render()"></select></div></div><div class="filters"><div><label>Sector</label><select id="dq_sector" onchange="render()"></select></div><div><label>Observed result</label><select id="dq_result_type" onchange="render()"><option value="all">All</option><option value="positive">Positive / zero result</option><option value="negative">Negative result</option></select></div><div><label>Sort by</label><select id="dq_sort" onchange="render()"><option value="total_observed_result_eur">Total observed result</option><option value="current_allocation_pct">Current allocation</option><option value="open_position_return_pct">Open return %</option><option value="dividend_received_eur">Dividends</option></select></div><div><label>Symbols to show in plots</label><select id="dq_top_n" onchange="render()"><option value="10">Top 10</option><option value="20">Top 20</option><option value="30" selected>Top 30</option><option value="50">Top 50</option><option value="custom">Custom number</option><option value="0">All filtered symbols</option></select></div></div><div class="filters"><div><label>Custom symbols to show</label><input id="dq_top_custom" type="number" min="1" max="500" value="30" oninput="render()"></div><div></div><div></div><div></div></div><p class="smallnote">These filters apply to both Decision Quality plots and the table. Symbol filter uses exact matching and supports multiple terms separated by semicolon, comma or space.</p></div><div class="two"><div class="card"><p class="plotnote">Best observed result = reported realized PnL + current unrealized PnL including FX + received dividends.</p><div id="chart_dq_result" class="chart"></div><div id="src_dq_result"></div></div><div class="card"><p class="plotnote">Best open return % by symbol, based on current open position cost basis and current unrealized PnL including FX.</p><div id="chart_dq_open_return" class="chart"></div><div id="src_dq_open_return"></div></div></div><div class="two"><div class="card"><p class="plotnote">Worst observed result from the same filtered symbols.</p><div id="chart_dq_result_worst" class="chart"></div><div id="src_dq_result_worst"></div></div><div class="card"><p class="plotnote">Worst open return % from the same filtered symbols.</p><div id="chart_dq_open_return_worst" class="chart"></div><div id="src_dq_open_return_worst"></div></div></div><div class="card" id="tbl_decision"></div></section><section id="data_manager" class="tab"><div class="card"><h2>Data Manager</h2><p class="note">This is the user-friendly source/input layer. First load Trading212 data from the sidebar. Then add or upload manual data from other brokers here, save, and rebuild the dashboard from local cache. The app keeps broker API raw files untouched and combines your manual inputs during dashboard processing.</p><div class="explain"><b>Important principle:</b> edit source-level inputs here, not final dashboard result tables. Manual rows are stored locally in <b>data/manual_inputs/</b>. When you save, the dashboard is recalculated so Overview, Dividends, Activity, PnL, Forecasting and Decision Quality can reflect these additions.</div><br><p id="manual_save_status" class="smallnote"></p></div><div class="card" id="manual_input_guide"></div><div class="card"><h3>Manual transactions</h3><p class="note">Use this table for manual buys, sells, dividends, deposits, withdrawals, fees, taxes or FX adjustments from other brokers or corrections. For BUY/SELL/DIVIDEND, symbol is required. If the row is not in account currency, fill currency and fx_rate_to_account_currency.</p><div class="explain"><b>Example rows:</b><br>Dividend: enabled=TRUE, date=2026-03-15, broker=manual_broker, event_type=Dividend, symbol=O, net_amount=25.50, currency=EUR, fx_rate_to_account_currency=1.<br>Buy: enabled=TRUE, date=2026-03-20, broker=manual_broker, event_type=Buy, symbol=AAPL, quantity=2, price=180, gross_amount=360, currency=USD, fx_rate_to_account_currency=0.92.</div><div id="manual_transactions_editor"></div></div><div class="card"><h3>Manual positions</h3><p class="note">Use this for holdings from another broker or manual open-position corrections. Provide either market_value_eur, or quantity + current_price. If the row is not in account currency, fill currency and fx_rate_to_account_currency so values can be converted.</p><div class="explain"><b>Example row:</b><br>enabled=TRUE, broker=manual_broker, symbol=VWCE, quantity=10, average_price=100, current_price=115, market_value_eur=1150, cost_basis_eur=1000, currency=EUR, fx_rate_to_account_currency=1.</div><div id="manual_positions_editor"></div></div><div class="card"><h3>Validation warnings</h3><p class="note">This table is read-only. It does not accept rows. It only shows warnings/errors found in your manual inputs, for example missing date, missing symbol, missing amount, missing FX rate, invalid event type, or a position without enough value fields. If it is empty, no obvious issue was detected.</p><div id="manual_validation"></div></div></section>
<section id="self" class="tab"><div class="card"><h2>Self-Service Charts</h2><p class="note">Create quick exploratory charts from any dashboard table. Choose X and Y, then optionally split the plot by one useful category such as broker, year, sector or asset type.</p><div class="filters"><div><label>Dataset</label><select id="ss_dataset" onchange="ssCols()"></select></div><div><label>X axis</label><select id="ss_x" onchange="ssChartAdvice()"></select></div><div><label>Y axis</label><select id="ss_y" onchange="ssChartAdvice()"></select></div><div><label>Split/group by</label><select id="ss_group" onchange="ssChartAdvice()"></select></div></div><div class="filters"><div><label>Chart type</label><select id="ss_type" onchange="ssChartAdvice()"><option>bar</option><option>line</option><option>scatter</option></select></div><div><label>Filter column</label><select id="ss_filter_col" onchange="ssChartAdvice()"><option value="">No filter</option></select></div><div><label>Filter value contains</label><input id="ss_filter_value" oninput="ssChartAdvice()" placeholder="Example: NIO or 2026"></div><div><label>Max rows before plotting</label><input id="ss_max_rows" type="number" min="10" max="5000" value="500" oninput="ssChartAdvice()"></div></div><p id="ss_chart_advice" class="smallnote"></p><br><button onclick="selfChart()">Create chart</button> <button class="btn2" onclick="applyRecommendedSelfServiceSetup()">Recommended setup</button> <button class="btn2" onclick="randomSelfChart()">Random plot</button></div><div class="card" id="ss_dataset_desc"></div><div class="card"><div id="chart_self" class="chart"></div><div id="src_self_chart"></div></div><div class="card" id="ss_column_info"></div></section>

<section id="mapping" class="tab"><div class="card"><h2>Mapping file</h2><p class="note">Use this tab to enrich broker symbols with true ticker, clean display name, asset type, sector, industry, country, ETF/leverage flags, risk bucket and custom portfolio. After saving, the dashboard is refreshed from local cache, so no Trading212 API call is needed.</p><a class="btn" href="/download_mapping">Download mapping CSV</a> <button class="btn2" onclick="saveMapping()">Save mapping and refresh</button></div><div class="card"><h3>Editable mapping table</h3><div id="tbl_mapping"></div></div></section>
<section id="exports" class="tab"><div class="card"><h2>Exports</h2><a class="btn green" href="/download_excel">Download latest Excel workbook</a><p class="note">Use this tab for full workbook export, custom HTML reports, and individual CSV downloads. The report builder creates a standalone HTML file from selected visible dashboard sections. Open that file in a browser and use Print / Save as PDF when you want a PDF copy.</p></div><div class="card" id="report_builder"></div><div class="card" id="gold_plot_mapping"></div><div class="card" id="csv_links"></div></section></main></div>
<script src="/static/js/app.js"></script></body></html>
