Files
xtb-investment-tools/test_portfolio_performance_exporter.py
farcasclaudiu 68cfec926e Add scripts for environment setup and validation, and implement tests for portfolio performance exporter
- Created requirements.txt for dependencies including pandas, numpy, openpyxl, and yfinance.
- Added setup-env.sh script to set up a Python virtual environment and install required packages.
- Introduced validate-export.sh script to validate the exporter module and check expected fields.
- Implemented test cases in test_portfolio_performance_exporter.py to ensure correct CSV export functionality and data handling.
2026-06-21 21:06:08 +03:00

127 lines
4.6 KiB
Python

import csv
import pandas as pd
import main
import portfolio_performance_exporter as pp
def _cash_ops(rows):
cols = ["Type", "Instrument", "Ticker", "Time", "Amount", "Comment", "Product"]
return main.clean_columns(pd.DataFrame(rows, columns=cols))
def _row(type_, instr="", ticker="", amount=0.0, comment="", time="2026-02-18 09:00:00"):
return [type_, instr, ticker, time, amount, comment, "My Trades"]
def _read_semicolon_csv(path):
with path.open(newline="", encoding="utf-8") as f:
return list(csv.DictReader(f, delimiter=";"))
def test_export_writes_two_semicolon_csvs_with_expected_headers(tmp_path):
outputs = pp.export(main.REPORT_FILE, output_dir=tmp_path)
portfolio_path = outputs["portfolio_transactions"]
account_path = outputs["account_transactions"]
assert portfolio_path.name == (
f"{main.REPORT_FILE.stem}_portfolio_performance_portfolio_transactions.csv"
)
assert account_path.name == (
f"{main.REPORT_FILE.stem}_portfolio_performance_account_transactions.csv"
)
with portfolio_path.open(encoding="utf-8") as f:
assert f.readline().strip() == ";".join(pp.PORTFOLIO_FIELDS)
with account_path.open(encoding="utf-8") as f:
assert f.readline().strip() == ";".join(pp.ACCOUNT_FIELDS)
portfolio_rows = _read_semicolon_csv(portfolio_path)
account_rows = _read_semicolon_csv(account_path)
assert [row["Type"] for row in portfolio_rows] == ["Buy", "Sell"]
assert [row["Type"] for row in account_rows] == ["Deposit", "Dividend", "Taxes"]
def test_build_rows_separates_trade_and_cash_activity():
ops = _cash_ops([
_row("Deposit", amount=4000.0, comment="deposit funds"),
_row("Stock purchase", "Demo Equity", "DEMO.DE", -500.0, "OPEN BUY 5 @ 100.00"),
_row("Dividend", "Demo Equity", "DEMO.DE", 10.0, "Dividend"),
_row("Dividend tax", "Demo Equity", "DEMO.DE", -1.5, "Dividend tax"),
_row("Free funds interest", amount=0.03, comment="Free funds interest"),
_row("RO tax", amount=-0.01, comment="Tax"),
_row("Stock sell", "Demo Equity", "DEMO.DE", 240.0, "CLOSE BUY 2 @ 120.00"),
])
portfolio_rows, account_rows = pp.build_rows(ops, "EUR")
assert [row["Type"] for row in portfolio_rows] == ["Buy", "Sell"]
assert portfolio_rows[0]["Shares"] == 5.0
assert portfolio_rows[0]["Ticker Symbol"] == "DEMO.DE"
assert portfolio_rows[0]["Security Name"] == "Demo Equity"
assert portfolio_rows[0]["Value"] == 500.0
assert portfolio_rows[0]["Securities Account"] == "XTB"
assert portfolio_rows[0]["Cash Account"] == "XTB (EUR)"
assert portfolio_rows[1]["Type"] == "Sell"
assert [row["Type"] for row in account_rows] == [
"Deposit",
"Dividend",
"Taxes",
"Interest",
"Taxes",
]
dividend = account_rows[1]
assert dividend["Ticker Symbol"] == "DEMO.DE"
assert dividend["Security Name"] == "Demo Equity"
assert dividend["Value"] == 10.0
def test_split_fill_quantity_uses_numerator():
ops = _cash_ops([
_row("Stock purchase", "S&P 500", "SPY.US", -14.31, "OPEN BUY 1/100 @ 14.3130"),
_row("Stock purchase", "S&P 500", "SPY.US", -1416.99, "OPEN BUY 99/100 @ 14.3130"),
])
portfolio_rows, account_rows = pp.build_rows(ops, "EUR")
assert account_rows == []
assert [row["Shares"] for row in portfolio_rows] == [1.0, 99.0]
def test_custom_account_names_are_used_in_rows():
ops = _cash_ops([
_row("Stock purchase", "Demo Equity", "DEMO.DE", -500.0, "OPEN BUY 5 @ 100.00"),
_row("Deposit", amount=4000.0, comment="deposit funds"),
])
portfolio_rows, account_rows = pp.build_rows(
ops,
"EUR",
securities_account="Broker Securities",
cash_account="Broker Cash EUR",
)
assert portfolio_rows[0]["Securities Account"] == "Broker Securities"
assert portfolio_rows[0]["Cash Account"] == "Broker Cash EUR"
assert account_rows[0]["Cash Account"] == "Broker Cash EUR"
def test_empty_input_writes_headers_only(tmp_path, monkeypatch):
empty = main.clean_columns(
pd.DataFrame(columns=["Type", "Instrument", "Ticker", "Time", "Amount", "Comment", "Product"])
)
monkeypatch.setattr(main, "load_data", lambda: (pd.DataFrame(), empty, pd.DataFrame(), 0.0))
outputs = pp.export(main.REPORT_FILE, output_dir=tmp_path)
for key, fields in (
("portfolio_transactions", pp.PORTFOLIO_FIELDS),
("account_transactions", pp.ACCOUNT_FIELDS),
):
with outputs[key].open(encoding="utf-8") as f:
assert f.readline().strip() == ";".join(fields)
assert f.read() == ""