[OQI-008] feat: Add portfolio DDL schema with tables for portfolios, allocations, goals
- Added portfolio schema to 01-schemas.sql - Created enums: risk_profile, goal_status, rebalance_action, allocation_status - Created tables: portfolios, portfolio_allocations, portfolio_goals - Created tables: rebalance_history, portfolio_snapshots - Added triggers for updated_at and goal progress calculations - Added indexes for performance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
439489bde4
commit
3fbb1c21e6
@ -20,6 +20,10 @@ COMMENT ON SCHEMA trading IS 'Trading bots, orders, positions, and market data';
|
|||||||
CREATE SCHEMA IF NOT EXISTS investment;
|
CREATE SCHEMA IF NOT EXISTS investment;
|
||||||
COMMENT ON SCHEMA investment IS 'Investment products, accounts, and transactions';
|
COMMENT ON SCHEMA investment IS 'Investment products, accounts, and transactions';
|
||||||
|
|
||||||
|
-- Portfolio Management
|
||||||
|
CREATE SCHEMA IF NOT EXISTS portfolio;
|
||||||
|
COMMENT ON SCHEMA portfolio IS 'User portfolios, allocations, and financial goals';
|
||||||
|
|
||||||
-- Financial Operations
|
-- Financial Operations
|
||||||
CREATE SCHEMA IF NOT EXISTS financial;
|
CREATE SCHEMA IF NOT EXISTS financial;
|
||||||
COMMENT ON SCHEMA financial IS 'Wallets, payments, subscriptions, and financial transactions';
|
COMMENT ON SCHEMA financial IS 'Wallets, payments, subscriptions, and financial transactions';
|
||||||
|
|||||||
36
ddl/schemas/portfolio/00-enums.sql
Normal file
36
ddl/schemas/portfolio/00-enums.sql
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- PORTFOLIO SCHEMA - ENUMS
|
||||||
|
-- =====================================================
|
||||||
|
-- Description: Enumerations for portfolio management
|
||||||
|
-- Schema: portfolio
|
||||||
|
-- Author: Database Agent
|
||||||
|
-- Date: 2026-01-25
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
-- Risk profile (shared with investment schema)
|
||||||
|
CREATE TYPE portfolio.risk_profile AS ENUM (
|
||||||
|
'conservative',
|
||||||
|
'moderate',
|
||||||
|
'aggressive'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Goal status
|
||||||
|
CREATE TYPE portfolio.goal_status AS ENUM (
|
||||||
|
'active',
|
||||||
|
'completed',
|
||||||
|
'abandoned'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Rebalance action
|
||||||
|
CREATE TYPE portfolio.rebalance_action AS ENUM (
|
||||||
|
'buy',
|
||||||
|
'sell',
|
||||||
|
'hold'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Allocation status
|
||||||
|
CREATE TYPE portfolio.allocation_status AS ENUM (
|
||||||
|
'active',
|
||||||
|
'inactive',
|
||||||
|
'rebalancing'
|
||||||
|
);
|
||||||
66
ddl/schemas/portfolio/tables/01-portfolios.sql
Normal file
66
ddl/schemas/portfolio/tables/01-portfolios.sql
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- PORTFOLIO SCHEMA - PORTFOLIOS TABLE
|
||||||
|
-- =====================================================
|
||||||
|
-- Description: User portfolios with asset allocations
|
||||||
|
-- Schema: portfolio
|
||||||
|
-- Author: Database Agent
|
||||||
|
-- Date: 2026-01-25
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
CREATE TABLE portfolio.portfolios (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
risk_profile portfolio.risk_profile NOT NULL DEFAULT 'moderate',
|
||||||
|
|
||||||
|
-- Portfolio values (updated by triggers/jobs)
|
||||||
|
total_value DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
total_cost DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
unrealized_pnl DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
unrealized_pnl_percent DECIMAL(10, 4) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Statistics
|
||||||
|
day_change_percent DECIMAL(10, 4) DEFAULT 0,
|
||||||
|
week_change_percent DECIMAL(10, 4) DEFAULT 0,
|
||||||
|
month_change_percent DECIMAL(10, 4) DEFAULT 0,
|
||||||
|
all_time_change_percent DECIMAL(10, 4) DEFAULT 0,
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
is_primary BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
last_rebalanced_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX idx_portfolios_user_id ON portfolio.portfolios(user_id);
|
||||||
|
CREATE INDEX idx_portfolios_risk_profile ON portfolio.portfolios(risk_profile);
|
||||||
|
CREATE INDEX idx_portfolios_is_active ON portfolio.portfolios(is_active) WHERE is_active = true;
|
||||||
|
|
||||||
|
-- Only one primary portfolio per user
|
||||||
|
CREATE UNIQUE INDEX idx_portfolios_user_primary
|
||||||
|
ON portfolio.portfolios(user_id)
|
||||||
|
WHERE is_primary = true;
|
||||||
|
|
||||||
|
-- Comments
|
||||||
|
COMMENT ON TABLE portfolio.portfolios IS 'User investment portfolios with configurable allocations';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolios.risk_profile IS 'User risk tolerance: conservative, moderate, aggressive';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolios.total_value IS 'Current total value of all allocations in USD';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolios.unrealized_pnl IS 'Unrealized profit/loss = total_value - total_cost';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolios.is_primary IS 'Only one portfolio can be primary per user';
|
||||||
|
|
||||||
|
-- Trigger for updated_at
|
||||||
|
CREATE OR REPLACE FUNCTION portfolio.update_portfolio_updated_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_portfolios_updated_at
|
||||||
|
BEFORE UPDATE ON portfolio.portfolios
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION portfolio.update_portfolio_updated_at();
|
||||||
63
ddl/schemas/portfolio/tables/02-portfolio_allocations.sql
Normal file
63
ddl/schemas/portfolio/tables/02-portfolio_allocations.sql
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- PORTFOLIO SCHEMA - PORTFOLIO ALLOCATIONS TABLE
|
||||||
|
-- =====================================================
|
||||||
|
-- Description: Asset allocations within portfolios
|
||||||
|
-- Schema: portfolio
|
||||||
|
-- Author: Database Agent
|
||||||
|
-- Date: 2026-01-25
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
CREATE TABLE portfolio.portfolio_allocations (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
portfolio_id UUID NOT NULL REFERENCES portfolio.portfolios(id) ON DELETE CASCADE,
|
||||||
|
asset VARCHAR(20) NOT NULL,
|
||||||
|
|
||||||
|
-- Allocation targets and current state
|
||||||
|
target_percent DECIMAL(5, 2) NOT NULL DEFAULT 0,
|
||||||
|
current_percent DECIMAL(5, 2) NOT NULL DEFAULT 0,
|
||||||
|
deviation DECIMAL(5, 2) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Holdings
|
||||||
|
quantity DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
avg_cost DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
current_price DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Values
|
||||||
|
value DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
cost DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
pnl DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
pnl_percent DECIMAL(10, 4) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
status portfolio.allocation_status NOT NULL DEFAULT 'active',
|
||||||
|
last_updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
-- Constraints
|
||||||
|
CONSTRAINT chk_target_percent CHECK (target_percent >= 0 AND target_percent <= 100),
|
||||||
|
CONSTRAINT chk_current_percent CHECK (current_percent >= 0 AND current_percent <= 100),
|
||||||
|
CONSTRAINT chk_quantity_positive CHECK (quantity >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX idx_portfolio_allocations_portfolio_id ON portfolio.portfolio_allocations(portfolio_id);
|
||||||
|
CREATE INDEX idx_portfolio_allocations_asset ON portfolio.portfolio_allocations(asset);
|
||||||
|
CREATE INDEX idx_portfolio_allocations_status ON portfolio.portfolio_allocations(status);
|
||||||
|
|
||||||
|
-- Unique asset per portfolio
|
||||||
|
CREATE UNIQUE INDEX idx_portfolio_allocations_unique_asset
|
||||||
|
ON portfolio.portfolio_allocations(portfolio_id, asset);
|
||||||
|
|
||||||
|
-- Comments
|
||||||
|
COMMENT ON TABLE portfolio.portfolio_allocations IS 'Individual asset allocations within a portfolio';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_allocations.target_percent IS 'Target allocation percentage (0-100)';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_allocations.current_percent IS 'Current actual allocation percentage';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_allocations.deviation IS 'Difference between current and target (current - target)';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_allocations.avg_cost IS 'Average cost basis per unit';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_allocations.pnl IS 'Profit/loss = value - cost';
|
||||||
|
|
||||||
|
-- Trigger for last_updated_at
|
||||||
|
CREATE TRIGGER trg_portfolio_allocations_updated_at
|
||||||
|
BEFORE UPDATE ON portfolio.portfolio_allocations
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION portfolio.update_portfolio_updated_at();
|
||||||
108
ddl/schemas/portfolio/tables/03-portfolio_goals.sql
Normal file
108
ddl/schemas/portfolio/tables/03-portfolio_goals.sql
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- PORTFOLIO SCHEMA - PORTFOLIO GOALS TABLE
|
||||||
|
-- =====================================================
|
||||||
|
-- Description: User financial goals and progress tracking
|
||||||
|
-- Schema: portfolio
|
||||||
|
-- Author: Database Agent
|
||||||
|
-- Date: 2026-01-25
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
CREATE TABLE portfolio.portfolio_goals (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||||
|
portfolio_id UUID REFERENCES portfolio.portfolios(id) ON DELETE SET NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
|
||||||
|
-- Goal targets
|
||||||
|
target_amount DECIMAL(20, 2) NOT NULL,
|
||||||
|
current_amount DECIMAL(20, 2) NOT NULL DEFAULT 0,
|
||||||
|
target_date DATE NOT NULL,
|
||||||
|
monthly_contribution DECIMAL(20, 2) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Progress tracking
|
||||||
|
progress DECIMAL(5, 2) NOT NULL DEFAULT 0,
|
||||||
|
projected_completion_date DATE,
|
||||||
|
months_remaining INTEGER,
|
||||||
|
required_monthly_contribution DECIMAL(20, 2),
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
status portfolio.goal_status NOT NULL DEFAULT 'active',
|
||||||
|
completed_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
-- Constraints
|
||||||
|
CONSTRAINT chk_target_amount_positive CHECK (target_amount > 0),
|
||||||
|
CONSTRAINT chk_current_amount_positive CHECK (current_amount >= 0),
|
||||||
|
CONSTRAINT chk_progress_range CHECK (progress >= 0 AND progress <= 100),
|
||||||
|
CONSTRAINT chk_monthly_contribution_positive CHECK (monthly_contribution >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX idx_portfolio_goals_user_id ON portfolio.portfolio_goals(user_id);
|
||||||
|
CREATE INDEX idx_portfolio_goals_portfolio_id ON portfolio.portfolio_goals(portfolio_id);
|
||||||
|
CREATE INDEX idx_portfolio_goals_status ON portfolio.portfolio_goals(status);
|
||||||
|
CREATE INDEX idx_portfolio_goals_target_date ON portfolio.portfolio_goals(target_date);
|
||||||
|
|
||||||
|
-- Comments
|
||||||
|
COMMENT ON TABLE portfolio.portfolio_goals IS 'User financial goals with progress tracking';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_goals.target_amount IS 'Target amount to save/invest in USD';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_goals.current_amount IS 'Current progress toward the goal';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_goals.progress IS 'Percentage progress (0-100)';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_goals.projected_completion_date IS 'Estimated date of goal completion based on contributions';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_goals.required_monthly_contribution IS 'Monthly contribution needed to meet target on time';
|
||||||
|
|
||||||
|
-- Trigger for updated_at
|
||||||
|
CREATE TRIGGER trg_portfolio_goals_updated_at
|
||||||
|
BEFORE UPDATE ON portfolio.portfolio_goals
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION portfolio.update_portfolio_updated_at();
|
||||||
|
|
||||||
|
-- Function to update goal progress
|
||||||
|
CREATE OR REPLACE FUNCTION portfolio.update_goal_progress()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
-- Calculate progress percentage
|
||||||
|
IF NEW.target_amount > 0 THEN
|
||||||
|
NEW.progress := LEAST(100, (NEW.current_amount / NEW.target_amount) * 100);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Calculate months remaining
|
||||||
|
NEW.months_remaining := GREATEST(0,
|
||||||
|
EXTRACT(YEAR FROM AGE(NEW.target_date, CURRENT_DATE)) * 12 +
|
||||||
|
EXTRACT(MONTH FROM AGE(NEW.target_date, CURRENT_DATE))
|
||||||
|
)::INTEGER;
|
||||||
|
|
||||||
|
-- Calculate required monthly contribution
|
||||||
|
IF NEW.months_remaining > 0 THEN
|
||||||
|
NEW.required_monthly_contribution := GREATEST(0,
|
||||||
|
(NEW.target_amount - NEW.current_amount) / NEW.months_remaining
|
||||||
|
);
|
||||||
|
ELSE
|
||||||
|
NEW.required_monthly_contribution := NEW.target_amount - NEW.current_amount;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Calculate projected completion date based on current contributions
|
||||||
|
IF NEW.monthly_contribution > 0 AND NEW.current_amount < NEW.target_amount THEN
|
||||||
|
NEW.projected_completion_date := CURRENT_DATE +
|
||||||
|
(CEIL((NEW.target_amount - NEW.current_amount) / NEW.monthly_contribution) * INTERVAL '1 month')::INTERVAL;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Auto-complete if goal reached
|
||||||
|
IF NEW.current_amount >= NEW.target_amount AND NEW.status = 'active' THEN
|
||||||
|
NEW.status := 'completed';
|
||||||
|
NEW.completed_at := NOW();
|
||||||
|
NEW.progress := 100;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_portfolio_goals_progress
|
||||||
|
BEFORE INSERT OR UPDATE ON portfolio.portfolio_goals
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION portfolio.update_goal_progress();
|
||||||
42
ddl/schemas/portfolio/tables/04-rebalance_history.sql
Normal file
42
ddl/schemas/portfolio/tables/04-rebalance_history.sql
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- PORTFOLIO SCHEMA - REBALANCE HISTORY TABLE
|
||||||
|
-- =====================================================
|
||||||
|
-- Description: History of portfolio rebalancing operations
|
||||||
|
-- Schema: portfolio
|
||||||
|
-- Author: Database Agent
|
||||||
|
-- Date: 2026-01-25
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
CREATE TABLE portfolio.rebalance_history (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
portfolio_id UUID NOT NULL REFERENCES portfolio.portfolios(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Rebalance details
|
||||||
|
asset VARCHAR(20) NOT NULL,
|
||||||
|
action portfolio.rebalance_action NOT NULL,
|
||||||
|
target_percent DECIMAL(5, 2) NOT NULL,
|
||||||
|
actual_percent_before DECIMAL(5, 2) NOT NULL,
|
||||||
|
actual_percent_after DECIMAL(5, 2) NOT NULL,
|
||||||
|
|
||||||
|
-- Amounts
|
||||||
|
quantity_change DECIMAL(20, 8) NOT NULL,
|
||||||
|
value_change DECIMAL(20, 8) NOT NULL,
|
||||||
|
price_at_rebalance DECIMAL(20, 8) NOT NULL,
|
||||||
|
|
||||||
|
-- Execution
|
||||||
|
executed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
execution_status VARCHAR(20) NOT NULL DEFAULT 'completed',
|
||||||
|
execution_notes TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX idx_rebalance_history_portfolio_id ON portfolio.rebalance_history(portfolio_id);
|
||||||
|
CREATE INDEX idx_rebalance_history_executed_at ON portfolio.rebalance_history(executed_at);
|
||||||
|
CREATE INDEX idx_rebalance_history_asset ON portfolio.rebalance_history(asset);
|
||||||
|
|
||||||
|
-- Comments
|
||||||
|
COMMENT ON TABLE portfolio.rebalance_history IS 'Historical record of portfolio rebalancing operations';
|
||||||
|
COMMENT ON COLUMN portfolio.rebalance_history.action IS 'Type of rebalance action: buy, sell, hold';
|
||||||
|
COMMENT ON COLUMN portfolio.rebalance_history.quantity_change IS 'Amount of asset bought (+) or sold (-)';
|
||||||
|
COMMENT ON COLUMN portfolio.rebalance_history.value_change IS 'USD value of the transaction';
|
||||||
41
ddl/schemas/portfolio/tables/05-portfolio_snapshots.sql
Normal file
41
ddl/schemas/portfolio/tables/05-portfolio_snapshots.sql
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- PORTFOLIO SCHEMA - PORTFOLIO SNAPSHOTS TABLE
|
||||||
|
-- =====================================================
|
||||||
|
-- Description: Daily snapshots of portfolio values for performance tracking
|
||||||
|
-- Schema: portfolio
|
||||||
|
-- Author: Database Agent
|
||||||
|
-- Date: 2026-01-25
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
CREATE TABLE portfolio.portfolio_snapshots (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
portfolio_id UUID NOT NULL REFERENCES portfolio.portfolios(id) ON DELETE CASCADE,
|
||||||
|
snapshot_date DATE NOT NULL,
|
||||||
|
|
||||||
|
-- Values at snapshot time
|
||||||
|
total_value DECIMAL(20, 8) NOT NULL,
|
||||||
|
total_cost DECIMAL(20, 8) NOT NULL,
|
||||||
|
unrealized_pnl DECIMAL(20, 8) NOT NULL,
|
||||||
|
unrealized_pnl_percent DECIMAL(10, 4) NOT NULL,
|
||||||
|
|
||||||
|
-- Daily changes
|
||||||
|
day_change DECIMAL(20, 8) NOT NULL DEFAULT 0,
|
||||||
|
day_change_percent DECIMAL(10, 4) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Allocation snapshot (JSON for flexibility)
|
||||||
|
allocations JSONB,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX idx_portfolio_snapshots_portfolio_id ON portfolio.portfolio_snapshots(portfolio_id);
|
||||||
|
CREATE INDEX idx_portfolio_snapshots_date ON portfolio.portfolio_snapshots(snapshot_date);
|
||||||
|
|
||||||
|
-- One snapshot per portfolio per day
|
||||||
|
CREATE UNIQUE INDEX idx_portfolio_snapshots_unique_day
|
||||||
|
ON portfolio.portfolio_snapshots(portfolio_id, snapshot_date);
|
||||||
|
|
||||||
|
-- Comments
|
||||||
|
COMMENT ON TABLE portfolio.portfolio_snapshots IS 'Daily snapshots for historical performance tracking';
|
||||||
|
COMMENT ON COLUMN portfolio.portfolio_snapshots.allocations IS 'JSON snapshot of allocation state at that time';
|
||||||
Loading…
Reference in New Issue
Block a user