A single-command Python CLI that turns an eToro Excel account statement into a clean per-metric performance summary in the terminal and a CSV alongside the input file.
etoro_statement is a small, focused command-line utility for retail eToro users. eToro's exported account statement is a multi-sheet Excel workbook. This tool ignores the noise and pulls only the aggregates that matter (deposits, withdrawals, realised gains, dividends, fees, equity), then prints a coloured, sectioned Rich table and writes the same numbers to a _summary.csv next to the source file for further analysis.
It does one thing, in one script (etoro_summary.py, about 300 lines), with no configuration and no persistent state.
flowchart TD
A[eToro .xlsx statement] --> B[pandas read_excel]
B --> C{Two sheets}
C -->|Account Summary| D[deposits, withdrawals,<br/>ending equities]
C -->|Financial Summary| E[trade P/L, dividends,<br/>other income, fees]
D --> F[metrics dict]
E --> F
F --> G[calculate_roi]
G --> H[Rich table<br/>4 sections]
F --> I[pandas to_csv]
H --> J[terminal]
I --> K[<input>_summary.csv]
Grounded in etoro_summary.py:
- Reads the
Account SummaryandFinancial Summarysheets from an eToro-exported.xlsx. - Extracts 11 metrics: total deposits, total withdrawals, net investment, realised gains, dividend income, other income, total expenses and fees, net realised profit, current realised equity, current unrealised equity, unrealised profit.
- Computes Return on Investment as
net_realized_profit / abs(net_investment)(percentage, with sign). - Robust to eToro's shifting column names: tries four variants of the amount column (
"Amount\r\n in (USD)","Amount in (USD)","Amount\nin (USD)","Amount (USD)") and falls back to any column containing bothAmountandUSD. - Terminal output uses
richwith four sections (Investment, Realized, Unrealized, Performance); positive values render green, negative red. - Persists the same metrics to a CSV named
<input>_summary.csvin the same directory as the source workbook. - Exits with a clear error on missing input or unloadable file.
- If no amount column can be matched at all, it prints
ERROR: Could not find amount column in Financial Summary sheetbut still exits 0 and still writes the CSV, with everyFinancial Summarymetric left at zero. Check that line before trusting the numbers.
The CSV counterpart holds the same rows as Metric,Value pairs.
- Python 3.11 or newer (CI runs on 3.12).
uvfor dependency management.
Runtime dependencies (pinned in uv.lock):
pandas(3.x)openpyxl(3.1+)rich(15.x)tabulate(0.10+, declared but not imported by the code)
git clone https://github.com/weirdapps/etoro_statement.git
cd etoro_statement
uv sync --frozenuv sync --frozen installs the exact versions in uv.lock. Drop --frozen if you want uv to re-resolve.
Point the script at any eToro-exported .xlsx account statement:
uv run python etoro_summary.py path/to/etoro-account-statement.xlsxThe tool will:
- Load the
Account SummaryandFinancial Summarysheets. - Print a Rich-formatted summary table to the terminal.
- Write
path/to/etoro-account-statement_summary.csvnext to the input.
No sample workbook is committed. .gitignore excludes *.xlsx, *.xls and *.csv, so a real
statement and its generated summary stay out of this public repository. Export your own statement
from eToro and pass its path.
| Section | Metrics | Source in the workbook |
|---|---|---|
| Investment | Deposits, Withdrawals, Net Investment | Account Summary rows Deposits, Withdrawals |
| Realized | Realized Gains, Dividend Income, Other Income, Expenses and Fees, Net Realized Profit, Current Realized Equity | Financial Summary (rows with Profit or Loss, Dividend, fee, charge, plus Account Summary row Ending Realized Equity) |
| Unrealized | Unrealized Profit, Current Unrealized Equity | Account Summary row Ending Unrealized Equity, minus realised equity |
| Performance | Return on Investment | Computed: net_realized_profit / abs(net_investment) |
Categorisation of Financial Summary rows follows the rules in process_etoro_statement:
- Any row whose
NamecontainsProfit or Losswith a positive amount is added to Realized Gains. - Any row whose
NamecontainsDividendis added to Dividend Income. - Any row whose
Namecontainsfeeorcharge, or any other negative amount not already categorised, is added to Total Expenses and Fees (stored as a positive number, subtracted from profit at the end). - Any remaining positive amount is added to Other Income.
Install dev tools and run the checks:
uv sync --frozen
uv run ruff check .
uv run ruff format --check .
uv run pytestThe test suite (tests/test_etoro_summary.py) covers ROI edge cases (positive, negative, zero investment, zero profit), the shape of the metrics dictionary, table construction on synthetic data, and the missing-file error path.
Optionally install the pre-commit hooks (.pre-commit-config.yaml), which add mypy, gitleaks,
yamllint and markdownlint on top of ruff. None of these four run in CI.
uv run --with pre-commit pre-commit installAll workflows run against the master branch.
| Workflow | File | Trigger |
|---|---|---|
| CI (ruff + pytest) | ci.yml |
push and PR to master |
| CodeQL | codeql.yml |
push, PR, weekly (Mon 06:00 UTC) |
| SonarCloud | sonarcloud.yml |
push, PR, manual (skipped if SONAR_TOKEN unset) |
| Monthly Dependency Refresh | deps-refresh.yml |
6th of each month, 04:23 UTC (opens PR with a fresh uv.lock) |
| Dependabot auto-merge | dependabot-auto-merge.yml |
Dependabot PRs. A thin caller that delegates to the shared reusable workflow weirdapps/shared-workflows/.github/workflows/dependabot-auto-merge.yml@main |
The caller passes no inputs, so the shared workflow's defaults apply: patch and minor updates auto-merge once the PR's own checks are green, a standalone major stays open for review, and a grouped update auto-merges even when its aggregate level is major.
etoro_statement/
├── etoro_summary.py # entire application
├── tests/
│ └── test_etoro_summary.py # 11 tests across 5 classes
├── example_output.png # screenshot of the Rich table
├── pyproject.toml # deps + ruff/mypy/pytest config
├── uv.lock # pinned dependency graph
├── .pre-commit-config.yaml # ruff, mypy, gitleaks, yamllint, markdownlint
└── .github/workflows/ # CI, CodeQL, SonarCloud, deps refresh, Dependabot auto-merge
Report vulnerabilities per SECURITY.md. Do not open a public issue for security reports.
MIT. Copyright (c) 2026 Dimitris Plessas.
