-- ============================================================================ -- SCHEMA: data_sources -- TABLE: api_providers -- DESCRIPTION: Proveedores de datos de mercado (Polygon, Alpha Vantage, etc.) -- VERSION: 1.0.0 -- CREATED: 2026-01-16 -- SPRINT: Sprint 4 - DDL Implementation Roadmap Q1-2026 -- ============================================================================ -- Crear schema si no existe CREATE SCHEMA IF NOT EXISTS data_sources; COMMENT ON SCHEMA data_sources IS 'Fuentes de datos de mercado y proveedores de API'; -- Enum para tipo de proveedor DO $$ BEGIN CREATE TYPE data_sources.provider_type AS ENUM ( 'market_data', -- Datos de mercado (OHLCV, quotes) 'news', -- Noticias financieras 'fundamental', -- Datos fundamentales 'sentiment', -- Analisis de sentimiento 'economic_calendar', -- Calendario economico 'alternative' -- Datos alternativos ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para estado del proveedor DO $$ BEGIN CREATE TYPE data_sources.provider_status AS ENUM ( 'active', -- Activo y funcionando 'inactive', -- Inactivo temporalmente 'degraded', -- Funcionando con problemas 'rate_limited', -- Limitado por rate limit 'maintenance', -- En mantenimiento 'error', -- Con errores 'deprecated' -- Deprecado ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para frecuencia de datos DO $$ BEGIN CREATE TYPE data_sources.data_frequency AS ENUM ( 'tick', -- Tick por tick 'second', -- Por segundo 'minute', -- Por minuto 'five_minutes', -- Cada 5 minutos 'fifteen_minutes', -- Cada 15 minutos 'hourly', -- Cada hora 'daily', -- Diario 'weekly', -- Semanal 'monthly' -- Mensual ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Tabla de Proveedores de API CREATE TABLE IF NOT EXISTS data_sources.api_providers ( -- Identificadores id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, -- Informacion del proveedor name VARCHAR(100) NOT NULL, code VARCHAR(50) NOT NULL, -- 'polygon', 'alpha_vantage', 'binance' type data_sources.provider_type NOT NULL, status data_sources.provider_status NOT NULL DEFAULT 'inactive', -- Configuracion de API base_url TEXT NOT NULL, api_version VARCHAR(20), auth_type VARCHAR(50) NOT NULL DEFAULT 'api_key', -- 'api_key', 'oauth2', 'basic', 'none' -- Credenciales (encriptadas) api_key_encrypted TEXT, -- Encriptado con pgcrypto api_secret_encrypted TEXT, additional_credentials JSONB DEFAULT '{}'::JSONB, -- Otras credenciales encriptadas -- Rate Limiting rate_limit_requests INTEGER, -- Requests por periodo rate_limit_period_seconds INTEGER DEFAULT 60, current_request_count INTEGER NOT NULL DEFAULT 0, rate_limit_reset_at TIMESTAMPTZ, burst_limit INTEGER, -- Limite de burst -- Capacidades supported_symbols JSONB DEFAULT '[]'::JSONB, -- Simbolos soportados supported_frequencies data_sources.data_frequency[], supports_realtime BOOLEAN NOT NULL DEFAULT FALSE, supports_historical BOOLEAN NOT NULL DEFAULT TRUE, max_historical_days INTEGER, -- Dias maximos de historico -- Cobertura asset_classes VARCHAR(50)[], -- ['forex', 'stocks', 'crypto'] exchanges VARCHAR(50)[], -- ['NYSE', 'NASDAQ', 'BINANCE'] regions VARCHAR(50)[], -- ['US', 'EU', 'LATAM'] -- Costos cost_type VARCHAR(20) DEFAULT 'free', -- 'free', 'paid', 'freemium' monthly_cost DECIMAL(10, 2), cost_per_request DECIMAL(10, 6), free_tier_limit INTEGER, -- Requests gratis por mes -- Prioridad y fallback priority INTEGER NOT NULL DEFAULT 100, -- Menor = mayor prioridad fallback_provider_id UUID REFERENCES data_sources.api_providers(id), is_primary BOOLEAN NOT NULL DEFAULT FALSE, -- Calidad de datos data_quality_score DECIMAL(5, 2), -- 0-100 latency_avg_ms INTEGER, uptime_percent DECIMAL(5, 2), -- Health check health_check_url TEXT, last_health_check_at TIMESTAMPTZ, last_health_status VARCHAR(20), consecutive_failures INTEGER NOT NULL DEFAULT 0, -- Estadisticas de uso total_requests INTEGER NOT NULL DEFAULT 0, successful_requests INTEGER NOT NULL DEFAULT 0, failed_requests INTEGER NOT NULL DEFAULT 0, last_request_at TIMESTAMPTZ, last_error TEXT, last_error_at TIMESTAMPTZ, -- Documentacion documentation_url TEXT, terms_of_service_url TEXT, support_email VARCHAR(255), -- Metadata metadata JSONB DEFAULT '{}'::JSONB, notes TEXT, -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), activated_at TIMESTAMPTZ, deactivated_at TIMESTAMPTZ, -- Constraints CONSTRAINT api_providers_code_unique UNIQUE (tenant_id, code), CONSTRAINT api_providers_rate_check CHECK (rate_limit_requests IS NULL OR rate_limit_requests > 0) ); COMMENT ON TABLE data_sources.api_providers IS 'Proveedores de datos de mercado configurados por tenant'; COMMENT ON COLUMN data_sources.api_providers.api_key_encrypted IS 'API key encriptada - usar pgcrypto para encrypt/decrypt'; COMMENT ON COLUMN data_sources.api_providers.priority IS 'Prioridad de uso - menor numero = mayor prioridad'; -- Indices CREATE INDEX IF NOT EXISTS idx_api_providers_tenant ON data_sources.api_providers(tenant_id); CREATE INDEX IF NOT EXISTS idx_api_providers_code ON data_sources.api_providers(code); CREATE INDEX IF NOT EXISTS idx_api_providers_status ON data_sources.api_providers(status); CREATE INDEX IF NOT EXISTS idx_api_providers_type ON data_sources.api_providers(type); CREATE INDEX IF NOT EXISTS idx_api_providers_active ON data_sources.api_providers(tenant_id, status, priority) WHERE status = 'active'; CREATE INDEX IF NOT EXISTS idx_api_providers_primary ON data_sources.api_providers(tenant_id, is_primary) WHERE is_primary = TRUE; -- GIN index para busqueda en asset_classes y exchanges CREATE INDEX IF NOT EXISTS idx_api_providers_assets_gin ON data_sources.api_providers USING GIN (asset_classes); -- Funcion de timestamp CREATE OR REPLACE FUNCTION data_sources.update_timestamp() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at := NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- Trigger para updated_at DROP TRIGGER IF EXISTS api_provider_updated_at ON data_sources.api_providers; CREATE TRIGGER api_provider_updated_at BEFORE UPDATE ON data_sources.api_providers FOR EACH ROW EXECUTE FUNCTION data_sources.update_timestamp(); -- Trigger para asegurar solo un proveedor primario por tipo CREATE OR REPLACE FUNCTION data_sources.ensure_single_primary_provider() RETURNS TRIGGER AS $$ BEGIN IF NEW.is_primary = TRUE THEN UPDATE data_sources.api_providers SET is_primary = FALSE WHERE tenant_id = NEW.tenant_id AND type = NEW.type AND id != NEW.id AND is_primary = TRUE; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS api_provider_single_primary ON data_sources.api_providers; CREATE TRIGGER api_provider_single_primary BEFORE INSERT OR UPDATE OF is_primary ON data_sources.api_providers FOR EACH ROW WHEN (NEW.is_primary = TRUE) EXECUTE FUNCTION data_sources.ensure_single_primary_provider(); -- Funcion para obtener proveedor activo por tipo CREATE OR REPLACE FUNCTION data_sources.get_active_provider( p_tenant_id UUID, p_type data_sources.provider_type, p_symbol VARCHAR DEFAULT NULL ) RETURNS data_sources.api_providers AS $$ DECLARE v_provider data_sources.api_providers; BEGIN SELECT * INTO v_provider FROM data_sources.api_providers WHERE tenant_id = p_tenant_id AND type = p_type AND status = 'active' AND (p_symbol IS NULL OR p_symbol = ANY( SELECT jsonb_array_elements_text(supported_symbols) )) ORDER BY is_primary DESC, priority ASC LIMIT 1; RETURN v_provider; END; $$ LANGUAGE plpgsql; -- Funcion para incrementar contador de requests CREATE OR REPLACE FUNCTION data_sources.increment_request_count( p_provider_id UUID, p_success BOOLEAN DEFAULT TRUE ) RETURNS VOID AS $$ BEGIN UPDATE data_sources.api_providers SET total_requests = total_requests + 1, successful_requests = successful_requests + CASE WHEN p_success THEN 1 ELSE 0 END, failed_requests = failed_requests + CASE WHEN NOT p_success THEN 1 ELSE 0 END, current_request_count = current_request_count + 1, last_request_at = NOW(), consecutive_failures = CASE WHEN p_success THEN 0 ELSE consecutive_failures + 1 END WHERE id = p_provider_id; END; $$ LANGUAGE plpgsql; -- Vista de proveedores activos CREATE OR REPLACE VIEW data_sources.v_active_providers AS SELECT id, tenant_id, name, code, type, status, is_primary, priority, rate_limit_requests, current_request_count, supports_realtime, asset_classes, data_quality_score, uptime_percent, total_requests, last_request_at FROM data_sources.api_providers WHERE status = 'active' ORDER BY is_primary DESC, priority ASC; -- RLS Policies ALTER TABLE data_sources.api_providers ENABLE ROW LEVEL SECURITY; CREATE POLICY api_providers_tenant_isolation ON data_sources.api_providers FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Grants GRANT USAGE ON SCHEMA data_sources TO trading_app; GRANT SELECT, INSERT, UPDATE, DELETE ON data_sources.api_providers TO trading_app; GRANT SELECT ON data_sources.api_providers TO trading_readonly; GRANT SELECT ON data_sources.v_active_providers TO trading_app; GRANT EXECUTE ON FUNCTION data_sources.get_active_provider TO trading_app; GRANT EXECUTE ON FUNCTION data_sources.increment_request_count TO trading_app;