-- ============================================================================ -- SCHEMA: ml -- TABLE: market_analysis -- DESCRIPTION: Analisis de mercado generado por ML (sesgo, volatilidad, estructura) -- VERSION: 1.0.0 -- CREATED: 2026-01-16 -- SPRINT: Sprint 4 - DDL Implementation Roadmap Q1-2026 -- ============================================================================ -- Enum para tipo de analisis DO $$ BEGIN CREATE TYPE ml.analysis_type AS ENUM ( 'daily_bias', -- Sesgo diario 'weekly_outlook', -- Perspectiva semanal 'session_analysis', -- Analisis de sesion 'structure_analysis', -- Analisis de estructura 'volatility_forecast', -- Pronostico de volatilidad 'correlation_report', -- Reporte de correlaciones 'sentiment_analysis', -- Analisis de sentimiento 'fundamental_impact', -- Impacto fundamental 'comprehensive' -- Analisis completo ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para sesgo de mercado DO $$ BEGIN CREATE TYPE ml.market_bias AS ENUM ( 'strong_bullish', -- Fuertemente alcista 'bullish', -- Alcista 'slightly_bullish', -- Ligeramente alcista 'neutral', -- Neutral 'slightly_bearish', -- Ligeramente bajista 'bearish', -- Bajista 'strong_bearish' -- Fuertemente bajista ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para regimen de volatilidad DO $$ BEGIN CREATE TYPE ml.volatility_regime AS ENUM ( 'extremely_low', -- Extremadamente baja 'low', -- Baja 'normal', -- Normal 'high', -- Alta 'extreme', -- Extrema 'transitioning' -- En transicion ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para estructura de mercado DO $$ BEGIN CREATE TYPE ml.market_structure AS ENUM ( 'uptrend_strong', -- Tendencia alcista fuerte 'uptrend_weak', -- Tendencia alcista debil 'downtrend_strong', -- Tendencia bajista fuerte 'downtrend_weak', -- Tendencia bajista debil 'ranging_tight', -- Rango estrecho 'ranging_wide', -- Rango amplio 'consolidating', -- Consolidando 'breaking_out', -- Rompiendo 'reversing' -- Revirtiendo ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Tabla de Analisis de Mercado CREATE TABLE IF NOT EXISTS ml.market_analysis ( -- Identificadores id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, -- Simbolo y scope symbol VARCHAR(20) NOT NULL, asset_class ml.asset_class NOT NULL DEFAULT 'FOREX', analysis_type ml.analysis_type NOT NULL, timeframe VARCHAR(10) NOT NULL DEFAULT 'D1', -- Periodo del analisis analysis_date DATE NOT NULL, valid_from TIMESTAMPTZ NOT NULL, valid_until TIMESTAMPTZ NOT NULL, -- Sesgo y direccion bias ml.market_bias NOT NULL, bias_confidence DECIMAL(5, 4) NOT NULL DEFAULT 0.5 CHECK (bias_confidence BETWEEN 0 AND 1), bias_reasoning TEXT, -- Estructura de mercado structure ml.market_structure NOT NULL, structure_timeframe VARCHAR(10), -- TF de la estructura dominante trend_strength DECIMAL(5, 2), -- -100 a +100 -- Volatilidad volatility_regime ml.volatility_regime NOT NULL, current_atr DECIMAL(15, 8), atr_percentile DECIMAL(5, 2), -- Percentil historico expected_range_pips DECIMAL(10, 2), volatility_forecast DECIMAL(10, 4), -- Volatilidad esperada -- Niveles clave key_resistance_1 DECIMAL(15, 8), key_resistance_2 DECIMAL(15, 8), key_resistance_3 DECIMAL(15, 8), key_support_1 DECIMAL(15, 8), key_support_2 DECIMAL(15, 8), key_support_3 DECIMAL(15, 8), pivot_point DECIMAL(15, 8), -- Order blocks y FVGs bullish_order_blocks JSONB DEFAULT '[]'::JSONB, bearish_order_blocks JSONB DEFAULT '[]'::JSONB, fair_value_gaps JSONB DEFAULT '[]'::JSONB, liquidity_levels JSONB DEFAULT '[]'::JSONB, -- Indicadores indicators JSONB DEFAULT '{}'::JSONB, -- RSI, MACD, etc. moving_averages JSONB DEFAULT '{}'::JSONB, -- SMAs, EMAs -- Correlaciones correlation_dxy DECIMAL(5, 4), -- Correlacion con DXY correlation_gold DECIMAL(5, 4), -- Correlacion con oro correlation_sp500 DECIMAL(5, 4), -- Correlacion con S&P500 correlated_pairs JSONB DEFAULT '[]'::JSONB, -- Pares correlacionados -- Sentimiento sentiment_score DECIMAL(5, 2), -- -100 a +100 sentiment_sources JSONB DEFAULT '{}'::JSONB, -- Fuentes de sentimiento cot_report JSONB DEFAULT '{}'::JSONB, -- Datos COT -- Eventos fundamentales upcoming_events JSONB DEFAULT '[]'::JSONB, -- Eventos proximos recent_events_impact JSONB DEFAULT '[]'::JSONB, -- Impacto de eventos recientes high_impact_event_today BOOLEAN NOT NULL DEFAULT FALSE, -- Escenarios primary_scenario JSONB DEFAULT '{}'::JSONB, -- Escenario principal secondary_scenario JSONB DEFAULT '{}'::JSONB, -- Escenario alternativo risk_scenario JSONB DEFAULT '{}'::JSONB, -- Escenario de riesgo -- Zonas de interes buy_zones JSONB DEFAULT '[]'::JSONB, -- Zonas de compra sell_zones JSONB DEFAULT '[]'::JSONB, -- Zonas de venta avoid_zones JSONB DEFAULT '[]'::JSONB, -- Zonas a evitar -- Modelo model_id VARCHAR(100), model_version VARCHAR(50) NOT NULL, analysis_confidence DECIMAL(5, 4) NOT NULL DEFAULT 0.5 CHECK (analysis_confidence BETWEEN 0 AND 1), -- Resumen summary TEXT, -- Resumen ejecutivo trading_plan TEXT, -- Plan de trading sugerido key_insights JSONB DEFAULT '[]'::JSONB, -- Insights clave -- Resultado (post-validacion) actual_direction VARCHAR(20), -- Direccion real actual_range_pips DECIMAL(10, 2), accuracy_score DECIMAL(5, 2), -- Score de precision validated_at TIMESTAMPTZ, -- Metadata tags VARCHAR(50)[], notes TEXT, metadata JSONB DEFAULT '{}'::JSONB, -- Timestamps generated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Constraints CONSTRAINT market_analysis_unique UNIQUE (tenant_id, symbol, analysis_type, analysis_date) ); COMMENT ON TABLE ml.market_analysis IS 'Analisis de mercado generado por modelos ML: sesgo, estructura, volatilidad, niveles clave'; COMMENT ON COLUMN ml.market_analysis.bias IS 'Sesgo direccional del mercado para el periodo analizado'; COMMENT ON COLUMN ml.market_analysis.structure IS 'Estructura de mercado dominante (tendencia, rango, etc.)'; -- Indices CREATE INDEX IF NOT EXISTS idx_market_analysis_tenant ON ml.market_analysis(tenant_id); CREATE INDEX IF NOT EXISTS idx_market_analysis_symbol ON ml.market_analysis(symbol, analysis_date DESC); CREATE INDEX IF NOT EXISTS idx_market_analysis_date ON ml.market_analysis(analysis_date DESC); CREATE INDEX IF NOT EXISTS idx_market_analysis_type ON ml.market_analysis(analysis_type, analysis_date DESC); CREATE INDEX IF NOT EXISTS idx_market_analysis_bias ON ml.market_analysis(bias, analysis_date DESC); CREATE INDEX IF NOT EXISTS idx_market_analysis_active ON ml.market_analysis(valid_until) WHERE valid_until > NOW(); CREATE INDEX IF NOT EXISTS idx_market_analysis_model ON ml.market_analysis(model_version, analysis_date DESC); CREATE INDEX IF NOT EXISTS idx_market_analysis_high_impact ON ml.market_analysis(analysis_date, high_impact_event_today) WHERE high_impact_event_today = TRUE; -- GIN indices para JSONB CREATE INDEX IF NOT EXISTS idx_market_analysis_events_gin ON ml.market_analysis USING GIN (upcoming_events); CREATE INDEX IF NOT EXISTS idx_market_analysis_insights_gin ON ml.market_analysis USING GIN (key_insights); -- Trigger para updated_at DROP TRIGGER IF EXISTS market_analysis_updated_at ON ml.market_analysis; CREATE TRIGGER market_analysis_updated_at BEFORE UPDATE ON ml.market_analysis FOR EACH ROW EXECUTE FUNCTION ml.update_ml_timestamp(); -- Funcion para obtener analisis actual CREATE OR REPLACE FUNCTION ml.get_current_analysis( p_tenant_id UUID, p_symbol VARCHAR, p_analysis_type ml.analysis_type DEFAULT 'daily_bias' ) RETURNS ml.market_analysis AS $$ DECLARE v_analysis ml.market_analysis; BEGIN SELECT * INTO v_analysis FROM ml.market_analysis WHERE tenant_id = p_tenant_id AND symbol = p_symbol AND analysis_type = p_analysis_type AND valid_until > NOW() ORDER BY analysis_date DESC LIMIT 1; RETURN v_analysis; END; $$ LANGUAGE plpgsql; -- Funcion para obtener sesgo agregado de multiples simbolos CREATE OR REPLACE FUNCTION ml.get_market_overview( p_tenant_id UUID, p_symbols VARCHAR[] DEFAULT NULL ) RETURNS TABLE ( symbol VARCHAR, bias ml.market_bias, bias_confidence DECIMAL, structure ml.market_structure, volatility_regime ml.volatility_regime, analysis_date DATE ) AS $$ BEGIN RETURN QUERY SELECT DISTINCT ON (ma.symbol) ma.symbol, ma.bias, ma.bias_confidence, ma.structure, ma.volatility_regime, ma.analysis_date FROM ml.market_analysis ma WHERE ma.tenant_id = p_tenant_id AND ma.analysis_type = 'daily_bias' AND ma.valid_until > NOW() AND (p_symbols IS NULL OR ma.symbol = ANY(p_symbols)) ORDER BY ma.symbol, ma.analysis_date DESC; END; $$ LANGUAGE plpgsql; -- Funcion para validar analisis con datos reales CREATE OR REPLACE FUNCTION ml.validate_market_analysis( p_analysis_id UUID, p_actual_high DECIMAL, p_actual_low DECIMAL, p_actual_close DECIMAL ) RETURNS VOID AS $$ DECLARE v_analysis ml.market_analysis; v_direction VARCHAR; v_accuracy DECIMAL; BEGIN SELECT * INTO v_analysis FROM ml.market_analysis WHERE id = p_analysis_id; IF NOT FOUND THEN RETURN; END IF; -- Determinar direccion real IF p_actual_close > (p_actual_high + p_actual_low) / 2 THEN v_direction := 'bullish'; ELSE v_direction := 'bearish'; END IF; -- Calcular accuracy basado en sesgo predicho vs real v_accuracy := CASE WHEN v_analysis.bias IN ('strong_bullish', 'bullish', 'slightly_bullish') AND v_direction = 'bullish' THEN 100 WHEN v_analysis.bias IN ('strong_bearish', 'bearish', 'slightly_bearish') AND v_direction = 'bearish' THEN 100 WHEN v_analysis.bias = 'neutral' THEN 50 ELSE 0 END; UPDATE ml.market_analysis SET actual_direction = v_direction, actual_range_pips = (p_actual_high - p_actual_low) * 10000, accuracy_score = v_accuracy, validated_at = NOW() WHERE id = p_analysis_id; END; $$ LANGUAGE plpgsql; -- Vista de analisis activos CREATE OR REPLACE VIEW ml.v_current_market_analysis AS SELECT id, tenant_id, symbol, asset_class, analysis_type, analysis_date, bias, bias_confidence, structure, volatility_regime, expected_range_pips, key_resistance_1, key_support_1, pivot_point, sentiment_score, high_impact_event_today, summary, analysis_confidence, valid_until FROM ml.market_analysis WHERE valid_until > NOW() ORDER BY analysis_date DESC, symbol; -- Vista de rendimiento de analisis CREATE OR REPLACE VIEW ml.v_market_analysis_performance AS SELECT symbol, analysis_type, model_version, COUNT(*) AS total_analyses, ROUND(AVG(accuracy_score)::NUMERIC, 2) AS avg_accuracy, ROUND(AVG(bias_confidence)::NUMERIC, 4) AS avg_confidence, COUNT(*) FILTER (WHERE accuracy_score >= 70) AS accurate_count, COUNT(*) FILTER (WHERE accuracy_score < 30) AS inaccurate_count, ROUND((COUNT(*) FILTER (WHERE accuracy_score >= 70)::DECIMAL / NULLIF(COUNT(*) FILTER (WHERE validated_at IS NOT NULL), 0) * 100), 2) AS accuracy_rate FROM ml.market_analysis WHERE validated_at IS NOT NULL GROUP BY symbol, analysis_type, model_version ORDER BY accuracy_rate DESC NULLS LAST; -- Vista de correlaciones actuales CREATE OR REPLACE VIEW ml.v_market_correlations AS SELECT symbol, analysis_date, correlation_dxy, correlation_gold, correlation_sp500, bias, structure FROM ml.market_analysis WHERE analysis_type = 'daily_bias' AND valid_until > NOW() AND (correlation_dxy IS NOT NULL OR correlation_gold IS NOT NULL) ORDER BY analysis_date DESC, symbol; -- RLS Policies ALTER TABLE ml.market_analysis ENABLE ROW LEVEL SECURITY; CREATE POLICY market_analysis_tenant ON ml.market_analysis FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Grants GRANT SELECT, INSERT, UPDATE ON ml.market_analysis TO trading_app; GRANT SELECT ON ml.market_analysis TO trading_readonly; GRANT SELECT ON ml.v_current_market_analysis TO trading_app; GRANT SELECT ON ml.v_market_analysis_performance TO trading_app; GRANT SELECT ON ml.v_market_correlations TO trading_app; GRANT EXECUTE ON FUNCTION ml.get_current_analysis TO trading_app; GRANT EXECUTE ON FUNCTION ml.get_market_overview TO trading_app; GRANT EXECUTE ON FUNCTION ml.validate_market_analysis TO trading_app;