-- ============================================================================
-- SCHEMA: trading
-- TABLE: signals
-- DESCRIPTION: Senales de trading generadas por ML/bots
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 3 - DDL Implementation Roadmap Q1-2026
-- ============================================================================

-- Enum para tipo de senal
DO $$ BEGIN
    CREATE TYPE trading.signal_type AS ENUM (
        'entry',            -- Senal de entrada
        'exit',             -- Senal de salida
        'scale_in',         -- Agregar a posicion
        'scale_out',        -- Reducir posicion
        'move_sl',          -- Mover stop loss
        'move_tp',          -- Mover take profit
        'alert'             -- Alerta informativa
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para estado de senal
DO $$ BEGIN
    CREATE TYPE trading.signal_status AS ENUM (
        'pending',          -- Pendiente de validacion
        'active',           -- Activa y disponible
        'executed',         -- Ejecutada
        'expired',          -- Expirada sin ejecutar
        'cancelled',        -- Cancelada
        'invalidated'       -- Invalidada por condiciones
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para fuente de senal
DO $$ BEGIN
    CREATE TYPE trading.signal_source AS ENUM (
        'ml_model',         -- Modelo ML
        'technical',        -- Analisis tecnico
        'fundamental',      -- Analisis fundamental
        'sentiment',        -- Analisis de sentimiento
        'manual',           -- Analista manual
        'bot',              -- Bot de trading
        'copy_trade'        -- Copy trading
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla de Senales de Trading
CREATE TABLE IF NOT EXISTS trading.signals (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
    bot_id UUID REFERENCES trading.bots(id),
    strategy_id UUID REFERENCES trading.strategies(id),

    -- Clasificacion
    type trading.signal_type NOT NULL DEFAULT 'entry',
    source trading.signal_source NOT NULL DEFAULT 'ml_model',
    status trading.signal_status NOT NULL DEFAULT 'pending',

    -- Instrumento
    symbol_id UUID REFERENCES trading.symbols(id),
    symbol VARCHAR(20) NOT NULL,
    timeframe trading.timeframe DEFAULT 'H1',

    -- Direccion y precios
    direction trading.trade_direction NOT NULL,
    entry_price DECIMAL(15, 8) NOT NULL,
    stop_loss DECIMAL(15, 8),
    take_profit DECIMAL(15, 8),
    take_profit_2 DECIMAL(15, 8),                   -- TP secundario
    take_profit_3 DECIMAL(15, 8),                   -- TP terciario

    -- Risk/Reward
    risk_pips DECIMAL(10, 4),
    reward_pips DECIMAL(10, 4),
    risk_reward_ratio DECIMAL(10, 4),

    -- Confianza y scoring
    confidence DECIMAL(5, 2) NOT NULL DEFAULT 50    -- 0-100
        CHECK (confidence BETWEEN 0 AND 100),
    strength VARCHAR(20),                           -- 'weak', 'moderate', 'strong', 'very_strong'
    quality_score DECIMAL(5, 2),

    -- Modelo ML (si aplica)
    model_id VARCHAR(100),
    model_version VARCHAR(20),
    model_prediction JSONB,                         -- Prediccion completa del modelo

    -- Analisis tecnico
    technical_analysis JSONB DEFAULT '{}'::JSONB,  -- Indicadores al momento de la senal
    pattern_detected VARCHAR(100),                  -- Patron identificado
    key_levels JSONB,                               -- Niveles clave S/R

    -- Contexto de mercado
    market_context JSONB DEFAULT '{}'::JSONB,      -- Condiciones de mercado
    trend_direction VARCHAR(20),
    volatility_level VARCHAR(20),

    -- Validez
    valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    valid_until TIMESTAMPTZ,                        -- NULL = sin expiracion
    invalidation_price DECIMAL(15, 8),              -- Precio que invalida la senal

    -- Ejecucion
    executed_at TIMESTAMPTZ,
    executed_price DECIMAL(15, 8),
    execution_slippage DECIMAL(10, 4),
    position_id UUID,                               -- Posicion resultante

    -- Resultado
    result VARCHAR(20),                             -- 'win', 'loss', 'breakeven', 'partial'
    result_pips DECIMAL(10, 4),
    result_amount DECIMAL(15, 4),

    -- Notificaciones
    notification_sent BOOLEAN NOT NULL DEFAULT FALSE,
    notification_sent_at TIMESTAMPTZ,
    notification_channels JSONB DEFAULT '[]'::JSONB,

    -- Usuarios que vieron/ejecutaron
    view_count INTEGER NOT NULL DEFAULT 0,
    execution_count INTEGER NOT NULL DEFAULT 0,

    -- 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()
);

COMMENT ON TABLE trading.signals IS
'Senales de trading generadas por modelos ML, bots o analistas';

COMMENT ON COLUMN trading.signals.confidence IS
'Nivel de confianza de la senal (0-100%)';

COMMENT ON COLUMN trading.signals.invalidation_price IS
'Precio que invalida la senal antes de ser ejecutada';

-- Indices
CREATE INDEX IF NOT EXISTS idx_signals_tenant
    ON trading.signals(tenant_id);

CREATE INDEX IF NOT EXISTS idx_signals_bot
    ON trading.signals(bot_id);

CREATE INDEX IF NOT EXISTS idx_signals_symbol
    ON trading.signals(symbol, generated_at DESC);

CREATE INDEX IF NOT EXISTS idx_signals_status
    ON trading.signals(status);

CREATE INDEX IF NOT EXISTS idx_signals_active
    ON trading.signals(tenant_id, status, generated_at DESC)
    WHERE status = 'active';

CREATE INDEX IF NOT EXISTS idx_signals_type_direction
    ON trading.signals(type, direction);

CREATE INDEX IF NOT EXISTS idx_signals_confidence
    ON trading.signals(confidence DESC)
    WHERE status = 'active';

CREATE INDEX IF NOT EXISTS idx_signals_valid_until
    ON trading.signals(valid_until)
    WHERE valid_until IS NOT NULL AND status = 'active';

CREATE INDEX IF NOT EXISTS idx_signals_generated
    ON trading.signals(generated_at DESC);

-- GIN index para technical_analysis
CREATE INDEX IF NOT EXISTS idx_signals_ta_gin
    ON trading.signals USING GIN (technical_analysis);

-- Trigger para updated_at
DROP TRIGGER IF EXISTS signal_updated_at ON trading.signals;
CREATE TRIGGER signal_updated_at
    BEFORE UPDATE ON trading.signals
    FOR EACH ROW
    EXECUTE FUNCTION trading.update_trading_timestamp();

-- Trigger para calcular risk/reward
CREATE OR REPLACE FUNCTION trading.calculate_signal_rr()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.entry_price IS NOT NULL AND NEW.stop_loss IS NOT NULL THEN
        IF NEW.direction = 'long' THEN
            NEW.risk_pips := (NEW.entry_price - NEW.stop_loss) * 10000;  -- Para forex
            IF NEW.take_profit IS NOT NULL THEN
                NEW.reward_pips := (NEW.take_profit - NEW.entry_price) * 10000;
            END IF;
        ELSE
            NEW.risk_pips := (NEW.stop_loss - NEW.entry_price) * 10000;
            IF NEW.take_profit IS NOT NULL THEN
                NEW.reward_pips := (NEW.entry_price - NEW.take_profit) * 10000;
            END IF;
        END IF;

        IF NEW.risk_pips > 0 AND NEW.reward_pips IS NOT NULL THEN
            NEW.risk_reward_ratio := NEW.reward_pips / NEW.risk_pips;
        END IF;
    END IF;

    -- Determinar strength basado en confidence
    NEW.strength := CASE
        WHEN NEW.confidence >= 85 THEN 'very_strong'
        WHEN NEW.confidence >= 70 THEN 'strong'
        WHEN NEW.confidence >= 55 THEN 'moderate'
        ELSE 'weak'
    END;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS signal_calc_rr ON trading.signals;
CREATE TRIGGER signal_calc_rr
    BEFORE INSERT OR UPDATE OF entry_price, stop_loss, take_profit, confidence ON trading.signals
    FOR EACH ROW
    EXECUTE FUNCTION trading.calculate_signal_rr();

-- Trigger para expirar senales
CREATE OR REPLACE FUNCTION trading.expire_old_signals()
RETURNS INTEGER AS $$
DECLARE
    v_count INTEGER;
BEGIN
    UPDATE trading.signals
    SET status = 'expired'
    WHERE status = 'active'
      AND valid_until IS NOT NULL
      AND valid_until < NOW();

    GET DIAGNOSTICS v_count = ROW_COUNT;
    RETURN v_count;
END;
$$ LANGUAGE plpgsql;

-- Funcion para actualizar estadisticas del bot cuando se procesa senal
CREATE OR REPLACE FUNCTION trading.update_bot_on_signal()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.bot_id IS NOT NULL THEN
        UPDATE trading.bots
        SET last_signal_at = NOW()
        WHERE id = NEW.bot_id;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS signal_bot_update ON trading.signals;
CREATE TRIGGER signal_bot_update
    AFTER INSERT ON trading.signals
    FOR EACH ROW
    EXECUTE FUNCTION trading.update_bot_on_signal();

-- Vista de senales activas
CREATE OR REPLACE VIEW trading.v_active_signals AS
SELECT
    s.id,
    s.tenant_id,
    s.bot_id,
    b.name AS bot_name,
    s.symbol,
    s.timeframe,
    s.type,
    s.direction,
    s.entry_price,
    s.stop_loss,
    s.take_profit,
    s.risk_reward_ratio,
    s.confidence,
    s.strength,
    s.pattern_detected,
    s.valid_until,
    s.generated_at
FROM trading.signals s
LEFT JOIN trading.bots b ON s.bot_id = b.id
WHERE s.status = 'active'
  AND (s.valid_until IS NULL OR s.valid_until > NOW())
ORDER BY s.confidence DESC, s.generated_at DESC;

-- Vista de rendimiento de senales por simbolo
CREATE OR REPLACE VIEW trading.v_signal_performance AS
SELECT
    symbol,
    COUNT(*) AS total_signals,
    COUNT(*) FILTER (WHERE result = 'win') AS wins,
    COUNT(*) FILTER (WHERE result = 'loss') AS losses,
    ROUND(AVG(confidence)::NUMERIC, 2) AS avg_confidence,
    ROUND(AVG(risk_reward_ratio)::NUMERIC, 2) AS avg_rr,
    ROUND((COUNT(*) FILTER (WHERE result = 'win')::DECIMAL / NULLIF(COUNT(*) FILTER (WHERE result IN ('win', 'loss')), 0) * 100)::NUMERIC, 2) AS win_rate
FROM trading.signals
WHERE status IN ('executed', 'expired')
  AND result IS NOT NULL
GROUP BY symbol
ORDER BY win_rate DESC NULLS LAST;

-- RLS Policy para multi-tenancy
ALTER TABLE trading.signals ENABLE ROW LEVEL SECURITY;

CREATE POLICY signals_tenant_isolation ON trading.signals
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

-- Grants
GRANT SELECT, INSERT, UPDATE ON trading.signals TO trading_app;
GRANT SELECT ON trading.signals TO trading_readonly;
GRANT SELECT ON trading.v_active_signals TO trading_app;
GRANT SELECT ON trading.v_signal_performance TO trading_app;
GRANT EXECUTE ON FUNCTION trading.expire_old_signals TO trading_app;
