mirror of https://github.com/ghostfolio/ghostfolio
Browse Source
[Tax][Iteration 1] Document existing transaction and dividend data modelpull/7897/head
committed by
GitHub
1 changed files with 330 additions and 0 deletions
@ -0,0 +1,330 @@ |
|||
# Tax Metrics — Existing Transaction and Dividend Data Model |
|||
|
|||
**Owner:** Tharun Swaminathan |
|||
**Iteration:** 1 — Design & Data Modelling |
|||
**Date:** September 15, 2026 |
|||
|
|||
## Objective |
|||
|
|||
The purpose of this review was to understand how Ghostfolio currently stores and |
|||
handles transactions, especially dividend transactions, before adding any |
|||
tax-related functionality. |
|||
|
|||
Since the tax feature will later include withholding tax, FIFO capital gains, |
|||
average-cost calculations, tax lots, yearly summaries, and exports, it is |
|||
important to first understand the existing data model instead of creating a |
|||
separate structure that does not match the rest of Ghostfolio. |
|||
|
|||
No implementation changes were made as part of this task. |
|||
|
|||
## Current Persistence Model |
|||
|
|||
Ghostfolio stores portfolio transactions using the Prisma `Order` model in |
|||
`prisma/schema.prisma`. |
|||
|
|||
Even though the database model is called `Order`, the rest of the application |
|||
usually refers to these records as activities. |
|||
|
|||
The `Order` model already contains the main information needed to describe a |
|||
portfolio transaction. |
|||
|
|||
| Field | Current purpose | |
|||
|---|---| |
|||
| `id` | Unique identifier for the activity | |
|||
| `accountId` | Account associated with the activity | |
|||
| `comment` | Optional user comment | |
|||
| `createdAt` | Time the database record was created | |
|||
| `currency` | Currency used by the activity | |
|||
| `date` | Date of the financial activity | |
|||
| `fee` | Fee associated with the activity | |
|||
| `quantity` | Number of units or shares | |
|||
| `symbolProfileId` | Asset associated with the activity | |
|||
| `tags` | Tags attached to the activity | |
|||
| `type` | Type of financial activity | |
|||
| `unitPrice` | Price or value per unit | |
|||
| `updatedAt` | Last time the record was updated | |
|||
| `userId` | User who owns the activity | |
|||
|
|||
The currently supported activity types are: |
|||
|
|||
- `BUY` |
|||
- `DIVIDEND` |
|||
- `FEE` |
|||
- `INTEREST` |
|||
- `LIABILITY` |
|||
- `SELL` |
|||
|
|||
This means the tax calculations should build on the existing activity model |
|||
where possible instead of introducing a completely separate transaction model. |
|||
|
|||
## Dividend Representation |
|||
|
|||
Ghostfolio does not currently have a separate database table or entity just for |
|||
dividends. |
|||
|
|||
A dividend is stored as a normal activity where: |
|||
|
|||
`type = DIVIDEND` |
|||
|
|||
The activity still uses fields such as `quantity`, `unitPrice`, `currency`, |
|||
`date`, and `fee`. |
|||
|
|||
For example, one of the existing Ghostfolio import samples contains the |
|||
following dividend activity: |
|||
|
|||
- Asset: Microsoft (`MSFT`) |
|||
- Quantity: 5 shares |
|||
- Unit price: 0.62 USD |
|||
- Type: `DIVIDEND` |
|||
- Fee: 0 |
|||
- Currency: USD |
|||
- Date: November 17, 2021 |
|||
|
|||
Using the existing quantity and unit-price values, the dividend amount |
|||
represented by this activity is: |
|||
|
|||
`5 × 0.62 = 3.10 USD` |
|||
|
|||
This gives the tax feature a natural starting point for determining the dividend |
|||
value. In the later withholding-tax design, this value can be used when defining |
|||
the relationship between gross dividend, withholding tax, and net dividend. |
|||
|
|||
## Existing Fee Field |
|||
|
|||
Ghostfolio already has a `fee` field on the activity model. |
|||
|
|||
The same field is available when an activity is created, and the current DTO |
|||
requires it to be a non-negative number. |
|||
|
|||
However, the current model does not define this field specifically as dividend |
|||
withholding tax. It is a general activity fee. |
|||
|
|||
Because of this, the tax extension should not simply reinterpret `fee` as |
|||
withholding tax. |
|||
|
|||
A brokerage fee and dividend withholding tax represent different pieces of |
|||
financial information and may both exist on the same activity. |
|||
|
|||
For the later withholding-tax design, these values should remain conceptually |
|||
separate: |
|||
|
|||
- Gross dividend |
|||
- Activity fee |
|||
- Withholding tax |
|||
- Net dividend |
|||
|
|||
The exact withholding fields will be designed in the Sep 16 task. This review |
|||
only establishes that the existing `fee` field should keep its current meaning. |
|||
|
|||
## API and DTO Flow |
|||
|
|||
The current Ghostfolio activity flow already provides a path that can be |
|||
extended later for tax-related information. |
|||
|
|||
At a high level, the flow is: |
|||
|
|||
`Prisma Order` → `CreateOrderDto / UpdateOrderDto` → Activities backend service → Activity API response |
|||
|
|||
The existing `CreateOrderDto` already validates important transaction fields |
|||
such as: |
|||
|
|||
- currency |
|||
- date |
|||
- fee |
|||
- quantity |
|||
- symbol |
|||
- activity type |
|||
- unit price |
|||
|
|||
The API activity interface then extends the stored `Order` with additional |
|||
calculated information used by the application. |
|||
|
|||
This means the tax feature should reuse this existing flow where possible. |
|||
Creating a second, independent dividend transaction API would add unnecessary |
|||
duplication and make the system harder to maintain. |
|||
|
|||
## Currency Handling |
|||
|
|||
Currency is already part of Ghostfolio's activity model. |
|||
|
|||
The activity response also includes calculated values such as: |
|||
|
|||
- `feeInAssetProfileCurrency` |
|||
- `feeInBaseCurrency` |
|||
- `unitPriceInAssetProfileCurrency` |
|||
- `value` |
|||
- `valueInBaseCurrency` |
|||
|
|||
This is important for the tax feature because tax values may eventually need |
|||
to be reported both in the activity currency and the user's base currency. |
|||
|
|||
Instead of creating a separate currency-conversion method only for tax, the |
|||
future implementation should follow Ghostfolio's existing conversion approach |
|||
where practical. |
|||
|
|||
The exact withholding-tax currency fields are not being decided in this task. |
|||
That will be part of the Sep 16 schema design. |
|||
|
|||
## Transaction Ordering |
|||
|
|||
Transaction ordering is especially important for the later FIFO capital-gains |
|||
calculation. |
|||
|
|||
During the code review, Ghostfolio's existing activity handling was found to |
|||
use the activity date as the main chronological value. |
|||
|
|||
This gives us the primary ordering value needed for tax calculations: |
|||
|
|||
`activity date` |
|||
|
|||
However, FIFO also needs a deterministic rule when two transactions have the |
|||
same date or timestamp. |
|||
|
|||
The activity model contains both an `id` and `createdAt`, but this review does |
|||
not yet define which should be treated as the official secondary tax ordering. |
|||
|
|||
This should be explicitly decided in the FIFO specification rather than leaving |
|||
the calculation dependent on accidental database ordering. |
|||
|
|||
For now, this is recorded as an open design question for the Sep 17 FIFO task. |
|||
|
|||
## Import Compatibility |
|||
|
|||
Ghostfolio already supports importing activities, including dividend |
|||
transactions. |
|||
|
|||
This matters because future tax functionality cannot assume that every dividend |
|||
was manually entered by the user. |
|||
|
|||
For example, an imported dividend may contain the basic dividend amount but may |
|||
not contain withholding-tax information. |
|||
|
|||
The tax design should therefore be able to handle at least two situations: |
|||
|
|||
1. A dividend where withholding information is available. |
|||
2. A dividend where withholding information is not available. |
|||
|
|||
Missing withholding information should not make an otherwise valid historical |
|||
dividend unusable. |
|||
|
|||
The exact validation and default behavior will be defined when the withholding |
|||
schema is designed. |
|||
|
|||
## Existing Tests |
|||
|
|||
Ghostfolio already contains tests and sample data involving dividend |
|||
transactions. |
|||
|
|||
These are useful because future tax tests should follow the project's existing |
|||
testing style instead of building a completely different testing approach. |
|||
|
|||
One useful example is |
|||
`portfolio-calculator-msft-buy-with-dividend.spec.ts`, which already covers a |
|||
Microsoft purchase together with a dividend. The existing import sample also |
|||
contains an MSFT dividend activity. |
|||
|
|||
For future tax calculations, similar tests can be extended to verify exact |
|||
numeric results for cases such as: |
|||
|
|||
- dividend withholding |
|||
- multiple purchases followed by a sale |
|||
- partial FIFO lot usage |
|||
- multiple tax lots |
|||
- same-date activities |
|||
- rounding |
|||
- missing withholding information |
|||
|
|||
No new tests were written during this task because Iteration 1 is currently |
|||
focused on design and data modelling. |
|||
|
|||
## Design Conclusions |
|||
|
|||
Based on the existing Ghostfolio implementation, the tax feature should extend |
|||
the current activity model rather than create a separate transaction system. |
|||
|
|||
The main conclusions from this review are: |
|||
|
|||
1. Dividends are already represented as normal activities using |
|||
`type = DIVIDEND`. |
|||
|
|||
2. The existing `Order` model provides most of the transaction information |
|||
needed by future tax calculations. |
|||
|
|||
3. The existing `fee` field should remain separate from withholding tax. |
|||
|
|||
4. Gross dividend, withholding amount, and net dividend should remain |
|||
distinguishable rather than being combined into one value. |
|||
|
|||
5. Existing currency-handling patterns should be reused where possible. |
|||
|
|||
6. Imported and historical dividends may not always contain withholding |
|||
information, so the future design needs to handle missing values safely. |
|||
|
|||
7. FIFO will need an explicitly documented ordering rule, especially when |
|||
multiple activities have the same date. |
|||
|
|||
These conclusions will be used as the starting point for the remaining tax |
|||
design tasks in Iteration 1. |
|||
|
|||
## Open Questions |
|||
|
|||
The following questions were identified during this review and will be resolved |
|||
in later Iteration 1 tax tasks. |
|||
|
|||
### Withholding Tax |
|||
|
|||
- Should the withholding amount be stored directly? |
|||
- Should a withholding rate also be stored? |
|||
- If both amount and rate are available, which one should be treated as the |
|||
source value? |
|||
- Should withholding be optional for older or imported dividend records? |
|||
- How should withholding amounts be represented across different currencies? |
|||
|
|||
### FIFO and Tax Lots |
|||
|
|||
- What secondary ordering rule should be used when activities share the same |
|||
timestamp? |
|||
- How should stock splits affect tax-lot quantities and cost basis? |
|||
- What information needs to be stored to trace a sale back to the acquisition |
|||
lots that were consumed? |
|||
|
|||
### Calculation Precision |
|||
|
|||
- Where should rounding occur? |
|||
- Should calculations use the activity currency first and convert afterward, |
|||
or use base-currency values during the calculation? |
|||
|
|||
These questions are intentionally left open here because they belong to the |
|||
specific design tasks scheduled later in Iteration 1. |
|||
|
|||
## Next Task |
|||
|
|||
The next tax task is scheduled for September 16: |
|||
|
|||
**Design the withholding-tax field schema for dividend transactions.** |
|||
|
|||
The Sep 15 review provides the baseline for that work. |
|||
|
|||
The next design will define the withholding-tax inputs and fields, including |
|||
their types, optional/required behavior, validation rules, currency meaning, |
|||
and relationship to gross and net dividend values. |
|||
|
|||
No withholding-tax implementation will be added yet because Iteration 1 is the |
|||
design and data-modelling phase. |
|||
|
|||
## Relevant Files Reviewed |
|||
|
|||
The main Ghostfolio files reviewed for this task were: |
|||
|
|||
- `prisma/schema.prisma` |
|||
- `libs/common/src/lib/dtos/create-order.dto.ts` |
|||
- `libs/common/src/lib/interfaces/activities.interface.ts` |
|||
- `apps/api/src/app/activities/activities.controller.ts` |
|||
- `apps/api/src/app/activities/activities.service.ts` |
|||
- `apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts` |
|||
- `apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts` |
|||
- `test/import/ok/sample.json` |
|||
|
|||
These files were used to understand the current transaction and dividend data |
|||
model, API flow, currency handling, and existing dividend behavior. No |
|||
source-code changes were made during this review. |
|||
Loading…
Reference in new issue