- 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>
296 lines
10 KiB
PL/PgSQL
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;
|