Skip to content

Fix (Lightyear) Error in commission calculation. Error in discounting the stock sale commission.#163

Open
elmasvital wants to merge 4 commits into
GeiserX:mainfrom
elmasvital:main
Open

Fix (Lightyear) Error in commission calculation. Error in discounting the stock sale commission.#163
elmasvital wants to merge 4 commits into
GeiserX:mainfrom
elmasvital:main

Conversation

@elmasvital
Copy link
Copy Markdown

@elmasvital elmasvital commented May 18, 2026

Se han corregido varios aspectos que hacían que los cash FX no cuadraran.

  1. En algunas operaciones se usaba GrossAmount en vez de NetAmount. En lightyear GrossAmoun incluye el importe total con comisiones incluidas. NetAmount es importe neto.
  2. En las operaciones de venta no es necesario crear una operación de Comisión ya que viene descontado del total que recibimos.

Dejo un csv para que se compruebe que con el sistema anterior los lotes_Fx no cuadran.
Lightyear Test Fallos FX comisiones 4.csv

Summary by CodeRabbit

  • Bug Fixes
    • Standardized monetary normalization to improve interest/reward income accuracy and skip zero-value entries.
    • Improved FX conversion handling to derive direction and quantities from net amounts and skip irrelevant conversions (e.g., base currency).
    • Ensured buy/sell trades compute trade money, proceeds, costs, and fees consistently using net amounts.
    • Adjusted commission recording so fees are correctly represented across buy and sell scenarios.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e62705b1-f264-4dfa-9751-709710e5554f

📥 Commits

Reviewing files that changed from the base of the PR and between 6aa34ba and 65d2b25.

📒 Files selected for processing (1)
  • src/parsers/lightyear.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/parsers/lightyear.ts

📝 Walkthrough

Walkthrough

Lightyear CSV parser refines normalized monetary fields: income, FX conversions, and buy/sell trades now prefer net-derived decimals (netAmount || grossAmount) and absolute values for quantities/fees, update FX skip/direction logic, and adjust trade money and commission mappings to use net-based numbers.

Changes

Monetary field normalization across transaction types

Layer / File(s) Summary
Import formatting
src/parsers/lightyear.ts
Reformatted the csv-utils named import block around stripBom.
Income normalization
src/parsers/lightyear.ts
Interest and reward income derive amountDec from absolute netAmount with a fallback to grossAmount when netAmount is falsy; zero amounts are skipped.
FX conversion normalization
src/parsers/lightyear.ts
FX conversions use `netAmount
Trade decimal derivation
src/parsers/lightyear.ts
Buy/sell trades make feeDec absolute, keep grossDec absolute, and introduce an absolute netDec.
Trade output mapping and commission
src/parsers/lightyear.ts
tradeMoney, proceeds, and cost are computed from netDec (signed for sells); price still uses grossDec when derived; commission is "0" if abs fee is zero or on sells, otherwise recorded as negative absolute fee.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • GeiserX/DeclaRenta#161: Modifies Lightyear FX conversion parsing logic for conversion legs and direction (related conversion handling changes).
  • GeiserX/DeclaRenta#115: Introduces the Lightyear CSV parser module that this PR updates.
  • GeiserX/DeclaRenta#117: Earlier changes to Lightyear parser detection/order related to this parser refinement.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main fix: correcting commission calculation in stock sales. It accurately reflects the core change from the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/parsers/lightyear.ts`:
- Around line 275-285: The commission field is incorrectly emitting a negative
buy-side fee on top of netAmount (double-counting); update the object so
buy-side commission is "0" (since tradeMoney/cost use netAmount that already
includes the fee) and do not emit feeDec.neg() for buys—i.e., change the
commission logic around the commission field (refer to tradeMoney, cost,
commission, feeDec and isSell) so buys produce "0" for commission while
preserving the intended sell behavior.
- Around line 259-261: The fallback price calculation currently uses
netAmount/qty which folds fees into the per-share price; change it to derive
trade price from the gross leg by adding fees back before dividing. In the block
using feeDec, netDec, qtyDec and pricePerShare, compute grossDec =
netDec.plus(feeDec) (both are already Decimal.abs()) and set price =
pricePerShare || (qtyDec.isZero() ? "0" : grossDec.div(qtyDec).toString());
ensure you replace the existing price computation so fees are not included in
the unit price.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 07385152-546d-4b2e-afac-977f22056dde

📥 Commits

Reviewing files that changed from the base of the PR and between 6d21d02 and e1937a6.

📒 Files selected for processing (1)
  • src/parsers/lightyear.ts

Comment thread src/parsers/lightyear.ts Outdated
Comment thread src/parsers/lightyear.ts
@elmasvital elmasvital changed the title Fix (Lightyear) Fix (Lightyear) Error in commission calculation. Error in discounting the stock sale commission. May 18, 2026
@elmasvital elmasvital marked this pull request as draft May 18, 2026 05:52
@elmasvital elmasvital marked this pull request as ready for review May 18, 2026 05:52
@elmasvital elmasvital marked this pull request as draft May 18, 2026 05:56
@elmasvital elmasvital marked this pull request as ready for review May 18, 2026 05:57
@elmasvital elmasvital closed this May 18, 2026
@elmasvital elmasvital reopened this May 18, 2026
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/parsers/lightyear.ts (1)

262-262: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix price fallback to differentiate buys vs sells.

The fallback uses grossAmount / qty for both transaction types, but that's only correct for sells:

  • BUY: GrossAmount = NetAmount + Fee, so grossAmount/qty = Price + Fee/qty (overstates execution price by fee per share).
  • SELL: GrossAmount = Qty × Price, so grossAmount/qty = Price

Use netAmount / qty for buys, grossAmount / qty for sells. This was approved in the prior review thread but not applied.

Proposed fix
-    const price = pricePerShare || (qtyDec.isZero() ? "0" : grossDec.div(qtyDec).toString());
+    const price = pricePerShare || (qtyDec.isZero() ? "0" : (isSell ? grossDec : netDec).div(qtyDec).toString());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parsers/lightyear.ts` at line 262, The computed price fallback currently
sets price = pricePerShare || (qtyDec.isZero() ? "0" :
grossDec.div(qtyDec).toString()); but grossDec/qtyDec is only correct for sells;
update the fallback to branch on the transaction side: if qtyDec.isZero() return
"0", else use netDec.div(qtyDec) for BUY transactions and grossDec.div(qtyDec)
for SELL transactions; keep the existing pricePerShare override and reference
the same symbols (price, pricePerShare, qtyDec, grossDec, netDec) and the
transaction side field used in this parser (e.g., type/side) to decide BUY vs
SELL.
🧹 Nitpick comments (1)
src/parsers/lightyear.ts (1)

214-214: ⚡ Quick win

Update comment to reflect netAmount usage.

The comment references "gross" but the code now uses netAmount for direction. Update line 214 to say "Positive net = acquiring that currency (BUY), negative = spending it (SELL)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parsers/lightyear.ts` at line 214, Update the inline comment that still
refers to "gross" to reflect the current variable name `netAmount` used for
direction in src/parsers/lightyear.ts; replace the text on that line with:
"Positive net = acquiring that currency (BUY), negative = spending it (SELL)."
and ensure the comment is adjacent to the `netAmount` usage so future readers
see it tied to the `netAmount` variable in the parsing logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/parsers/lightyear.ts`:
- Line 262: The computed price fallback currently sets price = pricePerShare ||
(qtyDec.isZero() ? "0" : grossDec.div(qtyDec).toString()); but grossDec/qtyDec
is only correct for sells; update the fallback to branch on the transaction
side: if qtyDec.isZero() return "0", else use netDec.div(qtyDec) for BUY
transactions and grossDec.div(qtyDec) for SELL transactions; keep the existing
pricePerShare override and reference the same symbols (price, pricePerShare,
qtyDec, grossDec, netDec) and the transaction side field used in this parser
(e.g., type/side) to decide BUY vs SELL.

---

Nitpick comments:
In `@src/parsers/lightyear.ts`:
- Line 214: Update the inline comment that still refers to "gross" to reflect
the current variable name `netAmount` used for direction in
src/parsers/lightyear.ts; replace the text on that line with: "Positive net =
acquiring that currency (BUY), negative = spending it (SELL)." and ensure the
comment is adjacent to the `netAmount` usage so future readers see it tied to
the `netAmount` variable in the parsing logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5e667afc-99fb-4222-8bbb-f9c6edbd46b4

📥 Commits

Reviewing files that changed from the base of the PR and between e1937a6 and 6aa34ba.

📒 Files selected for processing (1)
  • src/parsers/lightyear.ts

@GeiserX
Copy link
Copy Markdown
Owner

GeiserX commented May 18, 2026

Hey Igor, thanks for putting up a PR — the core insight here is correct! There's a real double-counting bug with fees because the FIFO engine adds |commission| on top of |cost|/|proceeds|, so passing cost = -Gross AND commission = -Fee effectively counts the fee twice.

Your fix logic is sound:

  • Buys: cost = -netAmount + commission = -fee → FIFO computes |Net| + |Fee| = Gross
  • Sells: proceeds = netAmount + commission = 0 → FIFO computes |Net| - 0 = Net
  • FX conversions: using netAmount for direction detection ✓

Issues to fix

1. Empty Net Amt fallback

The test "should emit SELL CASH for FCY→EUR conversion" fails because that fixture has an empty Net Amt field:

10/05/2025 12:00:00,CN-0000000099,USD,,Conversion,,USD,,-500.00,0.92,,,-500.00,

Here Net Amt. is ""parseNumber("") returns "0"netDec.isZero() → row gets skipped. Real Lightyear exports can have empty Net Amt fields (I've seen it on some conversion legs).

Fix: add a fallback in the FX conversion block and the Interest/Reward block:

// FX conversions
const netDec = new Decimal(netAmount || grossAmount);

// Interest/Reward  
const amountDec = new Decimal(netAmount || grossAmount).abs();

2. tradePrice fallback uses wrong base (CodeRabbit caught this one)

When pricePerShare is missing, the current fallback computes netAmount / quantity, which bakes the fee into the unit price. The execution price should be derived from gross:

// Current (wrong when fee > 0):
const price = pricePerShare || (qtyDec.isZero() ? "0" : netDec.div(qtyDec).toString());

// Fix — use grossDec for price derivation:
const price = pricePerShare || (qtyDec.isZero() ? "0" : grossDec.div(qtyDec).toString());

Could you push both fixes? Then CI should go green and I'll approve.


Re: your Telegram question about dividends

Los dividendos cómo los quiere tiene implementado el software Bruto total? Neto descontando retención?

The software reports them as AEAT expects:

  • Gross amounttype: "Dividends" (casilla 0029, base del ahorro)
  • Withholding tax → separate negative entry type: "Withholding Tax" (casilla 0588, deducción por doble imposición)

So it uses Gross Amount for the dividend entry and creates a separate withholding tax entry from Tax Amt.. This is correct — AEAT wants the gross in the income base, and the foreign withholding as a separate deduction. Your PR doesn't touch the dividend path so that's unaffected.

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.

2 participants