-- ============================================================================ -- SCHEMA: data_sources -- TABLE: ticker_mapping -- DESCRIPTION: Mapeo de simbolos entre proveedores y sistema interno -- VERSION: 1.0.0 -- CREATED: 2026-01-16 -- SPRINT: Sprint 4 - DDL Implementation Roadmap Q1-2026 -- ============================================================================ -- Tabla de Mapeo de Tickers CREATE TABLE IF NOT EXISTS data_sources.ticker_mapping ( -- Identificadores id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, -- Referencias provider_id UUID NOT NULL REFERENCES data_sources.api_providers(id) ON DELETE CASCADE, ticker_id UUID REFERENCES market_data.tickers(id), symbol_id UUID REFERENCES trading.symbols(id), -- Simbolos internal_symbol VARCHAR(20) NOT NULL, -- Simbolo interno: EURUSD provider_symbol VARCHAR(50) NOT NULL, -- Simbolo del provider: EUR/USD, EURUSD=X provider_code VARCHAR(50) NOT NULL, -- Codigo del proveedor -- Tipo de activo asset_class VARCHAR(30) NOT NULL, -- 'forex', 'crypto', 'stock', 'index', 'commodity' instrument_type VARCHAR(30), -- 'spot', 'future', 'option', 'cfd' -- Configuracion de precision price_precision INTEGER NOT NULL DEFAULT 5, -- Decimales de precio lot_precision INTEGER NOT NULL DEFAULT 2, -- Decimales de lote min_lot_size DECIMAL(10, 4) DEFAULT 0.01, max_lot_size DECIMAL(10, 4) DEFAULT 100, lot_step DECIMAL(10, 4) DEFAULT 0.01, -- Factor de ajuste price_multiplier DECIMAL(10, 6) DEFAULT 1.0, -- Para convertir precio del provider inverse_quote BOOLEAN NOT NULL DEFAULT FALSE, -- Si el provider da inverso -- Monedas base_currency VARCHAR(10), -- EUR quote_currency VARCHAR(10), -- USD -- Estado is_active BOOLEAN NOT NULL DEFAULT TRUE, is_verified BOOLEAN NOT NULL DEFAULT FALSE, -- Verificado manualmente -- Horarios de trading trading_hours JSONB, -- [{"day": 0, "open": "00:00", "close": "23:59"}] timezone VARCHAR(50) DEFAULT 'UTC', -- Estadisticas de uso last_data_at TIMESTAMPTZ, data_points_count BIGINT NOT NULL DEFAULT 0, error_count INTEGER NOT NULL DEFAULT 0, last_error TEXT, -- Metadata metadata JSONB DEFAULT '{}'::JSONB, notes TEXT, -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), verified_at TIMESTAMPTZ, verified_by UUID REFERENCES users.users(id), -- Constraints CONSTRAINT ticker_mapping_unique UNIQUE (tenant_id, provider_id, internal_symbol), CONSTRAINT ticker_mapping_provider_symbol_unique UNIQUE (provider_id, provider_symbol) ); COMMENT ON TABLE data_sources.ticker_mapping IS 'Mapeo de simbolos entre proveedores externos y nomenclatura interna'; COMMENT ON COLUMN data_sources.ticker_mapping.price_multiplier IS 'Factor para convertir precio del proveedor a precio interno. Ej: 1.0 para EURUSD, 0.01 para JPY pairs'; COMMENT ON COLUMN data_sources.ticker_mapping.inverse_quote IS 'TRUE si el proveedor reporta USD/EUR en lugar de EUR/USD'; -- Indices CREATE INDEX IF NOT EXISTS idx_ticker_mapping_tenant ON data_sources.ticker_mapping(tenant_id); CREATE INDEX IF NOT EXISTS idx_ticker_mapping_provider ON data_sources.ticker_mapping(provider_id); CREATE INDEX IF NOT EXISTS idx_ticker_mapping_internal ON data_sources.ticker_mapping(internal_symbol); CREATE INDEX IF NOT EXISTS idx_ticker_mapping_provider_symbol ON data_sources.ticker_mapping(provider_symbol); CREATE INDEX IF NOT EXISTS idx_ticker_mapping_active ON data_sources.ticker_mapping(provider_id, is_active) WHERE is_active = TRUE; CREATE INDEX IF NOT EXISTS idx_ticker_mapping_asset ON data_sources.ticker_mapping(asset_class); CREATE INDEX IF NOT EXISTS idx_ticker_mapping_ticker ON data_sources.ticker_mapping(ticker_id) WHERE ticker_id IS NOT NULL; -- Trigger para updated_at DROP TRIGGER IF EXISTS ticker_mapping_updated_at ON data_sources.ticker_mapping; CREATE TRIGGER ticker_mapping_updated_at BEFORE UPDATE ON data_sources.ticker_mapping FOR EACH ROW EXECUTE FUNCTION data_sources.update_timestamp(); -- Funcion para obtener simbolo del proveedor CREATE OR REPLACE FUNCTION data_sources.get_provider_symbol( p_tenant_id UUID, p_provider_code VARCHAR, p_internal_symbol VARCHAR ) RETURNS data_sources.ticker_mapping AS $$ DECLARE v_mapping data_sources.ticker_mapping; BEGIN SELECT tm.* INTO v_mapping FROM data_sources.ticker_mapping tm JOIN data_sources.api_providers ap ON tm.provider_id = ap.id WHERE tm.tenant_id = p_tenant_id AND ap.code = p_provider_code AND tm.internal_symbol = p_internal_symbol AND tm.is_active = TRUE; RETURN v_mapping; END; $$ LANGUAGE plpgsql; -- Funcion para convertir precio del proveedor a interno CREATE OR REPLACE FUNCTION data_sources.convert_provider_price( p_mapping_id UUID, p_provider_price DECIMAL ) RETURNS DECIMAL AS $$ DECLARE v_mapping data_sources.ticker_mapping; v_converted DECIMAL; BEGIN SELECT * INTO v_mapping FROM data_sources.ticker_mapping WHERE id = p_mapping_id; IF v_mapping IS NULL THEN RETURN p_provider_price; END IF; v_converted := p_provider_price * v_mapping.price_multiplier; IF v_mapping.inverse_quote THEN v_converted := 1.0 / v_converted; END IF; RETURN ROUND(v_converted, v_mapping.price_precision); END; $$ LANGUAGE plpgsql; -- Vista de mappings activos por proveedor CREATE OR REPLACE VIEW data_sources.v_ticker_mappings AS SELECT tm.id, tm.tenant_id, ap.name AS provider_name, ap.code AS provider_code, tm.internal_symbol, tm.provider_symbol, tm.asset_class, tm.price_precision, tm.price_multiplier, tm.inverse_quote, tm.is_active, tm.is_verified, tm.last_data_at, tm.data_points_count FROM data_sources.ticker_mapping tm JOIN data_sources.api_providers ap ON tm.provider_id = ap.id WHERE tm.is_active = TRUE ORDER BY tm.internal_symbol, ap.priority; -- RLS Policies ALTER TABLE data_sources.ticker_mapping ENABLE ROW LEVEL SECURITY; CREATE POLICY ticker_mapping_tenant_isolation ON data_sources.ticker_mapping FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Grants GRANT SELECT, INSERT, UPDATE, DELETE ON data_sources.ticker_mapping TO trading_app; GRANT SELECT ON data_sources.ticker_mapping TO trading_readonly; GRANT SELECT ON data_sources.v_ticker_mappings TO trading_app; GRANT EXECUTE ON FUNCTION data_sources.get_provider_symbol TO trading_app; GRANT EXECUTE ON FUNCTION data_sources.convert_provider_price TO trading_app;