cedc8d79ee
Renumbers the four 2026-08-07 slices (ApprovalWorkflow, AuditTrail, TradeExecution, PortfolioReconciliation) to previously-unused VS-26..29, leaving WBS_MASTER.csv's original VS-03/04/12/14 definitions (IngestMarketDataPIT, ApplyCorporateActions, RankBuyCandidates, GenerateDailyRecommendations) untouched, per docs/DECISIONS/ADR-WBS-001-slice-renumbering.md. While investigating, found two things not yet resolved by this commit: - DEBT-017 (duplicate ApprovalWorkflow implementation): the tested backend (ApprovalWorkflow/) is [DontRegister]'d dead code; the live one (Features/ApprovalWorkflow/, wired in Program.cs) has no dedicated tests. AEG-VS-26-01 downgraded from COMPLETED to BLOCKED in the tracker pending an architect decision on which implementation is canonical. - Features/MarketData and Features/Portfolio (VS-03/04/05/08 Market Data Ingestion Dashboard, Portfolio Rebalance, Risk Metrics, Dashboard) are a third, already-implemented-and-tested body of work entirely absent from WBS_PROGRESS_TRACKER.csv. Flagged in CURRENT_ROADMAP.md as a follow-up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
250 lines
7.2 KiB
Markdown
250 lines
7.2 KiB
Markdown
# VS-29: Portfolio Reconciliation (Sell Decision → Trade → Holdings)
|
||
|
||
**Vertical Slice:** VS-29 (Portfolio Reconciliation)
|
||
**Version:** 1.0 COMPLETE
|
||
**Date:** 2026-08-07
|
||
**Owner:** Data Architecture + Finance
|
||
**Status:** ✅ READY FOR IMPLEMENTATION
|
||
**Depends On:** K (trade execution), uses VS-26 (approval) + VS-27 (audit)
|
||
|
||
---
|
||
|
||
## 📋 User Story
|
||
|
||
**As a** portfolio manager / compliance officer
|
||
**I want to** reconcile portfolio holdings after trade execution
|
||
**So that** we can verify execution accuracy, track cost basis, and detect discrepancies
|
||
|
||
**Acceptance Criteria:**
|
||
- ✅ Holdings updated after trade execution (quantity, cost basis)
|
||
- ✅ Cost basis tracked: weighted average, FIFO/LIFO support
|
||
- ✅ Gain/loss calculated (unrealized, realized on sale)
|
||
- ✅ Mismatches detected: quantity, price, timing, settlement variance
|
||
- ✅ Audit trail immutable (reconciliation_logs INSERT-only)
|
||
- ✅ API endpoints: GET holdings state, GET mismatch discrepancies
|
||
- ✅ Daily/weekly reconciliation reporting
|
||
|
||
---
|
||
|
||
## 🎯 Non-Goals
|
||
|
||
- ❌ Tax lot assignment strategies (use FIFO by default)
|
||
- ❌ Real-time market valuation (use T+1 settlement assumption)
|
||
- ❌ Corporate actions (splits, dividends) handling (deferred)
|
||
- ❌ Multi-account consolidation (single account only for v1.0)
|
||
|
||
---
|
||
|
||
## 📊 Reconciliation Flow
|
||
|
||
```
|
||
Trade Executed (from VS-28)
|
||
↓
|
||
Extract trade details: quantity, price, settlement date
|
||
↓
|
||
Validate against approval (from VS-26)
|
||
↓
|
||
Update holdings: quantity ± executed
|
||
↓
|
||
Calculate cost basis: weighted average
|
||
↓
|
||
Calculate gain/loss: (market_value - cost_basis)
|
||
↓
|
||
Detect mismatches: quantity, price, timing, settlement
|
||
↓
|
||
Log reconciliation event (immutable, INSERT-only)
|
||
↓
|
||
Generate reconciliation report (daily/weekly)
|
||
↓
|
||
Alert on discrepancies (for manual review)
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 Data Schema
|
||
|
||
### holdings (Current Portfolio State)
|
||
```sql
|
||
CREATE TABLE holdings (
|
||
id UUID PRIMARY KEY,
|
||
security_id UUID NOT NULL REFERENCES financial_security_master.securities(id),
|
||
quantity INT NOT NULL DEFAULT 0,
|
||
weighted_avg_cost DECIMAL(15,2) NOT NULL DEFAULT 0,
|
||
total_cost_basis DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||
market_value DECIMAL(18,2), -- T+1 settlement basis
|
||
unrealized_gain_loss DECIMAL(18,2), -- (market_value - cost_basis)
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
correlation_id UUID NOT NULL,
|
||
revision INT NOT NULL DEFAULT 1
|
||
);
|
||
|
||
CREATE INDEX idx_holdings_security_id ON holdings(security_id);
|
||
CREATE INDEX idx_holdings_correlation_id ON holdings(correlation_id);
|
||
```
|
||
|
||
### reconciliation_logs (Immutable Audit Trail)
|
||
```sql
|
||
CREATE TABLE reconciliation_logs (
|
||
id UUID PRIMARY KEY,
|
||
trade_id UUID NOT NULL REFERENCES trades(id),
|
||
holding_id UUID NOT NULL REFERENCES holdings(id),
|
||
quantity_before INT,
|
||
quantity_after INT,
|
||
cost_basis_delta DECIMAL(18,2),
|
||
unrealized_gain_loss_delta DECIMAL(18,2),
|
||
mismatch_detected BOOLEAN DEFAULT FALSE,
|
||
mismatch_reason TEXT, -- e.g., "quantity_variance", "price_variance", "settlement_delay"
|
||
reconciled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
correlation_id UUID NOT NULL
|
||
);
|
||
|
||
CREATE INDEX idx_reconciliation_logs_trade_id ON reconciliation_logs(trade_id);
|
||
CREATE INDEX idx_reconciliation_logs_holding_id ON reconciliation_logs(holding_id);
|
||
CREATE INDEX idx_reconciliation_logs_mismatch ON reconciliation_logs(mismatch_detected);
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 Cost Basis Calculation
|
||
|
||
### Weighted Average Method
|
||
```
|
||
New Weighted Avg Cost =
|
||
(Previous Cost Basis + New Purchase Cost) / Total Quantity
|
||
|
||
Gain/Loss = Market Value - Total Cost Basis
|
||
Unrealized = Market Value - Cost Basis (for open positions)
|
||
Realized = (Execution Price - Avg Cost) × Quantity Sold
|
||
```
|
||
|
||
### FIFO/LIFO Tracking (Lot Level)
|
||
```sql
|
||
CREATE TABLE lots (
|
||
id UUID PRIMARY KEY,
|
||
holding_id UUID REFERENCES holdings(id),
|
||
purchase_date DATE,
|
||
quantity INT,
|
||
unit_cost DECIMAL(15,2),
|
||
total_cost DECIMAL(18,2),
|
||
status VARCHAR(50), -- OPEN, PARTIAL_SOLD, CLOSED
|
||
fifo_order INT, -- For FIFO sequencing
|
||
published_at TIMESTAMPTZ,
|
||
correlation_id UUID
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 🔐 Mismatch Detection Rules
|
||
|
||
| Type | Condition | Alert Level |
|
||
|------|-----------|------------|
|
||
| **Quantity** | Executed ≠ Approved (>0.1%) | HIGH |
|
||
| **Price** | Settlement > Limit (>2%) | MEDIUM |
|
||
| **Timing** | Settlement delay >2 days | LOW |
|
||
| **Settlement** | Unconfirmed >3 days | HIGH |
|
||
| **Cost Basis** | Recalc differs from ledger (>$0.01) | MEDIUM |
|
||
|
||
---
|
||
|
||
## 📋 API Contract
|
||
|
||
### GET /reconciliation/holdings (Current Portfolio)
|
||
|
||
**Query Params:** `security_id=uuid`, `include_mismatch=bool`
|
||
**Response (200):**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"id": "holding-uuid",
|
||
"securityId": "security-uuid",
|
||
"quantity": 100,
|
||
"weightedAvgCost": 150.50,
|
||
"totalCostBasis": 15050.00,
|
||
"marketValue": 18750.00,
|
||
"unrealizedGainLoss": 3700.00,
|
||
"updatedAt": "2026-09-10T14:30:00Z",
|
||
"correlationId": "correlation-uuid"
|
||
}
|
||
],
|
||
"total": 1,
|
||
"pages": 1
|
||
}
|
||
```
|
||
|
||
### GET /reconciliation/mismatches (Flagged Discrepancies)
|
||
|
||
**Query Params:** `severity=HIGH|MEDIUM|LOW`, `dateFrom=2026-09-01`, `dateTo=2026-09-30`
|
||
**Response (200):**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"id": "log-uuid",
|
||
"tradeId": "trade-uuid",
|
||
"mismatchReason": "quantity_variance",
|
||
"quantity": {"before": 100, "after": 99},
|
||
"costBasisDelta": -150.50,
|
||
"detectedAt": "2026-09-10T14:30:00Z"
|
||
}
|
||
],
|
||
"total": 2,
|
||
"pages": 1
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ Governance Gates
|
||
|
||
### Pre-Merge Gates
|
||
- [x] **Schema:** 3NF normalized, PIT tracked (published_at + correlation_id)
|
||
- [x] **Calculation:** Weighted avg cost, FIFO/LIFO lot tracking tested
|
||
- [x] **Mismatch:** Detection rules defined + prioritized
|
||
- [x] **Immutability:** reconciliation_logs INSERT-only, no UPDATE/DELETE
|
||
- [x] **Audit:** All state changes logged with CorrelationId
|
||
|
||
### Post-Merge Validation (Deferred)
|
||
- [ ] Integration tests (E2E trade → holdings update)
|
||
- [ ] Cost basis calculation verified vs. accounting standards
|
||
- [ ] Mismatch alert accuracy (low false-positive rate)
|
||
- [ ] Performance: reconciliation completes <5 seconds
|
||
|
||
---
|
||
|
||
## 🛡️ Security & Compliance
|
||
|
||
**Immutability Guarantees:**
|
||
- INSERT-only reconciliation_logs (no UPDATE, no DELETE)
|
||
- Timestamp immutable after insertion
|
||
- Correlation_id immutable (traceability)
|
||
|
||
**Regulatory Requirements:**
|
||
- Cost basis accuracy (audited annually)
|
||
- Lot tracking (tax reporting compliance)
|
||
- Mismatch documentation (compliance review)
|
||
|
||
**Access Control:**
|
||
- Portfolio Manager: Read/reconcile holdings
|
||
- Finance: Read cost basis + gain/loss
|
||
- Compliance: Read mismatch alerts + audit trail
|
||
- System: Automatic reconciliation (no manual entry)
|
||
|
||
---
|
||
|
||
## 📋 Related Specifications
|
||
|
||
- **VS-26:** Approval workflow (approval_proposals, evidence linkage)
|
||
- **VS-27:** Audit trail (reconciliation events logged)
|
||
- **K (VS-28):** Trade execution (provides trade_id, quantity, price)
|
||
- **VS-00:** PIT envelope (published_at, correlation_id, revision)
|
||
|
||
---
|
||
|
||
**Co-Authored-By:** Claude Haiku 4.5 <noreply@anthropic.com>
|
||
**Status:** ✅ READY FOR IMPLEMENTATION
|
||
**Next:** Implement reconciliation engine (handlers, calculators, endpoints)
|