docs: add PORTFOLIO and DISPLAY roadmap items for multi-position mode switching

This commit is contained in:
Bu5hm4nn
2026-03-28 20:59:29 +01:00
parent fd51f1e204
commit 447f4bbd0d
8 changed files with 205 additions and 0 deletions

View File

@@ -13,12 +13,19 @@ notes:
- Pre-alpha policy: we may cut or replace old features without backward compatibility until alpha is declared. - Pre-alpha policy: we may cut or replace old features without backward compatibility until alpha is declared.
- Alpha migration policy: once alpha is declared, compatibility only needs to move forward; backward migrations are not required. - Alpha migration policy: once alpha is declared, compatibility only needs to move forward; backward migrations are not required.
priority_queue: priority_queue:
- PORTFOLIO-001
- DISPLAY-001
- DISPLAY-002
- EXEC-002 - EXEC-002
- PORTFOLIO-002
- PORTFOLIO-003
- CONV-001
- DATA-002A - DATA-002A
- DATA-001A - DATA-001A
- OPS-001 - OPS-001
- BT-003 - BT-003
- BT-002A - BT-002A
- GCF-001
recently_completed: recently_completed:
- DATA-004 - DATA-004
- PRICING-003 - PRICING-003
@@ -41,12 +48,19 @@ recently_completed:
- CORE-002B - CORE-002B
states: states:
backlog: backlog:
- PORTFOLIO-001
- DISPLAY-001
- DISPLAY-002
- EXEC-002 - EXEC-002
- PORTFOLIO-002
- PORTFOLIO-003
- CONV-001
- DATA-002A - DATA-002A
- DATA-001A - DATA-001A
- OPS-001 - OPS-001
- BT-003 - BT-003
- BT-002A - BT-002A
- GCF-001
in_progress: [] in_progress: []
done: done:
- DATA-004 - DATA-004

View File

@@ -0,0 +1,25 @@
id: CONV-001
title: Historical GLD Backing Lookup
status: backlog
priority: P1
size: S
depends_on:
- PRICING-001
tags: [conversion, historical]
summary: Support lookups of GLD ounces-per-share for historical dates.
acceptance_criteria:
- `gld_ounces_per_share(entry_date)` returns accurate backing for any date
- Formula validates against known points (launch 2004, current 2026)
- Conversion between GLD shares and gold oz uses entry_date backing
- Error handling for dates outside GLD lifetime (pre-2004)
- Cache-friendly for repeated lookups
notes:
- GLD launched Nov 2004 with 0.10 oz/share
- Current backing ~0.0916 oz/share (2026)
- Formula: `0.10 × e^(-0.004 × years_since_2004)`
- Future enhancement: fetch actual NAV/ounce from spdrgoldshares.com
implementation_hints:
- Already implemented for current date in `app/domain/instruments.py`
- Extend to accept `date` parameter
- Clamp dates to GLD lifetime (Nov 2004 to present)
- Add historical validation tests

View File

@@ -0,0 +1,31 @@
id: DISPLAY-001
title: Underlying Mode Switching
status: backlog
priority: P0
size: M
depends_on:
- PORTFOLIO-001
- DATA-004
tags: [ui, display-mode]
summary: Allow the entire app to switch between GLD, GC=F, and XAU display modes.
acceptance_criteria:
- Settings page has "Display Mode" selector: GLD shares, Gold Ounces, Gold Grams, GC=F contracts
- In GLD mode: GLD positions show direct values, other positions converted using entry-date GLD backing
- In Gold mode: All positions converted to equivalent oz/g using entry-date conversions
- In GC=F mode: GC=F positions show direct values, others converted
- Overview shows collateral in display mode units
- Hedge page shows protection in display mode units
- Backtests show results in display mode units
- Mode persists per workspace
notes:
- Conversion uses GLD_ounces_per_share(entry_date) for historical accuracy
- GC=F contract size is fixed at 100 oz
- XAU grams to oz conversion: 31.1035 g/oz
- Mode switching is a display concern, not a data concern
- Position data remains in original units
implementation_hints:
- Add `display_mode: "GLD" | "XAU_OZ" | "XAU_G" | "GCF"` to `PortfolioConfig`
- Add `convert_to_display(position, display_mode, reference_date)` in `app/domain/conversions.py`
- Reuse existing `gld_ounces_per_share(date)` for historical conversion
- Overview/hedge/backtests use display conversion layer
- Settings page saves display_mode preference

View File

@@ -0,0 +1,27 @@
id: DISPLAY-002
title: GLD Mode Shows Real GLD Pricing
status: backlog
priority: P0
size: S
depends_on:
- DISPLAY-001
tags: [ui, gld-mode]
summary: In GLD display mode, show real GLD share prices without conversion to physical gold.
acceptance_criteria:
- GLD mode shows position quantity as "shares" not "oz"
- GLD mode shows position value as `shares × GLD_price` not converted to gold
- Entry price shown as GLD share price at time of purchase
- P&L calculated as `(current_GLD_price - entry_GLD_price) × shares`
- LTV calculated using GLD position value as collateral
- Options strike selection in GLD mode uses GLD share prices directly
- Hedge cost shown as $/share or $/position, not $/oz
notes:
- This is the key insight: GLD mode should NOT convert to gold
- User bought shares, they think in shares, they should see share metrics
- GLD backing decay is irrelevant in GLD mode (baked into price)
- Only when switching to XAU mode is conversion needed
implementation_hints:
- GLD mode short-circuits conversion logic
- Overview in GLD mode: position value = shares × GLD_price
- Hedge in GLD mode: strike = GLD_share_price × strike_pct
- Add `position.is_gld()` helper to check display treatment

View File

@@ -0,0 +1,25 @@
id: GCF-001
title: GC=F Options Data Source
status: backlog
priority: P2
size: L
depends_on:
- DATA-004
tags: [data-source, options, futures]
summary: Wire GC=F futures options data for users who choose GC=F as primary underlying.
acceptance_criteria:
- GC=F underlying fetches live options chain from CME or equivalent source
- Options chain includes: strikes, expirations, bid/ask, IV, delta
- Options displayed in futures contract units (100 oz per contract)
- Strike selection in GC=F mode uses futures prices directly
- Fallback to estimated options if live data unavailable
notes:
- GC=F is COMEX Gold Futures, contract size = 100 troy oz
- Options on futures have different quoting than equity options
- May need paid data feed (CME, ICE, broker API)
- Alternative: estimate from GLD options + basis
implementation_hints:
- Add `get_gcf_options_chain()` to `DataService`
- Contract size: 100 oz per futures option
- Explore yfinance GC=F options (limited) vs paid sources
- Cache aggressively to minimize API calls

View File

@@ -0,0 +1,26 @@
id: PORTFOLIO-001
title: Position-Level Portfolio Entries
status: backlog
priority: P0
size: M
depends_on: []
tags: [portfolio, domain-model]
summary: Evolve from single-quantity portfolio to multi-position entries with individual entry dates and prices.
acceptance_criteria:
- User can add a position entry with underlying type (GLD shares, GC=F contracts, XAU grams/oz)
- Each position has its own entry_price, entry_date, quantity
- Portfolio page shows list of positions with individual P&L
- Portfolio total collateral is sum of all position values in display currency
- Backward compatible: existing single-entry portfolios migrate to one position entry
- Settings page has "Add Position" and "Remove Position" controls
- Position CRUD persists to workspace storage
notes:
- This is foundational for mode switching between GLD/GC=F/physical gold
- Entry_date matters for conversion lookups (GLD backing varies by date)
- Single-quantity legacy portfolios should auto-migrate to one position
implementation_hints:
- Add `Position` dataclass in `app/models/portfolio.py`
- Add `positions: List[Position]` to `PortfolioConfig` (migrate `gold_ounces`/`entry_price` to first position)
- Position underlying defaults to "GLD" for backward compat
- Entry_date defaults to position creation date if not specified
- Storage costs deferred to PORTFOLIO-002

View File

@@ -0,0 +1,26 @@
id: PORTFOLIO-002
title: Position Storage Costs
status: backlog
priority: P1
size: S
depends_on:
- PORTFOLIO-001
tags: [portfolio, costs]
summary: Allow users to configure storage costs for portfolio positions.
acceptance_criteria:
- Each position can have optional storage_cost_basis (annual % or fixed amount)
- Storage cost period selectable: annual, monthly
- Overview page shows aggregate storage cost impact on net equity
- Hedge recommendations account for storage drag
- Physical gold positions default to typical vault storage (e.g., 0.12% annual for allocated)
- GLD positions note that expense ratio is already baked into price
notes:
- For GLD: expense ratio decay is implicit, no separate storage cost needed
- For GC=F: roll costs (contango) are the primary storage analog
- For physical XAU: vault storage is explicit cost
- Storage costs affect LTV calculations (reduce effective equity)
implementation_hints:
- Add `storage_cost_basis: Decimal | None` to `Position`
- Add `storage_cost_period: "annual" | "monthly" | None` to `Position`
- Compute annualized storage cost for equity/P&L display
- Add storage cost breakdown to overview page

View File

@@ -0,0 +1,31 @@
id: PORTFOLIO-003
title: Physical Gold Premium and Spread
status: backlog
priority: P1
size: S
depends_on:
- PORTFOLIO-001
tags: [portfolio, physical-gold]
summary: Support dealer premium and bid/ask spread for physical gold positions.
acceptance_criteria:
- Position can specify `purchase_premium` (dealer markup over spot)
- Position can specify `bid_ask_spread` (expected sale discount below spot)
- True cost basis = entry_price + purchase_premium
- Effective exit value = spot - bid_ask_spread
- P&L shows both paper P&L and realized P&L accounting for spread
- Default premium/spread values for common products (coins, bars)
- Settings page exposes premium/spread inputs for XAU positions
notes:
- Common gold products:
- Gold ETF (GLD): 0% premium (market price), spread ~0.1%
- Gold coins (1oz): 3-5% premium, 2-4% spread
- Gold bars (1kg): 1-2% premium, 1-2% spread
- Allocated storage (Goldmoney, BullionVault): 0.1% premium, 0.3% spread
- Premium lowers effective entry price
- Spread lowers effective exit price
implementation_hints:
- Add `purchase_premium: Decimal | None` to `Position` (percentage)
- Add `bid_ask_spread: Decimal | None` to `Position` (percentage)
- Cost basis formula: `effective_entry = spot_at_entry × (1 + premium)`
- Exit value formula: `effective_exit = current_spot × (1 - spread)`
- Add premium/spread presets to settings UI