Skip to content

Add trade confirmation parsing and transaction fees processing#188

Draft
mbronk wants to merge 5 commits intoRustInFinance:mainfrom
mbronk:personal/mbronk/trade_confirmation_parsing_and_fee_processing
Draft

Add trade confirmation parsing and transaction fees processing#188
mbronk wants to merge 5 commits intoRustInFinance:mainfrom
mbronk:personal/mbronk/trade_confirmation_parsing_and_fee_processing

Conversation

@mbronk
Copy link
Copy Markdown
Contributor

@mbronk mbronk commented Apr 4, 2026

Important

Non-standalone! (therefore DRAFT, until #187 is dispositioned)

Separate gross proceeds from costs for PIT-38 sold transactions

What:

Use Trade Confirmation commission/fee data to increase tax cost basis while keeping sale proceeds reported as gross proceeds (separate fields).

Why:

  • Polish PIT-38 capital-gains reporting requires separate values for proceeds (przychod) and deductible costs (koszty uzyskania przychodu).
  • Booking brokerage fees into costs (instead of blending into proceeds) keeps reconstructed SELL data aligned with tax-form semantics.

How:

  • Parse Trade Confirmation PDFs and extract trade_date, settlement_date, quantity, price, principal, commission, fee, and net amount.
  • Match confirmations to sold rows using normalized (trade_date, settlement_date, quantity, price, symbol).
  • Allocate same-day, same-symbol G&L lots to sold rows via quantity-constrained DFS/knapsack matching.
  • Reconstruct sold transactions by proportional fee allocation per matched row.
  • Store fees separately and add them into cost basis; keep proceeds and costs as distinct output values.
  • If confirmation matching remains ambiguous even after price matching, continue with deterministic selection and emit explicit warnings (including principal/net candidate diagnostics) surfaced in CLI and GUI notes.
  • Strengthen validation and diagnostics to distinguish missing statement entries from quantity/symbol inconsistencies.
  • Keep integer-only quantity handling from G&L.

mbronk added 5 commits April 4, 2026 16:30
Polish tax law requires different rounding methods depending on income type,
which was not previously implemented. This change brings the calculation into
compliance with Art. 63 Ordynacji Podatkowej (OP).

1. Per currency-converted "tax event" (i.e. stock sales, dividend...),
   convert each result to full "grosz" (0,01 precision) - before
   summing.
   Basis: Mathematical rounding rules (0,01zł is the lowest monetary
   value)

2. Interests and dividends are separated as they follow different
   rounding rules when calculating the lump-sum tax

   a) Interests aggregate (art. 30a ust. 1 pkt 3 PIT) as well as
      their resulting lump-sum tax, are rounded UP to the nearest
      full "grosz" — art. 63 §1a OP
   b) Dividends aggregate (art. 30a ust. 1 pkt 4 PIT) as well
      as their resulting lump-sum tax, are rounded to the nearest
      full ZLOTY (0,50zl -> 1zl)  — art. 63 §1 OP
   c) Foreign tax withholding: no rounding the standard FX rule
      to round to grosz (0,01) precision - rule #1 (above)
   d) Net/gross/cost stock proceeds are not subject to lump-sum
      tax calculations and reported in full on PIT-38 form, hence
      only standard FX rules (#1-above) applies and they are reported
      with "grosz" precision.

Signed-off-by: Mateusz Bronk <mbronk@users.noreply.github.com>
Per tax advisor consultation with Naczelna Izba Skarbowa and Art. 63
Ordynacja Podatkowa, rounding should only be applied to final tax base
and tax amount sums, not to individual transactions.

Change default behavior to carry full f32 precision through per-transaction
FX conversions. The previous per-transaction rounding to grosz is preserved
as an opt-in flag:
  - CLI: --round-per-transaction
  - GUI: Options menu toggle (MenuBar)

Also add default-run to Cargo.toml to resolve binary ambiguity.

Signed-off-by: Mateusz Bronk <mbronk@users.noreply.github.com>
Signed-off-by: Mateusz Bronk <mbronk@users.noreply.github.com>
Replace all f32/f64 money types with rust_decimal::Decimal for precise
financial arithmetic. This eliminates floating-point representation
errors in tax computations.

Motivation: starting at 131,072 PLN (2^17), f32's ULP reaches 0.015625,
exceeding 0.01 PLN and making it impossible to represent individual grosz
values correctly. Any realistic portfolio of ~$30k+ in US stock sales
converted at ~4 PLN/USD crosses this threshold.
A new test (test_decimal_precision_vs_f32_sold_taxation) demonstrates a
concrete 1-grosz error on a PIT-38-realistic scenario with just 3 trades.

Key changes:
- Add rust_decimal 1.36.0 (macros, serde-with-float features)
- Convert all money types (Currency, Transaction, SoldTransaction,
  TaxCalculationResult) and rounding functions to Decimal
- Update parsers: PDF uses DecimalEntry; CSV parses &str→Decimal
  directly; XLSX converts calamine f64→Decimal (IEEE 754 unavoidable)
- NBP rates: serde-with-float deserialization, cached rates as dec!()
- ECB rates: Decimal division for inverse exchange rates
- Round gross_sold/cost_sold to grosz in run_taxation output
- Add rust_decimal.natvis for VS debugger visualization
- Add proof test for f32 grosz error; update all existing tests

Signed-off-by: Mateusz Bronk <mbronk@users.noreply.github.com>
What:
Use Trade Confirmation commission/fee data to increase tax cost basis while
keeping sale proceeds reported as gross proceeds (separate fields).

Why:
- Polish PIT-38 capital-gains reporting requires separate values for proceeds
  (przychod) and deductible costs (koszty uzyskania przychodu).
- Booking brokerage fees into costs (instead of blending into proceeds)
  keeps reconstructed SELL data aligned with tax-form semantics.

How:
- Parse Trade Confirmation PDFs and extract trade_date, settlement_date,
  quantity, price, principal, commission, fee, and net amount.
- Match confirmations to sold rows using normalized
  (trade_date, settlement_date, quantity, price, symbol).
- Allocate same-day, same-symbol G&L lots to sold rows via
  quantity-constrained DFS/knapsack matching.
- Reconstruct sold transactions by proportional fee allocation per matched row.
- Store fees separately and add them into cost basis; keep proceeds and costs
  as distinct output values.
- If confirmation matching remains ambiguous even after price matching,
  continue with deterministic selection and emit explicit warnings
  (including principal/net candidate diagnostics) surfaced in CLI and GUI notes.
- Strengthen validation and diagnostics to distinguish missing statement entries
  from quantity/symbol inconsistencies.
- Keep integer-only quantity handling from G&L.

Signed-off-by: Mateusz Bronk <mbronk@users.noreply.github.com>
@mbronk mbronk force-pushed the personal/mbronk/trade_confirmation_parsing_and_fee_processing branch from 45ce7d9 to 7c9941d Compare April 4, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant