trading-platform-database-v2/ddl/schemas/data_sources/tables/001_api_providers.sql
rckrdmrd 9f8bbb7494 [DDL] feat: Sprint 4 - Add data_sources and ml_predictions schemas
- data_sources schema:
  - api_providers: Proveedores de datos de mercado
  - ticker_mapping: Mapeo de simbolos entre proveedores
  - data_sync_status: Estado de sincronizacion de datos

- ml schema additions:
  - range_predictions: Predicciones de rango (particionada)
  - entry_signals: Señales de entrada con metodologias ICT/SMC/AMD
  - market_analysis: Analisis de mercado (sesgo, estructura, volatilidad)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 20:08:51 -06:00

306 lines
10 KiB
PL/PgSQL

-- ============================================================================
-- 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;