-- ================================================================================================
-- Migration 043: Add Outcome Tracking Fields for Paper Trading
-- Adds actual_outcome, closed_at, and entry_price fields to ensemble_predictions
-- ================================================================================================

-- Add outcome tracking fields to ensemble_predictions table
ALTER TABLE ensemble_predictions
ADD COLUMN IF NOT EXISTS actual_outcome VARCHAR(10),  -- Actual trade outcome: WIN, LOSS, BREAKEVEN
ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ,       -- When the position was closed
ADD COLUMN IF NOT EXISTS entry_price BIGINT;          -- Entry price (in cents, same as executed_price)

-- Add check constraint for actual_outcome (conditional add to support re-runs)
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint
        WHERE conname = 'chk_actual_outcome'
        AND conrelid = 'ensemble_predictions'::regclass
    ) THEN
        ALTER TABLE ensemble_predictions
        ADD CONSTRAINT chk_actual_outcome
        CHECK (actual_outcome IS NULL OR actual_outcome IN ('WIN', 'LOSS', 'BREAKEVEN'));
    END IF;
END $$;

-- Add index for performance queries
CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_outcome
ON ensemble_predictions (actual_outcome, closed_at DESC)
WHERE actual_outcome IS NOT NULL;

-- Add index for open positions (not yet closed)
CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_open_positions
ON ensemble_predictions (symbol, prediction_timestamp DESC)
WHERE order_id IS NOT NULL AND closed_at IS NULL;

-- Add index for P&L queries with outcome
CREATE INDEX IF NOT EXISTS idx_ensemble_predictions_pnl_outcome
ON ensemble_predictions (symbol, actual_outcome, pnl DESC NULLS LAST)
WHERE pnl IS NOT NULL AND actual_outcome IS NOT NULL;

COMMENT ON COLUMN ensemble_predictions.actual_outcome IS 'Actual trade outcome after position close: WIN (pnl > 0), LOSS (pnl < 0), BREAKEVEN (pnl = 0)';
COMMENT ON COLUMN ensemble_predictions.closed_at IS 'Timestamp when position was closed and P&L realized';
COMMENT ON COLUMN ensemble_predictions.entry_price IS 'Actual entry price when order was filled (in cents, same unit as executed_price)';

-- ================================================================================================
-- Function: Update Model Performance Metrics (Trigger-Based)
-- Recalculates Sharpe ratio, win rate, and drawdown after each trade outcome
-- ================================================================================================
CREATE OR REPLACE FUNCTION update_model_performance_metrics()
RETURNS TRIGGER AS $$
DECLARE
    v_model_ids VARCHAR[] := ARRAY['DQN', 'PPO', 'MAMBA2', 'TFT'];
    v_model_id VARCHAR(50);
    v_window_hours INTEGER[] := ARRAY[1, 24, 168]; -- 1h, 24h, 1 week
    v_window INTEGER;
    v_total_predictions INTEGER;
    v_correct_predictions INTEGER;
    v_total_pnl BIGINT;
    v_total_trades INTEGER;
    v_winning_trades INTEGER;
    v_avg_pnl DOUBLE PRECISION;
    v_stddev_pnl DOUBLE PRECISION;
    v_sharpe_ratio DOUBLE PRECISION;
    v_win_rate DOUBLE PRECISION;
BEGIN
    -- Only recalculate if outcome was just recorded
    IF (TG_OP = 'UPDATE' AND NEW.actual_outcome IS NOT NULL AND OLD.actual_outcome IS NULL) THEN

        -- Loop through each model
        FOREACH v_model_id IN ARRAY v_model_ids
        LOOP
            -- Loop through each window
            FOREACH v_window IN ARRAY v_window_hours
            LOOP
                -- Calculate metrics for this model and window
                SELECT
                    COUNT(*) AS total_predictions,
                    COUNT(CASE WHEN actual_outcome = 'WIN' THEN 1 END) AS correct_predictions,
                    COALESCE(SUM(pnl), 0) AS total_pnl,
                    COUNT(CASE WHEN actual_outcome IN ('WIN', 'LOSS', 'BREAKEVEN') THEN 1 END) AS total_trades,
                    COUNT(CASE WHEN actual_outcome = 'WIN' THEN 1 END) AS winning_trades,
                    AVG(pnl) AS avg_pnl,
                    STDDEV(pnl) AS stddev_pnl
                INTO
                    v_total_predictions, v_correct_predictions, v_total_pnl,
                    v_total_trades, v_winning_trades, v_avg_pnl, v_stddev_pnl
                FROM ensemble_predictions
                WHERE
                    prediction_timestamp >= NOW() - (v_window || ' hours')::INTERVAL
                    AND symbol = NEW.symbol
                    AND actual_outcome IS NOT NULL
                    AND (
                        (v_model_id = 'DQN' AND dqn_vote IS NOT NULL) OR
                        (v_model_id = 'PPO' AND ppo_vote IS NOT NULL) OR
                        (v_model_id = 'MAMBA2' AND mamba2_vote IS NOT NULL) OR
                        (v_model_id = 'TFT' AND tft_vote IS NOT NULL)
                    );

                -- Calculate Sharpe ratio (annualized)
                IF v_stddev_pnl IS NOT NULL AND v_stddev_pnl > 0 THEN
                    v_sharpe_ratio := (v_avg_pnl / v_stddev_pnl) * SQRT(252);
                ELSE
                    v_sharpe_ratio := NULL;
                END IF;

                -- Calculate win rate
                IF v_total_trades > 0 THEN
                    v_win_rate := v_winning_trades::DOUBLE PRECISION / v_total_trades;
                ELSE
                    v_win_rate := 0.0;
                END IF;

                -- Upsert into model_performance_attribution
                INSERT INTO model_performance_attribution (
                    model_id, symbol, window_hours,
                    total_predictions, correct_predictions, accuracy,
                    total_pnl, total_trades, winning_trades,
                    sharpe_ratio, win_rate,
                    prediction_timestamp
                )
                VALUES (
                    v_model_id, NEW.symbol, v_window,
                    v_total_predictions, v_correct_predictions,
                    CASE WHEN v_total_predictions > 0 THEN v_correct_predictions::DOUBLE PRECISION / v_total_predictions ELSE 0.0 END,
                    v_total_pnl, v_total_trades, v_winning_trades,
                    v_sharpe_ratio, v_win_rate,
                    NOW()
                )
                ON CONFLICT (id, prediction_timestamp) DO NOTHING;

            END LOOP;
        END LOOP;

    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION update_model_performance_metrics() IS 'Automatically recalculate model performance metrics when trade outcomes are recorded';

-- Create trigger to automatically update metrics
DROP TRIGGER IF EXISTS trg_update_model_performance ON ensemble_predictions;
CREATE TRIGGER trg_update_model_performance
    AFTER UPDATE ON ensemble_predictions
    FOR EACH ROW
    WHEN (NEW.actual_outcome IS NOT NULL AND OLD.actual_outcome IS NULL)
    EXECUTE FUNCTION update_model_performance_metrics();

COMMENT ON TRIGGER trg_update_model_performance ON ensemble_predictions IS 'Automatically recalculate Sharpe ratio, win rate after each trade outcome';

-- ================================================================================================
-- Function: Get Real-time Performance Metrics
-- Query function for TLI to display current model performance
-- ================================================================================================
CREATE OR REPLACE FUNCTION get_real_performance_metrics(
    p_symbol VARCHAR(20) DEFAULT NULL,
    p_window_hours INTEGER DEFAULT 24
)
RETURNS TABLE (
    model_id VARCHAR(50),
    accuracy DOUBLE PRECISION,
    sharpe_ratio DOUBLE PRECISION,
    win_rate DOUBLE PRECISION,
    total_pnl BIGINT,
    total_trades INTEGER,
    avg_confidence DOUBLE PRECISION
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        mpa.model_id,
        mpa.accuracy,
        mpa.sharpe_ratio,
        mpa.win_rate,
        mpa.total_pnl,
        mpa.total_trades,
        mpa.avg_confidence
    FROM model_performance_attribution mpa
    WHERE
        mpa.window_hours = p_window_hours
        AND mpa.prediction_timestamp >= NOW() - (p_window_hours || ' hours')::INTERVAL
        AND (p_symbol IS NULL OR mpa.symbol = p_symbol)
    ORDER BY mpa.sharpe_ratio DESC NULLS LAST;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION get_real_performance_metrics IS 'Get real-time model performance metrics for TLI display (no mock data)';

-- Grant permissions
GRANT EXECUTE ON FUNCTION update_model_performance_metrics TO foxhunt;
GRANT EXECUTE ON FUNCTION get_real_performance_metrics TO foxhunt;

-- ================================================================================================
-- END MIGRATION 043
-- ================================================================================================
