trading-platform-database-v2/ddl/schemas/ml/003_range_predictions.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

296 lines
10 KiB
PL/PgSQL

-- ============================================================================
-- SCHEMA: ml
-- TABLE: range_predictions
-- DESCRIPTION: Predicciones de rango diario/semanal (High/Low esperados)
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 4 - DDL Implementation Roadmap Q1-2026
-- NOTE: Tabla particionada por fecha para alto volumen
-- ============================================================================
-- Enum para tipo de rango
DO $$ BEGIN
CREATE TYPE ml.range_type AS ENUM (
'daily', -- Rango diario
'weekly', -- Rango semanal
'session_asia', -- Sesion asiatica
'session_london', -- Sesion Londres
'session_ny', -- Sesion NY
'kill_zone', -- Kill zones ICT
'custom' -- Personalizado
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Enum para resultado de rango
DO $$ BEGIN
CREATE TYPE ml.range_result AS ENUM (
'pending', -- Pendiente de validacion
'hit_high', -- Alcanzo high predicho
'hit_low', -- Alcanzo low predicho
'hit_both', -- Alcanzo ambos
'missed', -- No alcanzo ninguno
'partial_high', -- Parcialmente hacia high
'partial_low', -- Parcialmente hacia low
'invalidated' -- Prediccion invalidada
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Tabla de Predicciones de Rango (particionada)
CREATE TABLE IF NOT EXISTS ml.range_predictions (
-- Identificadores
id UUID DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
-- Simbolo y timeframe
symbol VARCHAR(20) NOT NULL,
asset_class ml.asset_class NOT NULL DEFAULT 'FOREX',
range_type ml.range_type NOT NULL DEFAULT 'daily',
-- Fecha del rango
prediction_date DATE NOT NULL, -- Fecha de la prediccion
valid_from TIMESTAMPTZ NOT NULL, -- Inicio de validez
valid_until TIMESTAMPTZ NOT NULL, -- Fin de validez
-- Prediccion de rango
predicted_high DECIMAL(15, 8) NOT NULL, -- High esperado
predicted_low DECIMAL(15, 8) NOT NULL, -- Low esperado
predicted_open DECIMAL(15, 8), -- Open esperado
predicted_close DECIMAL(15, 8), -- Close esperado
-- Rangos de confianza
high_confidence_upper DECIMAL(15, 8), -- High upper bound
high_confidence_lower DECIMAL(15, 8), -- High lower bound
low_confidence_upper DECIMAL(15, 8), -- Low upper bound
low_confidence_lower DECIMAL(15, 8), -- Low lower bound
-- Pips esperados
expected_range_pips DECIMAL(10, 2), -- |High - Low|
expected_atr_pips DECIMAL(10, 2), -- ATR esperado
-- Niveles clave
pivot_point DECIMAL(15, 8),
resistance_1 DECIMAL(15, 8),
resistance_2 DECIMAL(15, 8),
support_1 DECIMAL(15, 8),
support_2 DECIMAL(15, 8),
-- Confianza del modelo
confidence_score DECIMAL(5, 4) NOT NULL DEFAULT 0.5
CHECK (confidence_score BETWEEN 0 AND 1),
model_version VARCHAR(50) NOT NULL,
model_id VARCHAR(100),
-- Features usadas
features_used JSONB DEFAULT '{}'::JSONB, -- Features del modelo
market_context JSONB DEFAULT '{}'::JSONB, -- Contexto de mercado
-- Resultado actual
result ml.range_result NOT NULL DEFAULT 'pending',
actual_high DECIMAL(15, 8),
actual_low DECIMAL(15, 8),
actual_open DECIMAL(15, 8),
actual_close DECIMAL(15, 8),
-- Metricas de precision
high_error_pips DECIMAL(10, 2), -- Error en prediccion de high
low_error_pips DECIMAL(10, 2), -- Error en prediccion de low
range_error_percent DECIMAL(5, 2), -- Error porcentual en rango
-- Timing
time_to_high_minutes INTEGER, -- Tiempo hasta alcanzar high
time_to_low_minutes INTEGER, -- Tiempo hasta alcanzar low
high_hit_first BOOLEAN, -- Si high se alcanzo primero
-- Estadisticas
max_drawdown_pips DECIMAL(10, 2), -- Max drawdown desde open
volatility_actual DECIMAL(10, 4), -- Volatilidad real
-- Metadata
notes TEXT,
metadata JSONB DEFAULT '{}'::JSONB,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
validated_at TIMESTAMPTZ,
-- Primary Key incluye columna de particion
PRIMARY KEY (id, prediction_date),
-- Constraints
CONSTRAINT range_pred_high_gt_low CHECK (predicted_high > predicted_low)
) PARTITION BY RANGE (prediction_date);
COMMENT ON TABLE ml.range_predictions IS
'Predicciones de rango (high/low) diario/semanal. Particionada por fecha para alto volumen';
-- Crear particiones para 2026
CREATE TABLE IF NOT EXISTS ml.range_predictions_2026_q1
PARTITION OF ml.range_predictions
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE IF NOT EXISTS ml.range_predictions_2026_q2
PARTITION OF ml.range_predictions
FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');
CREATE TABLE IF NOT EXISTS ml.range_predictions_2026_q3
PARTITION OF ml.range_predictions
FOR VALUES FROM ('2026-07-01') TO ('2026-10-01');
CREATE TABLE IF NOT EXISTS ml.range_predictions_2026_q4
PARTITION OF ml.range_predictions
FOR VALUES FROM ('2026-10-01') TO ('2027-01-01');
-- Indices en la tabla padre (se propagan a particiones)
CREATE INDEX IF NOT EXISTS idx_range_pred_tenant
ON ml.range_predictions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_range_pred_symbol
ON ml.range_predictions(symbol, prediction_date DESC);
CREATE INDEX IF NOT EXISTS idx_range_pred_date
ON ml.range_predictions(prediction_date DESC);
CREATE INDEX IF NOT EXISTS idx_range_pred_type
ON ml.range_predictions(range_type, prediction_date DESC);
CREATE INDEX IF NOT EXISTS idx_range_pred_pending
ON ml.range_predictions(result, valid_until)
WHERE result = 'pending';
CREATE INDEX IF NOT EXISTS idx_range_pred_model
ON ml.range_predictions(model_version, prediction_date DESC);
-- Funcion de timestamp (si no existe ya)
CREATE OR REPLACE FUNCTION ml.update_ml_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at := NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger para updated_at
DROP TRIGGER IF EXISTS range_pred_updated_at ON ml.range_predictions;
CREATE TRIGGER range_pred_updated_at
BEFORE UPDATE ON ml.range_predictions
FOR EACH ROW
EXECUTE FUNCTION ml.update_ml_timestamp();
-- Funcion para validar prediccion de rango
CREATE OR REPLACE FUNCTION ml.validate_range_prediction(
p_prediction_id UUID,
p_prediction_date DATE,
p_actual_high DECIMAL,
p_actual_low DECIMAL,
p_actual_open DECIMAL DEFAULT NULL,
p_actual_close DECIMAL DEFAULT NULL
)
RETURNS ml.range_result AS $$
DECLARE
v_pred ml.range_predictions;
v_result ml.range_result;
v_tolerance DECIMAL := 0.0005; -- 5 pips tolerance
BEGIN
SELECT * INTO v_pred
FROM ml.range_predictions
WHERE id = p_prediction_id AND prediction_date = p_prediction_date;
IF NOT FOUND THEN
RETURN NULL;
END IF;
-- Determinar resultado
IF p_actual_high >= v_pred.predicted_high * (1 - v_tolerance)
AND p_actual_low <= v_pred.predicted_low * (1 + v_tolerance) THEN
v_result := 'hit_both';
ELSIF p_actual_high >= v_pred.predicted_high * (1 - v_tolerance) THEN
v_result := 'hit_high';
ELSIF p_actual_low <= v_pred.predicted_low * (1 + v_tolerance) THEN
v_result := 'hit_low';
ELSIF p_actual_high >= v_pred.predicted_high * 0.9 THEN
v_result := 'partial_high';
ELSIF p_actual_low <= v_pred.predicted_low * 1.1 THEN
v_result := 'partial_low';
ELSE
v_result := 'missed';
END IF;
-- Actualizar prediccion
UPDATE ml.range_predictions
SET result = v_result,
actual_high = p_actual_high,
actual_low = p_actual_low,
actual_open = p_actual_open,
actual_close = p_actual_close,
high_error_pips = ABS(p_actual_high - predicted_high) * 10000,
low_error_pips = ABS(p_actual_low - predicted_low) * 10000,
range_error_percent = ABS((p_actual_high - p_actual_low) - (predicted_high - predicted_low))
/ (predicted_high - predicted_low) * 100,
validated_at = NOW()
WHERE id = p_prediction_id AND prediction_date = p_prediction_date;
RETURN v_result;
END;
$$ LANGUAGE plpgsql;
-- Vista de rendimiento de predicciones de rango
CREATE OR REPLACE VIEW ml.v_range_prediction_performance AS
SELECT
symbol,
range_type,
model_version,
COUNT(*) AS total_predictions,
COUNT(*) FILTER (WHERE result IN ('hit_both', 'hit_high', 'hit_low')) AS successful,
COUNT(*) FILTER (WHERE result = 'missed') AS missed,
ROUND((COUNT(*) FILTER (WHERE result IN ('hit_both', 'hit_high', 'hit_low'))::DECIMAL
/ NULLIF(COUNT(*) FILTER (WHERE result != 'pending'), 0) * 100), 2) AS accuracy_percent,
ROUND(AVG(confidence_score)::NUMERIC, 4) AS avg_confidence,
ROUND(AVG(high_error_pips)::NUMERIC, 2) AS avg_high_error_pips,
ROUND(AVG(low_error_pips)::NUMERIC, 2) AS avg_low_error_pips,
MAX(prediction_date) AS last_prediction_date
FROM ml.range_predictions
WHERE result != 'pending'
GROUP BY symbol, range_type, model_version
ORDER BY accuracy_percent DESC NULLS LAST;
-- Vista de predicciones activas
CREATE OR REPLACE VIEW ml.v_active_range_predictions AS
SELECT
id,
tenant_id,
symbol,
range_type,
prediction_date,
predicted_high,
predicted_low,
expected_range_pips,
pivot_point,
resistance_1,
support_1,
confidence_score,
model_version,
valid_until
FROM ml.range_predictions
WHERE result = 'pending'
AND valid_until > NOW()
ORDER BY prediction_date DESC, confidence_score DESC;
-- RLS Policies
ALTER TABLE ml.range_predictions ENABLE ROW LEVEL SECURITY;
CREATE POLICY range_pred_tenant ON ml.range_predictions
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
-- Grants
GRANT SELECT, INSERT, UPDATE ON ml.range_predictions TO trading_app;
GRANT SELECT ON ml.range_predictions TO trading_readonly;
GRANT SELECT ON ml.v_range_prediction_performance TO trading_app;
GRANT SELECT ON ml.v_active_range_predictions TO trading_app;
GRANT EXECUTE ON FUNCTION ml.validate_range_prediction TO trading_app;