trading-platform-database-v2/schemas/04_investment_schema.sql
rckrdmrd 45e77e9a9c feat: Initial commit - Database schemas and scripts
DDL schemas for Trading Platform:
- User management
- Authentication
- Payments
- Education
- ML predictions
- Trading data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 04:30:23 -06:00

427 lines
15 KiB
PL/PgSQL

-- ============================================================================
-- OrbiQuant IA - Esquema INVESTMENT
-- ============================================================================
-- Archivo: 04_investment_schema.sql
-- Descripción: Cuentas de inversión, productos y gestión de portafolios
-- Fecha: 2025-12-05
-- ============================================================================
SET search_path TO investment;
-- ============================================================================
-- TIPOS ENUMERADOS
-- ============================================================================
CREATE TYPE product_type_enum AS ENUM (
'fixed_return', -- Rendimiento fijo objetivo (ej: 5% mensual)
'variable_return', -- Rendimiento variable con reparto de utilidades
'long_term_portfolio' -- Cartera de largo plazo (acciones, ETFs)
);
CREATE TYPE account_status_enum AS ENUM (
'pending_kyc', -- Esperando verificación KYC
'pending_deposit', -- Esperando depósito inicial
'active', -- Cuenta activa operando
'paused', -- Pausada por usuario o admin
'suspended', -- Suspendida por compliance
'closed' -- Cerrada
);
CREATE TYPE fee_type_enum AS ENUM (
'management', -- Comisión de administración
'performance', -- Comisión de rendimiento
'deposit', -- Comisión de depósito
'withdrawal', -- Comisión de retiro
'subscription' -- Comisión de suscripción
);
-- ============================================================================
-- TABLA: products
-- Descripción: Productos de inversión disponibles
-- ============================================================================
CREATE TABLE IF NOT EXISTS products (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Identificación
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
short_description VARCHAR(500),
-- Tipo y riesgo
product_type product_type_enum NOT NULL,
risk_profile public.risk_profile_enum NOT NULL,
-- Objetivos
target_monthly_return DECIMAL(5,2), -- % objetivo mensual
max_drawdown DECIMAL(5,2), -- % máximo drawdown permitido
guaranteed_return BOOLEAN DEFAULT FALSE, -- SIEMPRE FALSE para cumplimiento
-- Comisiones
management_fee_percent DECIMAL(5,2) DEFAULT 0, -- % anual sobre AUM
performance_fee_percent DECIMAL(5,2) DEFAULT 0, -- % sobre ganancias
profit_share_platform DECIMAL(5,2), -- % de utilidades para plataforma (ej: 50)
profit_share_client DECIMAL(5,2), -- % de utilidades para cliente (ej: 50)
-- Límites
min_investment DECIMAL(15,2) DEFAULT 100,
max_investment DECIMAL(15,2),
min_investment_period_days INT DEFAULT 30,
-- Restricciones
requires_kyc_level INT DEFAULT 1,
allowed_risk_profiles public.risk_profile_enum[],
-- Bot/Agente asociado
default_bot_id UUID REFERENCES trading.bots(id),
-- Estado
is_active BOOLEAN DEFAULT TRUE,
is_visible BOOLEAN DEFAULT TRUE,
-- Metadata
terms_url TEXT,
risk_disclosure_url TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_products_slug ON products(slug);
CREATE INDEX idx_products_type ON products(product_type);
CREATE INDEX idx_products_risk ON products(risk_profile);
-- ============================================================================
-- TABLA: accounts
-- Descripción: Cuentas de inversión de usuarios
-- ============================================================================
CREATE TABLE IF NOT EXISTS accounts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Referencias
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE RESTRICT,
product_id UUID NOT NULL REFERENCES products(id),
bot_id UUID REFERENCES trading.bots(id),
-- Identificación
account_number VARCHAR(20) NOT NULL UNIQUE, -- OQ-INV-XXXXXX
name VARCHAR(100),
-- Moneda
currency CHAR(3) DEFAULT 'USD',
-- Balances
initial_deposit DECIMAL(15,2) NOT NULL,
current_balance DECIMAL(15,2) NOT NULL,
available_balance DECIMAL(15,2) NOT NULL, -- Balance disponible para retiro
reserved_balance DECIMAL(15,2) DEFAULT 0, -- En operaciones abiertas
-- Rendimiento acumulado
total_profit DECIMAL(15,2) DEFAULT 0,
total_fees_paid DECIMAL(15,2) DEFAULT 0,
total_deposits DECIMAL(15,2) DEFAULT 0,
total_withdrawals DECIMAL(15,2) DEFAULT 0,
-- Métricas de rendimiento
total_return_percent DECIMAL(8,4) DEFAULT 0,
monthly_return_percent DECIMAL(8,4) DEFAULT 0,
max_drawdown_percent DECIMAL(5,2) DEFAULT 0,
sharpe_ratio DECIMAL(5,2),
-- Estado
status account_status_enum DEFAULT 'pending_deposit',
activated_at TIMESTAMPTZ,
paused_at TIMESTAMPTZ,
closed_at TIMESTAMPTZ,
-- Configuración del usuario
auto_compound BOOLEAN DEFAULT TRUE, -- Reinvertir ganancias
max_drawdown_override DECIMAL(5,2), -- Override del drawdown máximo
pause_on_drawdown BOOLEAN DEFAULT TRUE, -- Pausar si alcanza DD máximo
-- Aceptación de términos
terms_accepted_at TIMESTAMPTZ,
risk_disclosure_accepted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_accounts_user ON accounts(user_id);
CREATE INDEX idx_accounts_product ON accounts(product_id);
CREATE INDEX idx_accounts_status ON accounts(status);
CREATE INDEX idx_accounts_number ON accounts(account_number);
-- ============================================================================
-- TABLA: account_transactions
-- Descripción: Movimientos en cuentas de inversión
-- ============================================================================
CREATE TYPE account_transaction_type AS ENUM (
'deposit',
'withdrawal',
'profit',
'loss',
'fee',
'adjustment',
'transfer_in',
'transfer_out'
);
CREATE TABLE IF NOT EXISTS account_transactions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
-- Tipo y monto
transaction_type account_transaction_type NOT NULL,
amount DECIMAL(15,2) NOT NULL, -- Positivo o negativo según tipo
currency CHAR(3) DEFAULT 'USD',
-- Balances
balance_before DECIMAL(15,2) NOT NULL,
balance_after DECIMAL(15,2) NOT NULL,
-- Referencia externa
reference_type VARCHAR(50), -- 'wallet_transaction', 'position', 'fee_charge'
reference_id UUID,
-- Descripción
description TEXT,
-- Estado
status VARCHAR(20) DEFAULT 'completed', -- 'pending', 'completed', 'cancelled'
processed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_account_tx_account ON account_transactions(account_id);
CREATE INDEX idx_account_tx_type ON account_transactions(transaction_type);
CREATE INDEX idx_account_tx_created ON account_transactions(created_at DESC);
-- ============================================================================
-- TABLA: performance_snapshots
-- Descripción: Snapshots periódicos de rendimiento
-- ============================================================================
CREATE TABLE IF NOT EXISTS performance_snapshots (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
-- Período
snapshot_date DATE NOT NULL,
period_type VARCHAR(20) NOT NULL, -- 'daily', 'weekly', 'monthly'
-- Valores
opening_balance DECIMAL(15,2) NOT NULL,
closing_balance DECIMAL(15,2) NOT NULL,
deposits DECIMAL(15,2) DEFAULT 0,
withdrawals DECIMAL(15,2) DEFAULT 0,
profit_loss DECIMAL(15,2) NOT NULL,
fees DECIMAL(15,2) DEFAULT 0,
-- Métricas
return_percent DECIMAL(8,4),
drawdown_percent DECIMAL(5,2),
-- Trading stats
total_trades INT DEFAULT 0,
winning_trades INT DEFAULT 0,
positions_opened INT DEFAULT 0,
positions_closed INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(account_id, snapshot_date, period_type)
);
CREATE INDEX idx_perf_snapshots_account ON performance_snapshots(account_id);
CREATE INDEX idx_perf_snapshots_date ON performance_snapshots(snapshot_date DESC);
-- ============================================================================
-- TABLA: profit_distributions
-- Descripción: Distribución de utilidades (para cuentas con profit sharing)
-- ============================================================================
CREATE TABLE IF NOT EXISTS profit_distributions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
-- Período
period_start DATE NOT NULL,
period_end DATE NOT NULL,
-- Cálculo
gross_profit DECIMAL(15,2) NOT NULL,
management_fee DECIMAL(15,2) DEFAULT 0,
net_profit DECIMAL(15,2) NOT NULL,
-- Distribución
platform_share_percent DECIMAL(5,2) NOT NULL,
client_share_percent DECIMAL(5,2) NOT NULL,
platform_amount DECIMAL(15,2) NOT NULL,
client_amount DECIMAL(15,2) NOT NULL,
-- Estado
status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'approved', 'distributed', 'cancelled'
distributed_at TIMESTAMPTZ,
approved_by UUID REFERENCES auth.users(id),
-- Referencia de pago
payment_reference VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_profit_dist_account ON profit_distributions(account_id);
CREATE INDEX idx_profit_dist_period ON profit_distributions(period_end DESC);
CREATE INDEX idx_profit_dist_status ON profit_distributions(status);
-- ============================================================================
-- TABLA: deposit_requests
-- Descripción: Solicitudes de depósito a cuentas de inversión
-- ============================================================================
CREATE TABLE IF NOT EXISTS deposit_requests (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
user_id UUID NOT NULL REFERENCES auth.users(id),
amount DECIMAL(15,2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
-- Origen
source_type VARCHAR(50) NOT NULL, -- 'wallet', 'external'
source_wallet_id UUID, -- Referencia a financial.wallets
-- Estado
status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'approved', 'completed', 'rejected'
processed_at TIMESTAMPTZ,
processed_by UUID REFERENCES auth.users(id),
rejection_reason TEXT,
-- Transacción resultante
transaction_id UUID REFERENCES account_transactions(id),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_deposit_requests_account ON deposit_requests(account_id);
CREATE INDEX idx_deposit_requests_user ON deposit_requests(user_id);
CREATE INDEX idx_deposit_requests_status ON deposit_requests(status);
-- ============================================================================
-- TABLA: withdrawal_requests
-- Descripción: Solicitudes de retiro de cuentas de inversión
-- ============================================================================
CREATE TABLE IF NOT EXISTS withdrawal_requests (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
user_id UUID NOT NULL REFERENCES auth.users(id),
amount DECIMAL(15,2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
-- Destino
destination_type VARCHAR(50) NOT NULL, -- 'wallet', 'bank_transfer'
destination_wallet_id UUID,
-- Estado
status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'processing', 'completed', 'rejected'
processed_at TIMESTAMPTZ,
processed_by UUID REFERENCES auth.users(id),
rejection_reason TEXT,
-- Comisiones
fee_amount DECIMAL(10,2) DEFAULT 0,
net_amount DECIMAL(15,2),
-- Transacción resultante
transaction_id UUID REFERENCES account_transactions(id),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_withdrawal_requests_account ON withdrawal_requests(account_id);
CREATE INDEX idx_withdrawal_requests_user ON withdrawal_requests(user_id);
CREATE INDEX idx_withdrawal_requests_status ON withdrawal_requests(status);
-- ============================================================================
-- TABLA: bot_assignments
-- Descripción: Asignación de bots a cuentas de inversión
-- ============================================================================
CREATE TABLE IF NOT EXISTS bot_assignments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
bot_id UUID NOT NULL REFERENCES trading.bots(id),
-- Estado
is_active BOOLEAN DEFAULT TRUE,
assigned_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
deactivated_at TIMESTAMPTZ,
-- Razón del cambio
assignment_reason TEXT,
deactivation_reason TEXT,
-- Usuario que asignó
assigned_by UUID REFERENCES auth.users(id), -- NULL = automático
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_bot_assignments_account ON bot_assignments(account_id);
CREATE INDEX idx_bot_assignments_bot ON bot_assignments(bot_id);
CREATE INDEX idx_bot_assignments_active ON bot_assignments(is_active) WHERE is_active = TRUE;
-- ============================================================================
-- TRIGGERS
-- ============================================================================
CREATE TRIGGER update_products_updated_at
BEFORE UPDATE ON products
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_accounts_updated_at
BEFORE UPDATE ON accounts
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_deposit_requests_updated_at
BEFORE UPDATE ON deposit_requests
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_withdrawal_requests_updated_at
BEFORE UPDATE ON withdrawal_requests
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_profit_distributions_updated_at
BEFORE UPDATE ON profit_distributions
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
-- ============================================================================
-- FUNCIÓN: Generar número de cuenta
-- ============================================================================
CREATE OR REPLACE FUNCTION generate_account_number()
RETURNS TRIGGER AS $$
DECLARE
seq_num INT;
BEGIN
SELECT COALESCE(MAX(CAST(SUBSTRING(account_number FROM 8) AS INT)), 0) + 1
INTO seq_num
FROM investment.accounts;
NEW.account_number := 'OQ-INV-' || LPAD(seq_num::TEXT, 6, '0');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_account_number
BEFORE INSERT ON accounts
FOR EACH ROW
WHEN (NEW.account_number IS NULL)
EXECUTE FUNCTION generate_account_number();