mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
1 changed files with 757 additions and 0 deletions
@ -0,0 +1,757 @@ |
|||
# Tax Metrics — FIFO Capital-Gains Calculation Specification |
|||
|
|||
**Owner:** Tharun Swaminathan |
|||
**Iteration:** 1 — Design & Data Modelling |
|||
**Date:** September 17, 2026 |
|||
|
|||
## Objective |
|||
|
|||
This document specifies the FIFO (First-In, First-Out) capital-gains method for the Ghostfolio tax extension. The goal is to define deterministic, traceable calculation behavior for Iteration 2 without changing production code during Iteration 1. |
|||
|
|||
The FIFO engine calculates realized gains from BUY and SELL activities and keeps each sale traceable to the acquisition lots it consumes. |
|||
|
|||
This is tax analytics software, not personal tax advice. The design does not introduce jurisdiction-specific tax rates, wash-sale rules, holding-period rules, or similar country-specific treatment. |
|||
|
|||
## Repository Findings |
|||
|
|||
### Activity ordering |
|||
|
|||
`ActivitiesService.getActivities()` already retrieves activities by `date` ascending and adds `id` as a secondary sort key. The FIFO engine should reuse that deterministic ordering: |
|||
|
|||
1. exact activity `date` ascending |
|||
2. activity `id` ascending when dates are equal |
|||
|
|||
### Existing performance calculation is not FIFO |
|||
|
|||
Ghostfolio's current portfolio calculator uses an average investment basis for SELL activities. It reduces investment proportionally and calculates sell performance against an average price. |
|||
|
|||
The tax FIFO engine should therefore be a separate tax-domain calculation. It should not replace or modify the existing ROAI performance behavior. |
|||
|
|||
### Use enriched Activity records |
|||
|
|||
The enriched `Activity` object contains source identifiers and transaction values such as: |
|||
|
|||
- `feeInAssetProfileCurrency` |
|||
- `feeInBaseCurrency` |
|||
- `unitPriceInAssetProfileCurrency` |
|||
- `value` |
|||
- `valueInBaseCurrency` |
|||
|
|||
The portfolio calculator later converts activities into a smaller `PortfolioOrder` representation that does not retain the activity `id` or full source timestamp. FIFO should therefore operate on enriched `Activity` records before that conversion. |
|||
|
|||
### Stock-split handling |
|||
|
|||
Ghostfolio already adjusts investment activities for stock splits. Activities before a split have quantity multiplied by the split ratio and unit price divided by the same ratio, preserving total value. |
|||
|
|||
FIFO should reuse this existing normalization instead of implementing a second split formula. |
|||
|
|||
### Currency conversion |
|||
|
|||
`ActivitiesService` already calculates transaction-date values in the asset-profile currency and the user's base currency. FIFO should reuse those values where practical. |
|||
|
|||
## Scope |
|||
|
|||
The FIFO algorithm operates on one **lot-matching scope** at a time. Activities from different assets must never be matched together. |
|||
|
|||
The caller is responsible for grouping activities into the correct lot-matching scope before running FIFO. |
|||
|
|||
Whether lots are pooled across accounts is intentionally not decided here because that can depend on later tax-lot requirements and jurisdiction-specific assumptions. The final scope key belongs to the Sep 21 tax-lot design. |
|||
|
|||
## Eligible Activities |
|||
|
|||
Only: |
|||
|
|||
- `BUY` |
|||
- `SELL` |
|||
|
|||
participate in FIFO lot matching. |
|||
|
|||
The following do not create or consume acquisition lots: |
|||
|
|||
- `DIVIDEND` |
|||
- `FEE` |
|||
- `INTEREST` |
|||
- `LIABILITY` |
|||
|
|||
A fee attached directly to a BUY or SELL can be allocated during the calculation. A standalone `FEE` activity is not automatically attached to a trade because the current data model does not identify which trade it belongs to. |
|||
|
|||
## Filtering |
|||
|
|||
The initial FIFO implementation should follow Ghostfolio's existing default filtering: |
|||
|
|||
- exclude draft activities |
|||
- exclude activities explicitly excluded from analysis |
|||
- exclude activities in excluded accounts |
|||
- include only BUY and SELL |
|||
- do not include synthetic cash activities |
|||
|
|||
The Sep 23 tax-relevant flag/filter can add another layer later without changing the core FIFO algorithm. |
|||
|
|||
## Required Inputs |
|||
|
|||
Each FIFO activity should retain: |
|||
|
|||
- activity `id` |
|||
- exact activity `date` |
|||
- activity `type` |
|||
- asset identity |
|||
- `quantity` |
|||
- transaction currency |
|||
- `unitPriceInAssetProfileCurrency` |
|||
- `feeInAssetProfileCurrency` |
|||
- `valueInBaseCurrency` |
|||
- `feeInBaseCurrency` |
|||
|
|||
Original transaction currency and unit price should also remain available for audit/export use. |
|||
|
|||
## Deterministic Ordering |
|||
|
|||
Process activities by: |
|||
|
|||
1. exact `date` ascending |
|||
2. `id` ascending for equal timestamps |
|||
|
|||
Do not rely on database return order or array insertion order. |
|||
|
|||
Example: |
|||
|
|||
```text |
|||
2026-01-10T10:00:00Z BUY id=0001 |
|||
2026-01-10T10:00:00Z BUY id=0002 |
|||
2026-01-10T12:00:00Z SELL id=0003 |
|||
``` |
|||
|
|||
The SELL consumes lot `0001` before lot `0002`. |
|||
|
|||
## Numeric Precision |
|||
|
|||
Convert relevant numeric inputs to `Big` values at the tax-engine boundary. |
|||
|
|||
Do not round intermediate calculations. Fractional quantities must remain supported because stock splits and some assets can produce non-integer quantities. |
|||
|
|||
Display/export rounding belongs to the Sep 24 export specification. |
|||
|
|||
## Working Acquisition Lot |
|||
|
|||
Each BUY creates one working FIFO lot containing at least: |
|||
|
|||
```text |
|||
sourceActivityId |
|||
acquiredAt |
|||
initialQuantity |
|||
remainingQuantity |
|||
unitCostAssetCurrency |
|||
grossAcquisitionValueAssetCurrency |
|||
buyFeeAssetCurrency |
|||
grossAcquisitionValueBaseCurrency |
|||
buyFeeBaseCurrency |
|||
currency |
|||
``` |
|||
|
|||
`sourceActivityId` points to the original Ghostfolio BUY. |
|||
|
|||
`remainingQuantity` begins equal to `initialQuantity` and decreases as SELL activities consume the lot. |
|||
|
|||
This is an in-memory FIFO structure. Permanent tax-lot persistence is a separate Sep 21 task. |
|||
|
|||
## BUY Processing |
|||
|
|||
For a BUY: |
|||
|
|||
1. validate the activity |
|||
2. create a new acquisition lot |
|||
3. append it to the end of the open-lot queue |
|||
4. do not calculate realized gain |
|||
|
|||
A BUY never consumes an older BUY lot. |
|||
|
|||
## SELL Processing |
|||
|
|||
For a SELL: |
|||
|
|||
```text |
|||
quantityToMatch = sell.quantity |
|||
``` |
|||
|
|||
While `quantityToMatch > 0`: |
|||
|
|||
1. take the oldest open lot |
|||
2. compute `matchedQuantity = min(quantityToMatch, lot.remainingQuantity)` |
|||
3. create a FIFO match record |
|||
4. subtract the matched quantity from the lot |
|||
5. subtract the matched quantity from the sale |
|||
6. remove the lot when its remaining quantity reaches zero |
|||
7. continue with the next oldest lot if needed |
|||
|
|||
Stop when the full sale is matched or no open lots remain. |
|||
|
|||
## Partial-Lot Consumption |
|||
|
|||
Example: |
|||
|
|||
```text |
|||
BUY 10 |
|||
SELL 4 |
|||
``` |
|||
|
|||
The lot remains open with: |
|||
|
|||
```text |
|||
remainingQuantity = 6 |
|||
``` |
|||
|
|||
A later SELL consumes those 6 before any newer lot. |
|||
|
|||
## Sale Across Multiple Lots |
|||
|
|||
Example: |
|||
|
|||
```text |
|||
BUY 100 |
|||
BUY 50 |
|||
SELL 120 |
|||
``` |
|||
|
|||
FIFO matches: |
|||
|
|||
```text |
|||
100 from BUY #1 |
|||
20 from BUY #2 |
|||
``` |
|||
|
|||
BUY #2 remains open with 30. |
|||
|
|||
Each consumption must be returned as a separate trace record. |
|||
|
|||
## FIFO Match Output |
|||
|
|||
Each matched BUY/SELL slice should expose data similar to: |
|||
|
|||
```text |
|||
buyActivityId |
|||
sellActivityId |
|||
buyDate |
|||
sellDate |
|||
matchedQuantity |
|||
|
|||
grossAcquisitionValueAssetCurrency |
|||
grossProceedsAssetCurrency |
|||
buyFeeAllocatedAssetCurrency |
|||
sellFeeAllocatedAssetCurrency |
|||
grossRealizedGainAssetCurrency |
|||
realizedGainAfterActivityFeesAssetCurrency |
|||
|
|||
grossAcquisitionValueBaseCurrency |
|||
grossProceedsBaseCurrency |
|||
buyFeeAllocatedBaseCurrency |
|||
sellFeeAllocatedBaseCurrency |
|||
grossRealizedGainBaseCurrency |
|||
realizedGainAfterActivityFeesBaseCurrency |
|||
``` |
|||
|
|||
This is calculation output, not necessarily a persisted Prisma model. |
|||
|
|||
## Gross Cost Basis |
|||
|
|||
For one matched slice: |
|||
|
|||
```text |
|||
grossAcquisitionValue = matchedQuantity × buyUnitPrice |
|||
``` |
|||
|
|||
For a SELL spanning multiple lots: |
|||
|
|||
```text |
|||
grossCostBasis = sum(grossAcquisitionValue of each match) |
|||
``` |
|||
|
|||
## Gross Sale Proceeds |
|||
|
|||
For one matched slice: |
|||
|
|||
```text |
|||
grossProceeds = matchedQuantity × sellUnitPrice |
|||
``` |
|||
|
|||
For a fully matched sale: |
|||
|
|||
```text |
|||
grossSaleProceeds = sellQuantity × sellUnitPrice |
|||
``` |
|||
|
|||
## Gross Realized Gain |
|||
|
|||
For each match: |
|||
|
|||
```text |
|||
grossRealizedGain = grossProceeds - grossAcquisitionValue |
|||
``` |
|||
|
|||
For the complete sale: |
|||
|
|||
```text |
|||
grossRealizedGainForSale = sum(match.grossRealizedGain) |
|||
``` |
|||
|
|||
A negative value is a realized loss. |
|||
|
|||
This is an analytics value and should not be labeled as universally "taxable gain." |
|||
|
|||
## Activity-Fee Treatment |
|||
|
|||
Fees should remain visible separately from gross basis/proceeds while also allowing a fee-adjusted analytics result. |
|||
|
|||
### BUY fee allocation |
|||
|
|||
```text |
|||
buyFeeAllocated = |
|||
buyFee × matchedQuantity / originalBuyQuantity |
|||
``` |
|||
|
|||
When the final remaining portion of a BUY lot is consumed, assign any tiny remaining fee-allocation balance to that final match so allocations sum exactly to the original fee. |
|||
|
|||
### SELL fee allocation |
|||
|
|||
```text |
|||
sellFeeAllocated = |
|||
sellFee × matchedQuantity / sellQuantity |
|||
``` |
|||
|
|||
Assign any tiny residual to the final match so allocated SELL fees sum exactly to the original SELL fee. |
|||
|
|||
### Fee-adjusted gain |
|||
|
|||
```text |
|||
realizedGainAfterActivityFees = |
|||
(grossProceeds - sellFeeAllocated) |
|||
- (grossAcquisitionValue + buyFeeAllocated) |
|||
``` |
|||
|
|||
Keep this separate from `grossRealizedGain`. |
|||
|
|||
### Standalone FEE activities |
|||
|
|||
Do not automatically allocate standalone `FEE` activities because the existing model does not link them to a specific trade. |
|||
|
|||
## Currency Behavior |
|||
|
|||
### Asset-profile currency |
|||
|
|||
Primary lot calculations should use: |
|||
|
|||
- `unitPriceInAssetProfileCurrency` |
|||
- `feeInAssetProfileCurrency` |
|||
|
|||
This gives a common currency for matching acquisition and sale values for the same asset. |
|||
|
|||
### User base currency |
|||
|
|||
Reuse historical transaction-date values already calculated by Ghostfolio. |
|||
|
|||
For a matched BUY portion: |
|||
|
|||
```text |
|||
acquisitionValueBase = |
|||
buy.valueInBaseCurrency |
|||
× matchedQuantity |
|||
/ buy.quantity |
|||
``` |
|||
|
|||
For a matched SELL portion: |
|||
|
|||
```text |
|||
proceedsBase = |
|||
sell.valueInBaseCurrency |
|||
× matchedQuantity |
|||
/ sell.quantity |
|||
``` |
|||
|
|||
Fees can be allocated from `feeInBaseCurrency` using the same matched-quantity proportions. |
|||
|
|||
This keeps BUY values tied to the BUY-date exchange rate and SELL values tied to the SELL-date exchange rate. |
|||
|
|||
### Missing FX data |
|||
|
|||
If historical base-currency conversion is unavailable, do not silently assume FX = 1 for tax analytics. |
|||
|
|||
Return the asset-currency result, leave the affected base-currency value unavailable, and return a traceable warning/error for the affected transaction. |
|||
|
|||
## Stock Splits |
|||
|
|||
Reuse Ghostfolio's existing split normalization: |
|||
|
|||
```text |
|||
adjustedQuantity = |
|||
originalQuantity × numerator / denominator |
|||
|
|||
adjustedUnitPrice = |
|||
originalUnitPrice × denominator / numerator |
|||
``` |
|||
|
|||
Total activity value remains unchanged. |
|||
|
|||
Pre-split BUY lots and post-split SELL quantities can then be matched using comparable adjusted quantities. |
|||
|
|||
Do not adjust provider market data a second time. |
|||
|
|||
Always retain the original activity ID for traceability. |
|||
|
|||
## Example 1 — Sale Across Two Lots |
|||
|
|||
```text |
|||
BUY #1: 100 @ 10.00, fee 2.00 |
|||
BUY #2: 50 @ 12.00, fee 1.00 |
|||
SELL #3: 120 @ 15.00, fee 3.00 |
|||
``` |
|||
|
|||
FIFO consumption: |
|||
|
|||
```text |
|||
100 from BUY #1 |
|||
20 from BUY #2 |
|||
``` |
|||
|
|||
Gross basis: |
|||
|
|||
```text |
|||
100 × 10.00 = 1000.00 |
|||
20 × 12.00 = 240.00 |
|||
grossCostBasis = 1240.00 |
|||
``` |
|||
|
|||
Gross proceeds: |
|||
|
|||
```text |
|||
120 × 15.00 = 1800.00 |
|||
``` |
|||
|
|||
Gross realized gain: |
|||
|
|||
```text |
|||
1800.00 - 1240.00 = 560.00 |
|||
``` |
|||
|
|||
Allocated BUY fees: |
|||
|
|||
```text |
|||
BUY #1 = 2.00 |
|||
BUY #2 = 1.00 × 20 / 50 = 0.40 |
|||
total = 2.40 |
|||
``` |
|||
|
|||
Allocated SELL fee: |
|||
|
|||
```text |
|||
3.00 |
|||
``` |
|||
|
|||
Fee-adjusted gain: |
|||
|
|||
```text |
|||
(1800.00 - 3.00) - (1240.00 + 2.40) |
|||
= 554.60 |
|||
``` |
|||
|
|||
Remaining lot: |
|||
|
|||
```text |
|||
BUY #2 remaining quantity = 30 |
|||
remaining unallocated BUY fee = 0.60 |
|||
``` |
|||
|
|||
## Example 2 — Partial Lot |
|||
|
|||
```text |
|||
BUY: 10 @ 100.00, fee 5.00 |
|||
SELL: 4 @ 130.00, fee 2.00 |
|||
``` |
|||
|
|||
Results: |
|||
|
|||
```text |
|||
gross acquisition value = 400.00 |
|||
gross proceeds = 520.00 |
|||
gross realized gain = 120.00 |
|||
|
|||
allocated BUY fee = 5.00 × 4 / 10 = 2.00 |
|||
allocated SELL fee = 2.00 |
|||
|
|||
fee-adjusted gain = |
|||
(520.00 - 2.00) - (400.00 + 2.00) |
|||
= 116.00 |
|||
|
|||
remaining lot quantity = 6 |
|||
remaining unallocated BUY fee = 3.00 |
|||
``` |
|||
|
|||
## Example 3 — Stock Split |
|||
|
|||
Original BUY: |
|||
|
|||
```text |
|||
10 shares @ 100.00 |
|||
fee = 5.00 |
|||
``` |
|||
|
|||
After a 2:1 split: |
|||
|
|||
```text |
|||
20 shares @ 50.00 |
|||
``` |
|||
|
|||
Later SELL: |
|||
|
|||
```text |
|||
5 shares @ 60.00 |
|||
fee = 1.00 |
|||
``` |
|||
|
|||
FIFO result: |
|||
|
|||
```text |
|||
gross acquisition value = 5 × 50.00 = 250.00 |
|||
gross proceeds = 5 × 60.00 = 300.00 |
|||
gross realized gain = 50.00 |
|||
|
|||
allocated BUY fee = 5.00 × 5 / 20 = 1.25 |
|||
allocated SELL fee = 1.00 |
|||
|
|||
fee-adjusted gain = |
|||
(300.00 - 1.00) - (250.00 + 1.25) |
|||
= 47.75 |
|||
|
|||
remaining adjusted shares = 15 |
|||
``` |
|||
|
|||
## Closed Position and New Cycle |
|||
|
|||
When every open lot in the current scope reaches zero, the long position is fully closed. |
|||
|
|||
A later BUY starts a new FIFO cycle. Fully consumed lots remain available in history/output for traceability but cannot be consumed again. |
|||
|
|||
## Invalid and Unsupported Conditions |
|||
|
|||
### Non-positive quantity |
|||
|
|||
A BUY or SELL with quantity <= 0 is invalid FIFO input and must not create or consume a lot. |
|||
|
|||
### Negative unit price or fee |
|||
|
|||
Negative price or fee values are invalid for this engine. The DTO layer already prevents these in normal input, but the engine should validate defensively. |
|||
|
|||
### SELL with insufficient open lots |
|||
|
|||
If a SELL exceeds available open BUY quantity, do not create a negative lot. |
|||
|
|||
Match the valid portion if desired, but record: |
|||
|
|||
```text |
|||
unmatchedQuantity |
|||
error = INSUFFICIENT_OPEN_LOTS |
|||
``` |
|||
|
|||
Do not include the unmatched portion as a valid FIFO realized gain. |
|||
|
|||
### SELL before any BUY |
|||
|
|||
Treat as the same insufficient-lot condition. |
|||
|
|||
Ghostfolio's existing performance calculator can represent short positions, but short-sale tax treatment is outside this long-lot FIFO specification. |
|||
|
|||
### Missing base-currency conversion |
|||
|
|||
If asset-currency inputs are valid but historical FX is missing: |
|||
|
|||
- continue asset-currency FIFO |
|||
- leave affected base-currency values unavailable |
|||
- return a traceable conversion warning/error |
|||
|
|||
## Reconciliation Rules |
|||
|
|||
For a valid long-only scope: |
|||
|
|||
```text |
|||
total adjusted BUY quantity |
|||
- total successfully matched SELL quantity |
|||
= total remaining open-lot quantity |
|||
``` |
|||
|
|||
For each fully matched SELL: |
|||
|
|||
```text |
|||
sum(match.matchedQuantity) = sell.quantity |
|||
``` |
|||
|
|||
For each fully consumed BUY: |
|||
|
|||
```text |
|||
sum(allocated BUY fees) = original BUY fee |
|||
``` |
|||
|
|||
For each fully matched SELL: |
|||
|
|||
```text |
|||
sum(allocated SELL fees) = original SELL fee |
|||
``` |
|||
|
|||
These invariants should be unit-tested. |
|||
|
|||
## Proposed Sale-Level Result |
|||
|
|||
In addition to lot-level matches, one aggregate result per SELL should contain: |
|||
|
|||
```text |
|||
sellActivityId |
|||
sellDate |
|||
currency |
|||
|
|||
sellQuantity |
|||
matchedQuantity |
|||
unmatchedQuantity |
|||
|
|||
grossProceeds |
|||
grossCostBasis |
|||
grossRealizedGain |
|||
|
|||
allocatedBuyFees |
|||
sellFee |
|||
realizedGainAfterActivityFees |
|||
|
|||
matches[] |
|||
errors[] |
|||
``` |
|||
|
|||
Add base-currency equivalents when conversion data is available. |
|||
|
|||
## Realized vs Unrealized |
|||
|
|||
This FIFO engine calculates realized gain only when a SELL consumes a BUY lot. |
|||
|
|||
Open lots are returned for future matching and traceability. This Sep 17 specification does not calculate unrealized gain. |
|||
|
|||
## Planned Iteration 2 Tests |
|||
|
|||
Use exact expected numeric results for: |
|||
|
|||
- one BUY and one full SELL |
|||
- one BUY and a partial SELL |
|||
- multiple BUY lots and one SELL spanning lots |
|||
- multiple sequential SELL activities |
|||
- closed position followed by a new BUY cycle |
|||
- zero fees |
|||
- non-zero BUY and SELL fees |
|||
- fractional quantities |
|||
- same timestamp with deterministic ID ordering |
|||
- normal stock split |
|||
- reverse split |
|||
- multiple stock splits |
|||
- missing base-currency conversion |
|||
- SELL greater than available lots |
|||
- SELL before any BUY |
|||
- separate assets never matching |
|||
- separate lot-matching scopes never matching |
|||
|
|||
Tests must verify numeric results and source-activity traceability, not only successful execution. |
|||
|
|||
## Implementation Boundary — Iteration 2 |
|||
|
|||
FIFO should be tax-domain business logic, not a modification of the existing ROAI portfolio calculator. |
|||
|
|||
The ROAI path currently uses average-price behavior and is shared with performance functionality. Changing it to FIFO could alter Arthur's performance-chart behavior. |
|||
|
|||
The Iteration 2 FIFO implementation should instead: |
|||
|
|||
1. obtain enriched activities from existing Ghostfolio services |
|||
2. apply existing stock-split normalization |
|||
3. filter/group eligible BUY and SELL activities |
|||
4. run the FIFO engine |
|||
5. return realized-gain matches, aggregate sale results, and remaining lots |
|||
|
|||
A candidate future location is: |
|||
|
|||
`apps/api/src/app/tax/` |
|||
|
|||
This is a recommendation, not a fixed shared-architecture decision. Raniya owns architecture/integration, so the final tax service boundary and shared contract location should be coordinated with her before implementation. |
|||
|
|||
## Likely Relevant Files in Iteration 2 |
|||
|
|||
- `apps/api/src/app/activities/activities.service.ts` |
|||
- `apps/api/src/services/asset-profile-split/asset-profile-split.helper.ts` |
|||
- `apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts` |
|||
- `libs/common/src/lib/interfaces/activities.interface.ts` |
|||
|
|||
New tax-specific service, interface, and test files will also be needed. |
|||
|
|||
No change to the existing ROAI performance formulas is required for FIFO tax calculation. |
|||
|
|||
## Acceptance Criteria |
|||
|
|||
The FIFO specification is complete when: |
|||
|
|||
- only eligible BUY/SELL activities participate |
|||
- deterministic ordering uses full activity date then activity ID |
|||
- each BUY creates one acquisition lot |
|||
- each SELL consumes the oldest available lot first |
|||
- partial lots keep the correct remainder |
|||
- one SELL can consume multiple lots |
|||
- every match references its BUY and SELL source activities |
|||
- gross cost basis and gross proceeds are defined |
|||
- gross realized gain is defined |
|||
- attached BUY/SELL fees are allocated deterministically |
|||
- fee-adjusted gain remains separate from gross gain |
|||
- asset-profile and base-currency behavior are defined |
|||
- missing FX behavior is defined |
|||
- stock splits reuse Ghostfolio's existing normalization |
|||
- insufficient holdings are reported instead of creating negative lots |
|||
- short-sale tax handling is outside the current scope |
|||
- open lots remain distinct from realized results |
|||
- reconciliation invariants are defined |
|||
- Iteration 2 numeric tests are identified |
|||
- no jurisdiction-specific tax rules are invented |
|||
- existing portfolio-performance logic remains unchanged |
|||
|
|||
## Open Questions for Later Iteration 1 Tasks |
|||
|
|||
### Sep 21 — Tax-lot data model |
|||
|
|||
Decide: |
|||
|
|||
- permanent lot schema |
|||
- final account/lot pooling scope |
|||
- which results are stored versus recalculated |
|||
- open/closed lot lifecycle fields |
|||
- relationships among source activities, lots, and sale matches |
|||
|
|||
### Sep 22 — Yearly summary |
|||
|
|||
Define how realized FIFO results are grouped by year. |
|||
|
|||
### Sep 23 — Tax-relevant filtering |
|||
|
|||
Define the explicit tax-relevant flag and its interaction with existing draft/excluded filters. |
|||
|
|||
### Sep 24 — Export |
|||
|
|||
Define output precision, display/export rounding, and FIFO trace fields for CSV/PDF. |
|||
|
|||
## Out of Scope |
|||
|
|||
- average-cost method |
|||
- permanent tax-lot persistence |
|||
- yearly tax-summary schema |
|||
- tax-relevant flag implementation |
|||
- React/UI work |
|||
- jurisdiction-specific tax rates |
|||
- holding-period classifications |
|||
- wash-sale/superficial-loss rules |
|||
- short-sale tax matching |
|||
- undefined account pooling rules |
|||
- corporate actions beyond Ghostfolio's existing stock-split support |
|||
|
|||
## Next Task |
|||
|
|||
The next scheduled tax task is September 18: |
|||
|
|||
**Specify the capital-gains average-cost method.** |
|||
|
|||
That specification should reuse the same filtering, precision, currency, split, validation, and traceability principles where applicable while replacing FIFO lot consumption with a pooled average-cost basis. |
|||
Loading…
Reference in new issue