-- ============================================================================
-- SCHEMA: ml
-- TABLE: entry_signals
-- DESCRIPTION: Senales de entrada generadas por modelos ML
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 4 - DDL Implementation Roadmap Q1-2026
-- ============================================================================

-- Enum para metodologia de senal
DO $$ BEGIN
    CREATE TYPE ml.signal_methodology AS ENUM (
        'amd',                  -- Accumulation, Manipulation, Distribution
        'ict_smc',              -- ICT Smart Money Concepts
        'order_blocks',         -- Order Blocks
        'fair_value_gap',       -- Fair Value Gaps
        'liquidity_sweep',      -- Liquidity Sweeps
        'break_of_structure',   -- Break of Structure
        'supply_demand',        -- Supply & Demand zones
        'technical',            -- Indicadores tecnicos clasicos
        'pattern',              -- Patrones de precio
        'ensemble'              -- Combinacion de metodologias
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para fase AMD
DO $$ BEGIN
    CREATE TYPE ml.amd_phase AS ENUM (
        'accumulation',         -- Fase de acumulacion
        'manipulation',         -- Fase de manipulacion
        'distribution',         -- Fase de distribucion
        'expansion',            -- Fase de expansion
        'retracement',          -- Fase de retroceso
        'consolidation',        -- Consolidacion
        'unknown'               -- Fase no determinada
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para estado de senal de entrada
DO $$ BEGIN
    CREATE TYPE ml.entry_signal_status AS ENUM (
        'generated',            -- Generada por modelo
        'pending',              -- Pendiente de activacion
        'active',               -- Activa (precio llego a zona)
        'triggered',            -- Disparada (orden ejecutada)
        'expired',              -- Expirada sin activar
        'invalidated',          -- Invalidada por condiciones
        'cancelled'             -- Cancelada
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla de Senales de Entrada ML
CREATE TABLE IF NOT EXISTS ml.entry_signals (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    user_id UUID,                                       -- NULL si es senal publica

    -- Referencias
    range_prediction_id UUID,                           -- Prediccion de rango asociada
    ticker_id UUID REFERENCES market_data.tickers(id),

    -- Simbolo y timeframe
    symbol VARCHAR(20) NOT NULL,
    asset_class ml.asset_class NOT NULL DEFAULT 'FOREX',
    timeframe VARCHAR(10) NOT NULL DEFAULT 'H1',
    session VARCHAR(20),                                -- 'asia', 'london', 'ny'

    -- Metodologia
    methodology ml.signal_methodology NOT NULL,
    amd_phase ml.amd_phase,

    -- Direccion
    direction VARCHAR(10) NOT NULL CHECK (direction IN ('LONG', 'SHORT')),

    -- Estado
    status ml.entry_signal_status NOT NULL DEFAULT 'generated',

    -- Zona de entrada
    entry_zone_high DECIMAL(15, 8) NOT NULL,            -- Limite superior de entrada
    entry_zone_low DECIMAL(15, 8) NOT NULL,             -- Limite inferior de entrada
    optimal_entry DECIMAL(15, 8),                       -- Precio optimo de entrada

    -- Stop Loss
    stop_loss DECIMAL(15, 8) NOT NULL,
    stop_loss_pips DECIMAL(10, 2),
    invalidation_price DECIMAL(15, 8),                  -- Precio que invalida la senal

    -- Take Profits
    take_profit_1 DECIMAL(15, 8),
    take_profit_2 DECIMAL(15, 8),
    take_profit_3 DECIMAL(15, 8),
    tp1_pips DECIMAL(10, 2),
    tp2_pips DECIMAL(10, 2),
    tp3_pips DECIMAL(10, 2),

    -- Risk/Reward
    risk_reward_1 DECIMAL(5, 2),                        -- RR a TP1
    risk_reward_2 DECIMAL(5, 2),                        -- RR a TP2
    risk_reward_3 DECIMAL(5, 2),                        -- RR a TP3

    -- Confianza
    confidence_score DECIMAL(5, 4) NOT NULL DEFAULT 0.5
        CHECK (confidence_score BETWEEN 0 AND 1),
    strength VARCHAR(20),                               -- 'weak', 'moderate', 'strong', 'very_strong'
    quality_grade VARCHAR(2),                           -- 'A+', 'A', 'B', 'C', 'D'

    -- Modelo
    model_id VARCHAR(100),
    model_version VARCHAR(50) NOT NULL,

    -- Contexto tecnico
    market_structure JSONB DEFAULT '{}'::JSONB,         -- BOS, CHoCH, etc.
    key_levels JSONB DEFAULT '[]'::JSONB,               -- Niveles clave cercanos
    order_blocks JSONB DEFAULT '[]'::JSONB,             -- Order blocks detectados
    fair_value_gaps JSONB DEFAULT '[]'::JSONB,          -- FVGs detectados
    liquidity_pools JSONB DEFAULT '[]'::JSONB,          -- Pools de liquidez

    -- Indicadores al momento
    rsi_14 DECIMAL(5, 2),
    macd_histogram DECIMAL(15, 8),
    atr_14 DECIMAL(15, 8),
    volume_ratio DECIMAL(5, 2),

    -- Contexto de mercado
    market_bias VARCHAR(20),                            -- 'bullish', 'bearish', 'neutral'
    volatility_regime VARCHAR(20),                      -- 'low', 'medium', 'high', 'extreme'
    trend_direction VARCHAR(20),                        -- 'up', 'down', 'ranging'
    session_bias VARCHAR(20),                           -- Sesgo de la sesion

    -- Validez
    valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    valid_until TIMESTAMPTZ NOT NULL,

    -- Ejecucion
    activated_at TIMESTAMPTZ,                           -- Cuando precio llego a zona
    triggered_at TIMESTAMPTZ,                           -- Cuando se ejecuto
    triggered_price DECIMAL(15, 8),
    slippage DECIMAL(10, 4),

    -- Resultado
    result VARCHAR(20),                                 -- 'win', 'loss', 'breakeven', 'partial'
    exit_price DECIMAL(15, 8),
    exit_reason VARCHAR(50),
    pnl_pips DECIMAL(10, 2),
    pnl_rr DECIMAL(5, 2),                               -- Resultado en multiplos de RR

    -- Trazabilidad
    position_id UUID,                                   -- Posicion resultante

    -- Features del modelo
    features JSONB DEFAULT '{}'::JSONB,
    explanation TEXT,                                   -- Explicacion de la senal

    -- Notificacion
    notified_at TIMESTAMPTZ,
    notification_channels VARCHAR(20)[],

    -- 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 entry_signals_zone_valid CHECK (entry_zone_high >= entry_zone_low)
);

COMMENT ON TABLE ml.entry_signals IS
'Senales de entrada generadas por modelos ML con metodologias ICT/SMC/AMD';

COMMENT ON COLUMN ml.entry_signals.amd_phase IS
'Fase AMD detectada: Accumulation, Manipulation, Distribution';

COMMENT ON COLUMN ml.entry_signals.quality_grade IS
'Calificacion de calidad de la senal: A+, A, B, C, D';

-- Indices
CREATE INDEX IF NOT EXISTS idx_entry_signals_tenant
    ON ml.entry_signals(tenant_id);

CREATE INDEX IF NOT EXISTS idx_entry_signals_user
    ON ml.entry_signals(user_id)
    WHERE user_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_entry_signals_symbol
    ON ml.entry_signals(symbol, generated_at DESC);

CREATE INDEX IF NOT EXISTS idx_entry_signals_status
    ON ml.entry_signals(status);

CREATE INDEX IF NOT EXISTS idx_entry_signals_active
    ON ml.entry_signals(status, valid_until)
    WHERE status IN ('pending', 'active');

CREATE INDEX IF NOT EXISTS idx_entry_signals_methodology
    ON ml.entry_signals(methodology, direction);

CREATE INDEX IF NOT EXISTS idx_entry_signals_amd
    ON ml.entry_signals(amd_phase, generated_at DESC)
    WHERE amd_phase IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_entry_signals_confidence
    ON ml.entry_signals(confidence_score DESC)
    WHERE status IN ('pending', 'active');

CREATE INDEX IF NOT EXISTS idx_entry_signals_generated
    ON ml.entry_signals(generated_at DESC);

CREATE INDEX IF NOT EXISTS idx_entry_signals_result
    ON ml.entry_signals(result, triggered_at DESC)
    WHERE result IS NOT NULL;

-- GIN indices para JSONB
CREATE INDEX IF NOT EXISTS idx_entry_signals_keys_gin
    ON ml.entry_signals USING GIN (key_levels);

CREATE INDEX IF NOT EXISTS idx_entry_signals_ob_gin
    ON ml.entry_signals USING GIN (order_blocks);

-- Trigger para updated_at
DROP TRIGGER IF EXISTS entry_signal_updated_at ON ml.entry_signals;
CREATE TRIGGER entry_signal_updated_at
    BEFORE UPDATE ON ml.entry_signals
    FOR EACH ROW
    EXECUTE FUNCTION ml.update_ml_timestamp();

-- Trigger para calcular strength basado en confidence
CREATE OR REPLACE FUNCTION ml.calculate_signal_strength()
RETURNS TRIGGER AS $$
BEGIN
    NEW.strength := CASE
        WHEN NEW.confidence_score >= 0.85 THEN 'very_strong'
        WHEN NEW.confidence_score >= 0.70 THEN 'strong'
        WHEN NEW.confidence_score >= 0.55 THEN 'moderate'
        ELSE 'weak'
    END;

    -- Calcular pips si hay precios
    IF NEW.optimal_entry IS NOT NULL AND NEW.stop_loss IS NOT NULL THEN
        NEW.stop_loss_pips := ABS(NEW.optimal_entry - NEW.stop_loss) * 10000;

        IF NEW.take_profit_1 IS NOT NULL THEN
            NEW.tp1_pips := ABS(NEW.take_profit_1 - NEW.optimal_entry) * 10000;
            NEW.risk_reward_1 := NEW.tp1_pips / NULLIF(NEW.stop_loss_pips, 0);
        END IF;

        IF NEW.take_profit_2 IS NOT NULL THEN
            NEW.tp2_pips := ABS(NEW.take_profit_2 - NEW.optimal_entry) * 10000;
            NEW.risk_reward_2 := NEW.tp2_pips / NULLIF(NEW.stop_loss_pips, 0);
        END IF;

        IF NEW.take_profit_3 IS NOT NULL THEN
            NEW.tp3_pips := ABS(NEW.take_profit_3 - NEW.optimal_entry) * 10000;
            NEW.risk_reward_3 := NEW.tp3_pips / NULLIF(NEW.stop_loss_pips, 0);
        END IF;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS entry_signal_calc_strength ON ml.entry_signals;
CREATE TRIGGER entry_signal_calc_strength
    BEFORE INSERT OR UPDATE OF confidence_score, optimal_entry, stop_loss, take_profit_1, take_profit_2, take_profit_3
    ON ml.entry_signals
    FOR EACH ROW
    EXECUTE FUNCTION ml.calculate_signal_strength();

-- Funcion para activar senal (precio llego a zona)
CREATE OR REPLACE FUNCTION ml.activate_entry_signal(
    p_signal_id UUID,
    p_current_price DECIMAL
)
RETURNS BOOLEAN AS $$
DECLARE
    v_signal ml.entry_signals;
BEGIN
    SELECT * INTO v_signal FROM ml.entry_signals WHERE id = p_signal_id;

    IF NOT FOUND OR v_signal.status != 'pending' THEN
        RETURN FALSE;
    END IF;

    -- Verificar si precio esta en zona
    IF p_current_price BETWEEN v_signal.entry_zone_low AND v_signal.entry_zone_high THEN
        UPDATE ml.entry_signals
        SET status = 'active',
            activated_at = NOW()
        WHERE id = p_signal_id;
        RETURN TRUE;
    END IF;

    RETURN FALSE;
END;
$$ LANGUAGE plpgsql;

-- Funcion para registrar resultado de senal
CREATE OR REPLACE FUNCTION ml.record_signal_result(
    p_signal_id UUID,
    p_result VARCHAR,
    p_exit_price DECIMAL,
    p_exit_reason VARCHAR DEFAULT 'manual',
    p_position_id UUID DEFAULT NULL
)
RETURNS VOID AS $$
DECLARE
    v_signal ml.entry_signals;
BEGIN
    SELECT * INTO v_signal FROM ml.entry_signals WHERE id = p_signal_id;

    IF NOT FOUND THEN
        RETURN;
    END IF;

    UPDATE ml.entry_signals
    SET result = p_result,
        exit_price = p_exit_price,
        exit_reason = p_exit_reason,
        position_id = p_position_id,
        pnl_pips = CASE v_signal.direction
            WHEN 'LONG' THEN (p_exit_price - v_signal.triggered_price) * 10000
            ELSE (v_signal.triggered_price - p_exit_price) * 10000
        END,
        pnl_rr = CASE
            WHEN v_signal.stop_loss_pips > 0 THEN
                CASE v_signal.direction
                    WHEN 'LONG' THEN (p_exit_price - v_signal.triggered_price) * 10000 / v_signal.stop_loss_pips
                    ELSE (v_signal.triggered_price - p_exit_price) * 10000 / v_signal.stop_loss_pips
                END
            ELSE NULL
        END
    WHERE id = p_signal_id;
END;
$$ LANGUAGE plpgsql;

-- Vista de senales activas
CREATE OR REPLACE VIEW ml.v_active_entry_signals AS
SELECT
    id,
    tenant_id,
    symbol,
    direction,
    methodology,
    amd_phase,
    status,
    entry_zone_high,
    entry_zone_low,
    optimal_entry,
    stop_loss,
    take_profit_1,
    risk_reward_1,
    confidence_score,
    strength,
    quality_grade,
    market_bias,
    volatility_regime,
    valid_until,
    generated_at
FROM ml.entry_signals
WHERE status IN ('pending', 'active')
  AND valid_until > NOW()
ORDER BY confidence_score DESC, generated_at DESC;

-- Vista de rendimiento por metodologia
CREATE OR REPLACE VIEW ml.v_signal_methodology_performance AS
SELECT
    methodology,
    amd_phase,
    direction,
    COUNT(*) AS total_signals,
    COUNT(*) FILTER (WHERE result = 'win') AS wins,
    COUNT(*) FILTER (WHERE result = 'loss') AS losses,
    COUNT(*) FILTER (WHERE result = 'breakeven') AS breakeven,
    ROUND((COUNT(*) FILTER (WHERE result = 'win')::DECIMAL
        / NULLIF(COUNT(*) FILTER (WHERE result IS NOT NULL), 0) * 100), 2) AS win_rate,
    ROUND(AVG(confidence_score)::NUMERIC, 4) AS avg_confidence,
    ROUND(AVG(pnl_rr) FILTER (WHERE pnl_rr IS NOT NULL)::NUMERIC, 2) AS avg_rr,
    ROUND(SUM(pnl_pips) FILTER (WHERE pnl_pips IS NOT NULL)::NUMERIC, 2) AS total_pips
FROM ml.entry_signals
WHERE result IS NOT NULL
GROUP BY methodology, amd_phase, direction
ORDER BY win_rate DESC NULLS LAST;

-- RLS Policies
ALTER TABLE ml.entry_signals ENABLE ROW LEVEL SECURITY;

CREATE POLICY entry_signals_tenant ON ml.entry_signals
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

-- Grants
GRANT SELECT, INSERT, UPDATE ON ml.entry_signals TO trading_app;
GRANT SELECT ON ml.entry_signals TO trading_readonly;
GRANT SELECT ON ml.v_active_entry_signals TO trading_app;
GRANT SELECT ON ml.v_signal_methodology_performance TO trading_app;
GRANT EXECUTE ON FUNCTION ml.activate_entry_signal TO trading_app;
GRANT EXECUTE ON FUNCTION ml.record_signal_result TO trading_app;
