diff --git a/docs/roadmap/ROADMAP.yaml b/docs/roadmap/ROADMAP.yaml index dd7bf96..966cee0 100644 --- a/docs/roadmap/ROADMAP.yaml +++ b/docs/roadmap/ROADMAP.yaml @@ -13,12 +13,19 @@ notes: - 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. priority_queue: + - PORTFOLIO-001 + - DISPLAY-001 + - DISPLAY-002 - EXEC-002 + - PORTFOLIO-002 + - PORTFOLIO-003 + - CONV-001 - DATA-002A - DATA-001A - OPS-001 - BT-003 - BT-002A + - GCF-001 recently_completed: - DATA-004 - PRICING-003 @@ -41,12 +48,19 @@ recently_completed: - CORE-002B states: backlog: + - PORTFOLIO-001 + - DISPLAY-001 + - DISPLAY-002 - EXEC-002 + - PORTFOLIO-002 + - PORTFOLIO-003 + - CONV-001 - DATA-002A - DATA-001A - OPS-001 - BT-003 - BT-002A + - GCF-001 in_progress: [] done: - DATA-004 diff --git a/docs/roadmap/backlog/CONV-001-historical-gld-backing.yaml b/docs/roadmap/backlog/CONV-001-historical-gld-backing.yaml new file mode 100644 index 0000000..0bc80e4 --- /dev/null +++ b/docs/roadmap/backlog/CONV-001-historical-gld-backing.yaml @@ -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 \ No newline at end of file diff --git a/docs/roadmap/backlog/DISPLAY-001-underlying-mode-switching.yaml b/docs/roadmap/backlog/DISPLAY-001-underlying-mode-switching.yaml new file mode 100644 index 0000000..4328e56 --- /dev/null +++ b/docs/roadmap/backlog/DISPLAY-001-underlying-mode-switching.yaml @@ -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 \ No newline at end of file diff --git a/docs/roadmap/backlog/DISPLAY-002-gld-mode-no-conversion.yaml b/docs/roadmap/backlog/DISPLAY-002-gld-mode-no-conversion.yaml new file mode 100644 index 0000000..9f09892 --- /dev/null +++ b/docs/roadmap/backlog/DISPLAY-002-gld-mode-no-conversion.yaml @@ -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 \ No newline at end of file diff --git a/docs/roadmap/backlog/GCF-001-gcf-options-data.yaml b/docs/roadmap/backlog/GCF-001-gcf-options-data.yaml new file mode 100644 index 0000000..d372b57 --- /dev/null +++ b/docs/roadmap/backlog/GCF-001-gcf-options-data.yaml @@ -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 \ No newline at end of file diff --git a/docs/roadmap/backlog/PORTFOLIO-001-position-entries.yaml b/docs/roadmap/backlog/PORTFOLIO-001-position-entries.yaml new file mode 100644 index 0000000..0b999d6 --- /dev/null +++ b/docs/roadmap/backlog/PORTFOLIO-001-position-entries.yaml @@ -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 \ No newline at end of file diff --git a/docs/roadmap/backlog/PORTFOLIO-002-position-storage-costs.yaml b/docs/roadmap/backlog/PORTFOLIO-002-position-storage-costs.yaml new file mode 100644 index 0000000..3d17f75 --- /dev/null +++ b/docs/roadmap/backlog/PORTFOLIO-002-position-storage-costs.yaml @@ -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 \ No newline at end of file diff --git a/docs/roadmap/backlog/PORTFOLIO-003-physical-gold-premium-spread.yaml b/docs/roadmap/backlog/PORTFOLIO-003-physical-gold-premium-spread.yaml new file mode 100644 index 0000000..f67a2bd --- /dev/null +++ b/docs/roadmap/backlog/PORTFOLIO-003-physical-gold-premium-spread.yaml @@ -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 \ No newline at end of file