-- ============================================================================ -- SCHEMA: trading -- TABLE: symbols -- DESCRIPTION: Simbolos/instrumentos habilitados para trading por tenant -- VERSION: 1.0.0 -- CREATED: 2026-01-16 -- SPRINT: Sprint 3 - DDL Implementation Roadmap Q1-2026 -- ============================================================================ -- Crear schema si no existe CREATE SCHEMA IF NOT EXISTS trading; -- Grant usage GRANT USAGE ON SCHEMA trading TO trading_app; GRANT USAGE ON SCHEMA trading TO trading_readonly; -- ============================================================================ -- ENUMS COMPARTIDOS DEL SCHEMA TRADING -- ============================================================================ -- Enum para direccion de trade DO $$ BEGIN CREATE TYPE trading.trade_direction AS ENUM ( 'long', -- Compra / Buy 'short' -- Venta / Sell ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para estado de posicion DO $$ BEGIN CREATE TYPE trading.position_status AS ENUM ( 'pending', -- Pendiente de ejecucion 'open', -- Posicion abierta 'closed', -- Cerrada manualmente 'stopped', -- Cerrada por stop loss 'target_hit', -- Cerrada por take profit 'expired', -- Expirada 'cancelled' -- Cancelada antes de ejecutar ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para tipo de orden DO $$ BEGIN CREATE TYPE trading.order_type AS ENUM ( 'market', -- Orden de mercado 'limit', -- Orden limitada 'stop', -- Stop order 'stop_limit' -- Stop limit order ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para timeframe DO $$ BEGIN CREATE TYPE trading.timeframe AS ENUM ( 'M1', 'M5', 'M15', 'M30', -- Minutos 'H1', 'H4', -- Horas 'D1', 'W1', 'MN' -- Dia, Semana, Mes ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- ============================================================================ -- TABLA: symbols -- Simbolos habilitados por tenant (copia de market_data.tickers con config tenant) -- ============================================================================ CREATE TABLE IF NOT EXISTS trading.symbols ( -- Identificadores id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, ticker_id UUID NOT NULL REFERENCES market_data.tickers(id), -- Datos del simbolo (denormalizados para performance) symbol VARCHAR(20) NOT NULL, name VARCHAR(200), type market_data.instrument_type, -- Configuracion por tenant is_enabled BOOLEAN NOT NULL DEFAULT TRUE, is_tradeable BOOLEAN NOT NULL DEFAULT TRUE, is_visible BOOLEAN NOT NULL DEFAULT TRUE, -- Limites especificos del tenant min_lot_size DECIMAL(10, 4), max_lot_size DECIMAL(10, 4), max_position_size DECIMAL(15, 2), -- TamaƱo maximo de posicion en USD max_daily_volume DECIMAL(15, 2), -- Volumen diario maximo -- Spread markup (si el tenant agrega spread) spread_markup_pips DECIMAL(10, 4) DEFAULT 0, -- Comisiones del tenant commission_per_lot DECIMAL(10, 4) DEFAULT 0, commission_percent DECIMAL(5, 4) DEFAULT 0, -- Margen requerido (override del default) margin_required_percent DECIMAL(5, 2), max_leverage INTEGER, -- Trading hours override custom_trading_hours JSONB, -- Categorias/tags del tenant categories VARCHAR(50)[], tags VARCHAR(50)[], display_order INTEGER DEFAULT 999, -- Estadisticas del tenant trade_count INTEGER NOT NULL DEFAULT 0, total_volume DECIMAL(20, 4) NOT NULL DEFAULT 0, last_trade_at TIMESTAMPTZ, -- Metadata metadata JSONB DEFAULT '{}'::JSONB, -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Constraints CONSTRAINT symbols_unique_per_tenant UNIQUE (tenant_id, ticker_id), CONSTRAINT symbols_unique_symbol_per_tenant UNIQUE (tenant_id, symbol) ); COMMENT ON TABLE trading.symbols IS 'Simbolos habilitados para trading por tenant con configuracion personalizada'; COMMENT ON COLUMN trading.symbols.spread_markup_pips IS 'Pips adicionales de spread que el tenant agrega al precio del broker'; -- Indices CREATE INDEX IF NOT EXISTS idx_symbols_tenant ON trading.symbols(tenant_id); CREATE INDEX IF NOT EXISTS idx_symbols_ticker ON trading.symbols(ticker_id); CREATE INDEX IF NOT EXISTS idx_symbols_symbol ON trading.symbols(tenant_id, symbol); CREATE INDEX IF NOT EXISTS idx_symbols_enabled ON trading.symbols(tenant_id, is_enabled, is_tradeable) WHERE is_enabled = TRUE AND is_tradeable = TRUE; CREATE INDEX IF NOT EXISTS idx_symbols_type ON trading.symbols(tenant_id, type); -- GIN index para categorias CREATE INDEX IF NOT EXISTS idx_symbols_categories_gin ON trading.symbols USING GIN (categories); -- Trigger para updated_at CREATE OR REPLACE FUNCTION trading.update_trading_timestamp() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS symbol_updated_at ON trading.symbols; CREATE TRIGGER symbol_updated_at BEFORE UPDATE ON trading.symbols FOR EACH ROW EXECUTE FUNCTION trading.update_trading_timestamp(); -- Trigger para sincronizar datos del ticker CREATE OR REPLACE FUNCTION trading.sync_symbol_from_ticker() RETURNS TRIGGER AS $$ BEGIN SELECT symbol, name, type INTO NEW.symbol, NEW.name, NEW.type FROM market_data.tickers WHERE id = NEW.ticker_id; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS symbol_sync ON trading.symbols; CREATE TRIGGER symbol_sync BEFORE INSERT OR UPDATE OF ticker_id ON trading.symbols FOR EACH ROW EXECUTE FUNCTION trading.sync_symbol_from_ticker(); -- Vista de simbolos activos CREATE OR REPLACE VIEW trading.v_active_symbols AS SELECT s.id, s.tenant_id, s.ticker_id, s.symbol, s.name, s.type, t.pip_size, t.typical_spread_pips, s.spread_markup_pips, (COALESCE(t.typical_spread_pips, 0) + COALESCE(s.spread_markup_pips, 0)) AS total_spread_pips, t.current_bid, t.current_ask, s.min_lot_size, s.max_lot_size, COALESCE(s.max_leverage, t.max_leverage) AS max_leverage, s.display_order FROM trading.symbols s JOIN market_data.tickers t ON s.ticker_id = t.id WHERE s.is_enabled = TRUE AND s.is_tradeable = TRUE AND t.status = 'active' ORDER BY s.display_order, s.symbol; -- RLS Policy para multi-tenancy ALTER TABLE trading.symbols ENABLE ROW LEVEL SECURITY; CREATE POLICY symbols_tenant_isolation ON trading.symbols FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Grants GRANT SELECT, INSERT, UPDATE, DELETE ON trading.symbols TO trading_app; GRANT SELECT ON trading.symbols TO trading_readonly; GRANT SELECT ON trading.v_active_symbols TO trading_app;