[TASK-2026-02-03-ANALISIS-DDL-MODELADO] feat(ddl): FASE-1 Gaps Críticos P0
ST-1.1: financial.refunds - Already exists with approval flow ST-1.2: education.instructors - Created with GIN indexes ST-1.3: trading.price_alerts - FK exists, idempotent migration added ST-1.4: ml.prediction_overlays - New table + overlay columns New files: - ddl/schemas/education/tables/17-instructors.sql - ddl/schemas/ml/tables/12-prediction_overlays.sql - migrations/2026-02-03_add_predictions_overlay.sql - migrations/2026-02-03_add_price_alerts_symbol_fk.sql Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e4d39b1293
commit
c651fe5a30
17
ddl/schemas/education/migrations/001-add-courses-tags.sql
Normal file
17
ddl/schemas/education/migrations/001-add-courses-tags.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- =====================================================
|
||||
-- MIGRATION: Add tags column to courses
|
||||
-- =====================================================
|
||||
-- Gap: GAP-DDL-002
|
||||
-- Created: 2026-02-03
|
||||
-- =====================================================
|
||||
|
||||
-- Add tags column
|
||||
ALTER TABLE education.courses
|
||||
ADD COLUMN IF NOT EXISTS tags VARCHAR[] DEFAULT '{}';
|
||||
|
||||
-- Create GIN index for array search
|
||||
CREATE INDEX IF NOT EXISTS idx_courses_tags
|
||||
ON education.courses USING GIN(tags);
|
||||
|
||||
-- Comment
|
||||
COMMENT ON COLUMN education.courses.tags IS 'Tags para busqueda y filtrado de cursos';
|
||||
83
ddl/schemas/education/tables/17-instructors.sql
Normal file
83
ddl/schemas/education/tables/17-instructors.sql
Normal file
@ -0,0 +1,83 @@
|
||||
-- =====================================================
|
||||
-- TABLE: education.instructors
|
||||
-- =====================================================
|
||||
-- Proyecto: OrbiQuant IA (Trading Platform)
|
||||
-- Modulo: OQI-002 - Education
|
||||
-- Purpose: Instructor profiles for educational content
|
||||
-- Related: OQI-002 Education Module
|
||||
-- Gap: GAP-DDL-001
|
||||
-- Created: 2026-02-03
|
||||
-- Updated: 2026-02-03
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS education.instructors (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
display_name VARCHAR(100) NOT NULL,
|
||||
bio TEXT,
|
||||
avatar_url VARCHAR(500),
|
||||
expertise TEXT[] DEFAULT '{}',
|
||||
social_links JSONB DEFAULT '{}',
|
||||
total_courses INTEGER NOT NULL DEFAULT 0,
|
||||
total_students INTEGER NOT NULL DEFAULT 0,
|
||||
total_reviews INTEGER NOT NULL DEFAULT 0,
|
||||
average_rating DECIMAL(3, 2),
|
||||
is_verified BOOLEAN NOT NULL DEFAULT false,
|
||||
verified_at TIMESTAMPTZ,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT instructors_rating_range CHECK (average_rating IS NULL OR (average_rating >= 0 AND average_rating <= 5)),
|
||||
CONSTRAINT instructors_counts_positive CHECK (total_courses >= 0 AND total_students >= 0 AND total_reviews >= 0)
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_instructors_user_id ON education.instructors(user_id);
|
||||
CREATE INDEX idx_instructors_is_verified ON education.instructors(is_verified) WHERE is_verified = true;
|
||||
CREATE INDEX idx_instructors_is_active ON education.instructors(is_active) WHERE is_active = true;
|
||||
CREATE INDEX idx_instructors_average_rating ON education.instructors(average_rating DESC NULLS LAST);
|
||||
CREATE INDEX idx_instructors_expertise ON education.instructors USING GIN(expertise);
|
||||
|
||||
-- Trigger for updated_at
|
||||
CREATE TRIGGER trg_instructors_updated_at
|
||||
BEFORE UPDATE ON education.instructors
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION education.update_updated_at();
|
||||
|
||||
-- Comments
|
||||
COMMENT ON TABLE education.instructors IS 'Instructor profiles linked to auth.users';
|
||||
COMMENT ON COLUMN education.instructors.user_id IS 'Reference to auth.users - one user can only be one instructor';
|
||||
COMMENT ON COLUMN education.instructors.display_name IS 'Public display name for the instructor';
|
||||
COMMENT ON COLUMN education.instructors.expertise IS 'Array of expertise areas (e.g., trading, forex, crypto)';
|
||||
COMMENT ON COLUMN education.instructors.social_links IS 'JSON with social media links (twitter, linkedin, youtube)';
|
||||
COMMENT ON COLUMN education.instructors.total_courses IS 'Denormalized count of active courses';
|
||||
COMMENT ON COLUMN education.instructors.total_students IS 'Denormalized count of unique students';
|
||||
COMMENT ON COLUMN education.instructors.total_reviews IS 'Denormalized count of course reviews';
|
||||
COMMENT ON COLUMN education.instructors.average_rating IS 'Calculated average rating from course reviews';
|
||||
COMMENT ON COLUMN education.instructors.metadata IS 'Additional metadata in JSON format';
|
||||
|
||||
-- =====================================================
|
||||
-- MIGRATION NOTE: education.courses.instructor_id
|
||||
-- =====================================================
|
||||
-- The current education.courses table has instructor_id
|
||||
-- referencing auth.users(id). A migration is needed to:
|
||||
--
|
||||
-- 1. Ensure instructors exist for current course instructors:
|
||||
-- INSERT INTO education.instructors (user_id, display_name)
|
||||
-- SELECT DISTINCT instructor_id, instructor_name
|
||||
-- FROM education.courses
|
||||
-- WHERE instructor_id NOT IN (SELECT user_id FROM education.instructors);
|
||||
--
|
||||
-- 2. Change the FK reference (requires careful migration):
|
||||
-- ALTER TABLE education.courses
|
||||
-- DROP CONSTRAINT courses_instructor_id_fkey;
|
||||
--
|
||||
-- ALTER TABLE education.courses
|
||||
-- ADD CONSTRAINT courses_instructor_id_fkey
|
||||
-- FOREIGN KEY (instructor_id) REFERENCES education.instructors(id);
|
||||
--
|
||||
-- NOTE: This migration should be created in migrations/ folder
|
||||
-- when the team decides to implement the full instructor system.
|
||||
-- =====================================================
|
||||
74
ddl/schemas/financial/tables/11-refunds.sql
Normal file
74
ddl/schemas/financial/tables/11-refunds.sql
Normal file
@ -0,0 +1,74 @@
|
||||
-- =====================================================
|
||||
-- ORBIQUANT IA - REFUNDS TABLE
|
||||
-- =====================================================
|
||||
-- Description: Registro de reembolsos para compliance
|
||||
-- Schema: financial
|
||||
-- Gap: GAP-DDL-004
|
||||
-- Created: 2026-02-03
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE financial.refunds (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Relacion con pago original
|
||||
payment_id UUID NOT NULL REFERENCES financial.payments(id) ON DELETE RESTRICT,
|
||||
|
||||
-- Monto y moneda
|
||||
amount DECIMAL(20,8) NOT NULL CHECK (amount > 0),
|
||||
currency financial.currency_code NOT NULL,
|
||||
|
||||
-- Razon y metadata
|
||||
reason VARCHAR(255) NOT NULL,
|
||||
reason_code VARCHAR(50), -- e.g., 'duplicate', 'fraudulent', 'requested_by_customer'
|
||||
notes TEXT,
|
||||
|
||||
-- Estado
|
||||
status financial.payment_status NOT NULL DEFAULT 'pending',
|
||||
|
||||
-- Stripe integration
|
||||
stripe_refund_id VARCHAR(255) UNIQUE,
|
||||
stripe_failure_reason TEXT,
|
||||
|
||||
-- Aprobacion
|
||||
requested_by UUID REFERENCES auth.users(id),
|
||||
approved_by UUID REFERENCES auth.users(id),
|
||||
approved_at TIMESTAMPTZ,
|
||||
rejected_at TIMESTAMPTZ,
|
||||
rejection_reason TEXT,
|
||||
|
||||
-- Metadata
|
||||
metadata JSONB DEFAULT '{}',
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
processed_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
failed_at TIMESTAMPTZ,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT refund_approval_valid CHECK (
|
||||
(status = 'succeeded' AND approved_by IS NOT NULL AND approved_at IS NOT NULL) OR
|
||||
(status != 'succeeded')
|
||||
),
|
||||
CONSTRAINT refund_failure_valid CHECK (
|
||||
(status = 'failed' AND failed_at IS NOT NULL) OR
|
||||
(status != 'failed')
|
||||
)
|
||||
);
|
||||
|
||||
-- Indices
|
||||
CREATE INDEX idx_refunds_payment ON financial.refunds(payment_id);
|
||||
CREATE INDEX idx_refunds_status ON financial.refunds(status);
|
||||
CREATE INDEX idx_refunds_stripe ON financial.refunds(stripe_refund_id)
|
||||
WHERE stripe_refund_id IS NOT NULL;
|
||||
CREATE INDEX idx_refunds_created ON financial.refunds(created_at DESC);
|
||||
CREATE INDEX idx_refunds_requested_by ON financial.refunds(requested_by)
|
||||
WHERE requested_by IS NOT NULL;
|
||||
|
||||
-- Comentarios
|
||||
COMMENT ON TABLE financial.refunds IS 'Registro de reembolsos para compliance y tracking';
|
||||
COMMENT ON COLUMN financial.refunds.payment_id IS 'Pago original que se esta reembolsando';
|
||||
COMMENT ON COLUMN financial.refunds.reason_code IS 'Codigo de razon: duplicate, fraudulent, requested_by_customer, etc.';
|
||||
COMMENT ON COLUMN financial.refunds.stripe_refund_id IS 'ID del refund en Stripe';
|
||||
COMMENT ON COLUMN financial.refunds.requested_by IS 'Usuario que solicito el reembolso';
|
||||
COMMENT ON COLUMN financial.refunds.approved_by IS 'Admin que aprobo el reembolso';
|
||||
75
ddl/schemas/investment/tables/10-agent_executions.sql
Normal file
75
ddl/schemas/investment/tables/10-agent_executions.sql
Normal file
@ -0,0 +1,75 @@
|
||||
-- =====================================================
|
||||
-- INVESTMENT SCHEMA - AGENT EXECUTIONS TABLE
|
||||
-- =====================================================
|
||||
-- Description: Tracking de ejecuciones de trading agents
|
||||
-- Schema: investment
|
||||
-- Gap: GAP-DDL-005
|
||||
-- Created: 2026-02-03
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE investment.agent_executions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Relacion con cuenta PAMM
|
||||
account_id UUID NOT NULL REFERENCES investment.accounts(id) ON DELETE CASCADE,
|
||||
|
||||
-- Tipo de agente (Atlas, Orion, Nova)
|
||||
agent_type investment.trading_agent NOT NULL,
|
||||
|
||||
-- Tipo de ejecucion
|
||||
execution_type VARCHAR(20) NOT NULL CHECK (
|
||||
execution_type IN ('trade', 'rebalance', 'distribution', 'stop_loss', 'take_profit', 'hedge')
|
||||
),
|
||||
|
||||
-- Detalles del trade (si aplica)
|
||||
symbol VARCHAR(20),
|
||||
side VARCHAR(4) CHECK (side IN ('buy', 'sell')),
|
||||
quantity DECIMAL(20,8),
|
||||
entry_price DECIMAL(20,8),
|
||||
exit_price DECIMAL(20,8),
|
||||
|
||||
-- Resultado
|
||||
pnl DECIMAL(20,8),
|
||||
pnl_percentage DECIMAL(8,4),
|
||||
fees DECIMAL(20,8) DEFAULT 0,
|
||||
|
||||
-- Detalles adicionales
|
||||
trade_details JSONB DEFAULT '{}',
|
||||
market_conditions JSONB, -- volatility, trend, etc.
|
||||
|
||||
-- Estado
|
||||
status VARCHAR(20) DEFAULT 'executed' CHECK (
|
||||
status IN ('pending', 'executed', 'partially_filled', 'cancelled', 'failed')
|
||||
),
|
||||
failure_reason TEXT,
|
||||
|
||||
-- Metadata ML/AI
|
||||
signal_source VARCHAR(50), -- ml_model, llm, manual, scheduled
|
||||
confidence_score DECIMAL(5,4),
|
||||
model_version VARCHAR(50),
|
||||
|
||||
-- Timestamps
|
||||
executed_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indices
|
||||
CREATE INDEX idx_agent_exec_account ON investment.agent_executions(account_id);
|
||||
CREATE INDEX idx_agent_exec_type ON investment.agent_executions(agent_type);
|
||||
CREATE INDEX idx_agent_exec_date ON investment.agent_executions(executed_at DESC);
|
||||
CREATE INDEX idx_agent_exec_symbol ON investment.agent_executions(symbol)
|
||||
WHERE symbol IS NOT NULL;
|
||||
CREATE INDEX idx_agent_exec_pnl ON investment.agent_executions(pnl)
|
||||
WHERE pnl IS NOT NULL;
|
||||
CREATE INDEX idx_agent_exec_status ON investment.agent_executions(status);
|
||||
|
||||
-- Indice compuesto para reportes
|
||||
CREATE INDEX idx_agent_exec_account_date ON investment.agent_executions(account_id, executed_at DESC);
|
||||
|
||||
-- Comentarios
|
||||
COMMENT ON TABLE investment.agent_executions IS 'Tracking de ejecuciones de trading agents (Atlas, Orion, Nova)';
|
||||
COMMENT ON COLUMN investment.agent_executions.agent_type IS 'Agente que ejecuto: atlas (conservador), orion (moderado), nova (agresivo)';
|
||||
COMMENT ON COLUMN investment.agent_executions.execution_type IS 'Tipo: trade, rebalance, distribution, stop_loss, take_profit, hedge';
|
||||
COMMENT ON COLUMN investment.agent_executions.trade_details IS 'JSON con detalles adicionales del trade';
|
||||
COMMENT ON COLUMN investment.agent_executions.market_conditions IS 'Condiciones de mercado al momento de ejecucion';
|
||||
COMMENT ON COLUMN investment.agent_executions.signal_source IS 'Fuente de la senal: ml_model, llm, manual, scheduled';
|
||||
80
ddl/schemas/ml/migrations/001-add-predictions-overlay.sql
Normal file
80
ddl/schemas/ml/migrations/001-add-predictions-overlay.sql
Normal file
@ -0,0 +1,80 @@
|
||||
-- =====================================================
|
||||
-- MIGRATION: Add overlay columns to ml.predictions
|
||||
-- =====================================================
|
||||
-- Gap: GAP-DDL-006
|
||||
-- Task: TASK-2026-02-03-ANALISIS-DDL-MODELADO / ST-1.4
|
||||
-- Created: 2026-02-03
|
||||
-- Updated: 2026-02-03
|
||||
-- Description: Complete overlay structure for ML predictions visualization on trading charts
|
||||
-- =====================================================
|
||||
|
||||
-- ===========================================
|
||||
-- 1. ADD OVERLAY COLUMNS
|
||||
-- ===========================================
|
||||
|
||||
-- Add overlay visualization data
|
||||
ALTER TABLE ml.predictions
|
||||
ADD COLUMN IF NOT EXISTS overlay_data JSONB DEFAULT '{}';
|
||||
|
||||
-- Add chart configuration
|
||||
ALTER TABLE ml.predictions
|
||||
ADD COLUMN IF NOT EXISTS chart_config JSONB DEFAULT '{
|
||||
"show_on_chart": true,
|
||||
"color": "#4CAF50",
|
||||
"line_style": "dashed",
|
||||
"opacity": 0.8
|
||||
}';
|
||||
|
||||
-- Add display priority for ordering multiple predictions
|
||||
ALTER TABLE ml.predictions
|
||||
ADD COLUMN IF NOT EXISTS display_priority INTEGER DEFAULT 0;
|
||||
|
||||
-- ===========================================
|
||||
-- 2. CREATE INDEXES FOR OVERLAY QUERIES
|
||||
-- ===========================================
|
||||
|
||||
-- Index for overlay queries - predictions visible on chart ordered by priority
|
||||
CREATE INDEX IF NOT EXISTS idx_predictions_overlay
|
||||
ON ml.predictions(symbol, timeframe, display_priority DESC)
|
||||
WHERE (chart_config->>'show_on_chart')::boolean = true;
|
||||
|
||||
-- Index for fetching overlay data efficiently
|
||||
CREATE INDEX IF NOT EXISTS idx_predictions_overlay_data
|
||||
ON ml.predictions USING GIN (overlay_data)
|
||||
WHERE overlay_data IS NOT NULL AND overlay_data != '{}';
|
||||
|
||||
-- ===========================================
|
||||
-- 3. COLUMN COMMENTS
|
||||
-- ===========================================
|
||||
|
||||
COMMENT ON COLUMN ml.predictions.overlay_data IS
|
||||
'JSON data for chart overlay visualization. Example:
|
||||
{
|
||||
"price_levels": [1850.00, 1865.50, 1880.25],
|
||||
"zones": [
|
||||
{"type": "support", "low": 1845.00, "high": 1850.00, "color": "#4CAF50"},
|
||||
{"type": "resistance", "low": 1875.00, "high": 1880.00, "color": "#F44336"}
|
||||
],
|
||||
"trend_lines": [
|
||||
{"start": {"time": "2026-02-01T10:00:00Z", "price": 1840.00}, "end": {"time": "2026-02-03T10:00:00Z", "price": 1870.00}}
|
||||
],
|
||||
"annotations": [
|
||||
{"time": "2026-02-02T14:00:00Z", "text": "Breakout signal", "position": "above"}
|
||||
]
|
||||
}';
|
||||
|
||||
COMMENT ON COLUMN ml.predictions.chart_config IS
|
||||
'Configuration for how to display this prediction on charts. Example:
|
||||
{
|
||||
"show_on_chart": true,
|
||||
"color": "#4CAF50",
|
||||
"line_style": "dashed",
|
||||
"opacity": 0.8,
|
||||
"label_visible": true,
|
||||
"z_index": 10
|
||||
}';
|
||||
|
||||
COMMENT ON COLUMN ml.predictions.display_priority IS
|
||||
'Priority for ordering when multiple predictions exist (higher = more prominent).
|
||||
Default: 0, Range: -100 to 100.
|
||||
Use negative values for background overlays, positive for foreground.';
|
||||
208
ddl/schemas/ml/tables/12-prediction_overlays.sql
Normal file
208
ddl/schemas/ml/tables/12-prediction_overlays.sql
Normal file
@ -0,0 +1,208 @@
|
||||
-- =====================================================
|
||||
-- ML SCHEMA - PREDICTION OVERLAYS TABLE
|
||||
-- =====================================================
|
||||
-- Description: Complex overlay configurations for chart visualization
|
||||
-- Schema: ml
|
||||
-- Author: Database Agent
|
||||
-- Date: 2026-02-03
|
||||
-- Task: TASK-2026-02-03-ANALISIS-DDL-MODELADO / ST-1.4
|
||||
-- Module: OQI-006-senales-ml
|
||||
-- =====================================================
|
||||
|
||||
-- ===========================================
|
||||
-- ENUM: Overlay types for chart visualization
|
||||
-- ===========================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'overlay_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'ml')) THEN
|
||||
CREATE TYPE ml.overlay_type AS ENUM (
|
||||
'support_resistance', -- Horizontal support/resistance levels
|
||||
'trend_line', -- Diagonal trend lines
|
||||
'zone', -- Price zones (supply/demand, liquidity)
|
||||
'arrow', -- Direction arrows on chart
|
||||
'label', -- Text annotations
|
||||
'fibonacci', -- Fibonacci retracements/extensions
|
||||
'order_block', -- ICT Order Blocks
|
||||
'fair_value_gap', -- FVG/Imbalances
|
||||
'liquidity_level', -- Liquidity pools
|
||||
'ict_killzone' -- ICT Killzone time highlighting
|
||||
);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ===========================================
|
||||
-- TABLE: ml.prediction_overlays
|
||||
-- ===========================================
|
||||
CREATE TABLE IF NOT EXISTS ml.prediction_overlays (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Reference to parent prediction
|
||||
prediction_id UUID NOT NULL REFERENCES ml.predictions(id) ON DELETE CASCADE,
|
||||
|
||||
-- Overlay type classification
|
||||
overlay_type ml.overlay_type NOT NULL,
|
||||
|
||||
-- Label for display
|
||||
label VARCHAR(100),
|
||||
|
||||
-- Price levels (for support/resistance, zones, etc.)
|
||||
price_levels DECIMAL(18,8)[] DEFAULT '{}',
|
||||
|
||||
-- Time range for time-bounded overlays
|
||||
time_range TSTZRANGE,
|
||||
|
||||
-- Specific time points (for arrows, labels)
|
||||
time_point TIMESTAMPTZ,
|
||||
|
||||
-- Price point (for single-point overlays)
|
||||
price_point DECIMAL(18,8),
|
||||
|
||||
-- Coordinates for complex shapes (trend lines, etc.)
|
||||
-- Format: [{"time": "ISO8601", "price": number}, ...]
|
||||
coordinates JSONB DEFAULT '[]',
|
||||
|
||||
-- Style configuration
|
||||
style_config JSONB DEFAULT '{
|
||||
"color": "#4CAF50",
|
||||
"line_width": 1,
|
||||
"line_style": "solid",
|
||||
"fill_opacity": 0.2,
|
||||
"text_color": "#FFFFFF",
|
||||
"font_size": 12
|
||||
}',
|
||||
|
||||
-- Additional metadata (e.g., Fibonacci levels, zone strength)
|
||||
metadata JSONB DEFAULT '{}',
|
||||
|
||||
-- Display control
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
display_priority INTEGER DEFAULT 0,
|
||||
z_index INTEGER DEFAULT 0,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- ===========================================
|
||||
-- INDEXES
|
||||
-- ===========================================
|
||||
|
||||
-- Primary lookup by prediction
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_prediction
|
||||
ON ml.prediction_overlays(prediction_id);
|
||||
|
||||
-- Active overlays only
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_active
|
||||
ON ml.prediction_overlays(prediction_id, is_active)
|
||||
WHERE is_active = true;
|
||||
|
||||
-- By type for filtering
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_type
|
||||
ON ml.prediction_overlays(overlay_type);
|
||||
|
||||
-- Time range queries (for time-bounded overlays)
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_time_range
|
||||
ON ml.prediction_overlays USING GIST (time_range)
|
||||
WHERE time_range IS NOT NULL;
|
||||
|
||||
-- Display ordering
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_priority
|
||||
ON ml.prediction_overlays(display_priority DESC, z_index DESC)
|
||||
WHERE is_active = true;
|
||||
|
||||
-- Expiration cleanup
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_expires
|
||||
ON ml.prediction_overlays(expires_at)
|
||||
WHERE expires_at IS NOT NULL AND is_active = true;
|
||||
|
||||
-- GIN index for metadata queries
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_metadata
|
||||
ON ml.prediction_overlays USING GIN (metadata)
|
||||
WHERE metadata IS NOT NULL AND metadata != '{}';
|
||||
|
||||
-- ===========================================
|
||||
-- TRIGGERS
|
||||
-- ===========================================
|
||||
|
||||
-- Auto-update updated_at timestamp
|
||||
CREATE OR REPLACE FUNCTION ml.update_prediction_overlays_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trigger_prediction_overlays_updated_at ON ml.prediction_overlays;
|
||||
CREATE TRIGGER trigger_prediction_overlays_updated_at
|
||||
BEFORE UPDATE ON ml.prediction_overlays
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION ml.update_prediction_overlays_updated_at();
|
||||
|
||||
-- ===========================================
|
||||
-- COMMENTS
|
||||
-- ===========================================
|
||||
|
||||
COMMENT ON TABLE ml.prediction_overlays IS
|
||||
'Complex overlay configurations for chart visualization.
|
||||
Stores detailed overlay data that can be rendered on trading charts
|
||||
to visualize ML predictions, support/resistance levels, zones, and annotations.';
|
||||
|
||||
COMMENT ON COLUMN ml.prediction_overlays.id IS 'Unique identifier for the overlay';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.prediction_id IS 'Reference to parent ML prediction';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.overlay_type IS 'Type of overlay: support_resistance, trend_line, zone, arrow, label, fibonacci, order_block, fair_value_gap, liquidity_level, ict_killzone';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.label IS 'Optional text label to display on chart';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.price_levels IS 'Array of price levels for horizontal overlays';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.time_range IS 'Time range for time-bounded overlays (e.g., killzones)';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.time_point IS 'Specific timestamp for point-based overlays (arrows, labels)';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.price_point IS 'Specific price for point-based overlays';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.coordinates IS 'JSON array of {time, price} coordinates for complex shapes';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.style_config IS 'Visual styling: color, line_width, line_style, fill_opacity, etc.';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.metadata IS 'Additional overlay-specific data (e.g., Fibonacci ratios, zone strength)';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.is_active IS 'Whether overlay should be displayed';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.display_priority IS 'Ordering priority (higher = more prominent)';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.z_index IS 'Z-order for overlapping overlays';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.expires_at IS 'Optional expiration timestamp for auto-cleanup';
|
||||
|
||||
-- ===========================================
|
||||
-- EXAMPLE DATA STRUCTURES
|
||||
-- ===========================================
|
||||
|
||||
COMMENT ON COLUMN ml.prediction_overlays.coordinates IS
|
||||
'JSON array of coordinate points. Example for trend line:
|
||||
[
|
||||
{"time": "2026-02-01T10:00:00Z", "price": 1840.00},
|
||||
{"time": "2026-02-03T10:00:00Z", "price": 1870.00}
|
||||
]
|
||||
|
||||
Example for Fibonacci:
|
||||
[
|
||||
{"time": "2026-02-01T08:00:00Z", "price": 1800.00, "label": "0%"},
|
||||
{"time": "2026-02-02T14:00:00Z", "price": 1880.00, "label": "100%"}
|
||||
]';
|
||||
|
||||
COMMENT ON COLUMN ml.prediction_overlays.metadata IS
|
||||
'Additional overlay-specific metadata. Examples:
|
||||
|
||||
For Fibonacci:
|
||||
{
|
||||
"levels": [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1],
|
||||
"extension_levels": [1.272, 1.618, 2.0]
|
||||
}
|
||||
|
||||
For Order Block:
|
||||
{
|
||||
"ob_type": "bullish",
|
||||
"mitigated": false,
|
||||
"strength": 0.85
|
||||
}
|
||||
|
||||
For Fair Value Gap:
|
||||
{
|
||||
"fvg_type": "bullish",
|
||||
"filled_percent": 0.35,
|
||||
"high": 1875.50,
|
||||
"low": 1872.00
|
||||
}';
|
||||
62
ddl/schemas/trading/tables/11-price_alerts.sql
Normal file
62
ddl/schemas/trading/tables/11-price_alerts.sql
Normal file
@ -0,0 +1,62 @@
|
||||
-- ============================================================================
|
||||
-- Schema: trading
|
||||
-- Table: price_alerts
|
||||
-- Description: Alertas de precio configuradas por usuarios
|
||||
-- Gap: GAP-DDL-003 (RESOLVED - ST-1.3)
|
||||
-- Created: 2026-02-03
|
||||
-- Migration: migrations/2026-02-03_add_price_alerts_symbol_fk.sql
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE trading.price_alerts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Relaciones
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
symbol_id UUID NOT NULL REFERENCES trading.symbols(id) ON DELETE CASCADE,
|
||||
|
||||
-- Configuracion de alerta
|
||||
condition VARCHAR(10) NOT NULL CHECK (condition IN ('above', 'below', 'crosses')),
|
||||
target_price DECIMAL(20,8) NOT NULL CHECK (target_price > 0),
|
||||
|
||||
-- Precio de referencia al crear
|
||||
reference_price DECIMAL(20,8),
|
||||
|
||||
-- Estado
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
is_recurring BOOLEAN DEFAULT false,
|
||||
|
||||
-- Notificacion
|
||||
notification_type VARCHAR(20) DEFAULT 'push' CHECK (notification_type IN ('push', 'email', 'both')),
|
||||
notification_sent BOOLEAN DEFAULT false,
|
||||
|
||||
-- Ejecucion
|
||||
triggered_at TIMESTAMPTZ,
|
||||
triggered_price DECIMAL(20,8),
|
||||
trigger_count INTEGER DEFAULT 0,
|
||||
|
||||
-- Expiracion opcional
|
||||
expires_at TIMESTAMPTZ,
|
||||
|
||||
-- Metadata
|
||||
notes VARCHAR(255),
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indices
|
||||
CREATE INDEX idx_price_alerts_user ON trading.price_alerts(user_id);
|
||||
CREATE INDEX idx_price_alerts_symbol ON trading.price_alerts(symbol_id);
|
||||
CREATE INDEX idx_price_alerts_active ON trading.price_alerts(is_active, symbol_id)
|
||||
WHERE is_active = true;
|
||||
CREATE INDEX idx_price_alerts_expiry ON trading.price_alerts(expires_at)
|
||||
WHERE expires_at IS NOT NULL AND is_active = true;
|
||||
|
||||
-- Comentarios
|
||||
COMMENT ON TABLE trading.price_alerts IS 'Alertas de precio configuradas por usuarios';
|
||||
COMMENT ON COLUMN trading.price_alerts.condition IS 'Condicion: above (>=), below (<=), crosses';
|
||||
COMMENT ON COLUMN trading.price_alerts.target_price IS 'Precio objetivo para disparar alerta';
|
||||
COMMENT ON COLUMN trading.price_alerts.is_recurring IS 'Si es true, se reactiva despues de disparar';
|
||||
COMMENT ON COLUMN trading.price_alerts.notification_type IS 'Tipo de notificacion: push, email, both';
|
||||
COMMENT ON COLUMN trading.price_alerts.triggered_at IS 'Timestamp de ultima vez que se disparo';
|
||||
155
migrations/2026-02-03_add_predictions_overlay.sql
Normal file
155
migrations/2026-02-03_add_predictions_overlay.sql
Normal file
@ -0,0 +1,155 @@
|
||||
-- =====================================================
|
||||
-- MIGRATION: Add overlay columns to ml.predictions
|
||||
-- =====================================================
|
||||
-- Date: 2026-02-03
|
||||
-- Task: TASK-2026-02-03-ANALISIS-DDL-MODELADO / ST-1.4
|
||||
-- Description: Complete overlay structure for ML predictions visualization
|
||||
-- =====================================================
|
||||
|
||||
-- ===========================================
|
||||
-- PART 1: ADD COLUMNS TO ml.predictions
|
||||
-- ===========================================
|
||||
|
||||
-- Add overlay visualization data
|
||||
ALTER TABLE ml.predictions
|
||||
ADD COLUMN IF NOT EXISTS overlay_data JSONB DEFAULT '{}';
|
||||
|
||||
-- Add chart configuration
|
||||
ALTER TABLE ml.predictions
|
||||
ADD COLUMN IF NOT EXISTS chart_config JSONB DEFAULT '{
|
||||
"show_on_chart": true,
|
||||
"color": "#4CAF50",
|
||||
"line_style": "dashed",
|
||||
"opacity": 0.8
|
||||
}';
|
||||
|
||||
-- Add display priority for ordering multiple predictions
|
||||
ALTER TABLE ml.predictions
|
||||
ADD COLUMN IF NOT EXISTS display_priority INTEGER DEFAULT 0;
|
||||
|
||||
-- ===========================================
|
||||
-- PART 2: INDEXES FOR OVERLAY QUERIES
|
||||
-- ===========================================
|
||||
|
||||
-- Index for overlay queries
|
||||
CREATE INDEX IF NOT EXISTS idx_predictions_overlay
|
||||
ON ml.predictions(symbol, timeframe, display_priority DESC)
|
||||
WHERE (chart_config->>'show_on_chart')::boolean = true;
|
||||
|
||||
-- Index for overlay data
|
||||
CREATE INDEX IF NOT EXISTS idx_predictions_overlay_data
|
||||
ON ml.predictions USING GIN (overlay_data)
|
||||
WHERE overlay_data IS NOT NULL AND overlay_data != '{}';
|
||||
|
||||
-- ===========================================
|
||||
-- PART 3: COLUMN COMMENTS
|
||||
-- ===========================================
|
||||
|
||||
COMMENT ON COLUMN ml.predictions.overlay_data IS 'JSON data for chart overlay visualization (price levels, zones, etc)';
|
||||
COMMENT ON COLUMN ml.predictions.chart_config IS 'Configuration for how to display this prediction on charts';
|
||||
COMMENT ON COLUMN ml.predictions.display_priority IS 'Priority for ordering when multiple predictions exist (higher = more prominent)';
|
||||
|
||||
-- ===========================================
|
||||
-- PART 4: CREATE OVERLAY TYPE ENUM (if not exists)
|
||||
-- ===========================================
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'overlay_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'ml')) THEN
|
||||
CREATE TYPE ml.overlay_type AS ENUM (
|
||||
'support_resistance',
|
||||
'trend_line',
|
||||
'zone',
|
||||
'arrow',
|
||||
'label',
|
||||
'fibonacci',
|
||||
'order_block',
|
||||
'fair_value_gap',
|
||||
'liquidity_level',
|
||||
'ict_killzone'
|
||||
);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ===========================================
|
||||
-- PART 5: CREATE prediction_overlays TABLE
|
||||
-- ===========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ml.prediction_overlays (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
prediction_id UUID NOT NULL REFERENCES ml.predictions(id) ON DELETE CASCADE,
|
||||
overlay_type ml.overlay_type NOT NULL,
|
||||
label VARCHAR(100),
|
||||
price_levels DECIMAL(18,8)[] DEFAULT '{}',
|
||||
time_range TSTZRANGE,
|
||||
time_point TIMESTAMPTZ,
|
||||
price_point DECIMAL(18,8),
|
||||
coordinates JSONB DEFAULT '[]',
|
||||
style_config JSONB DEFAULT '{
|
||||
"color": "#4CAF50",
|
||||
"line_width": 1,
|
||||
"line_style": "solid",
|
||||
"fill_opacity": 0.2,
|
||||
"text_color": "#FFFFFF",
|
||||
"font_size": 12
|
||||
}',
|
||||
metadata JSONB DEFAULT '{}',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
display_priority INTEGER DEFAULT 0,
|
||||
z_index INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Indexes for prediction_overlays
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_prediction
|
||||
ON ml.prediction_overlays(prediction_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_active
|
||||
ON ml.prediction_overlays(prediction_id, is_active)
|
||||
WHERE is_active = true;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_type
|
||||
ON ml.prediction_overlays(overlay_type);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_time_range
|
||||
ON ml.prediction_overlays USING GIST (time_range)
|
||||
WHERE time_range IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_priority
|
||||
ON ml.prediction_overlays(display_priority DESC, z_index DESC)
|
||||
WHERE is_active = true;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_expires
|
||||
ON ml.prediction_overlays(expires_at)
|
||||
WHERE expires_at IS NOT NULL AND is_active = true;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_prediction_overlays_metadata
|
||||
ON ml.prediction_overlays USING GIN (metadata)
|
||||
WHERE metadata IS NOT NULL AND metadata != '{}';
|
||||
|
||||
-- Trigger for updated_at
|
||||
CREATE OR REPLACE FUNCTION ml.update_prediction_overlays_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trigger_prediction_overlays_updated_at ON ml.prediction_overlays;
|
||||
CREATE TRIGGER trigger_prediction_overlays_updated_at
|
||||
BEFORE UPDATE ON ml.prediction_overlays
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION ml.update_prediction_overlays_updated_at();
|
||||
|
||||
-- Comments
|
||||
COMMENT ON TABLE ml.prediction_overlays IS 'Complex overlay configurations for chart visualization';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.prediction_id IS 'Reference to parent ML prediction';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.overlay_type IS 'Type of overlay for rendering';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.price_levels IS 'Array of price levels for horizontal overlays';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.time_range IS 'Time range for time-bounded overlays';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.coordinates IS 'JSON array of {time, price} coordinates';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.style_config IS 'Visual styling configuration';
|
||||
COMMENT ON COLUMN ml.prediction_overlays.is_active IS 'Whether overlay should be displayed';
|
||||
104
migrations/2026-02-03_add_price_alerts_symbol_fk.sql
Normal file
104
migrations/2026-02-03_add_price_alerts_symbol_fk.sql
Normal file
@ -0,0 +1,104 @@
|
||||
-- ============================================================================
|
||||
-- Migration: Add symbol_id FK to trading.price_alerts
|
||||
-- Date: 2026-02-03
|
||||
-- Task: TASK-2026-02-03-ANALISIS-DDL-MODELADO / ST-1.3
|
||||
-- Description: Ensures price_alerts has proper FK relationship to symbols
|
||||
-- ============================================================================
|
||||
|
||||
-- Note: This migration is idempotent - safe to run multiple times
|
||||
-- The DDL file (11-price_alerts.sql) already includes symbol_id with FK,
|
||||
-- but this migration ensures existing databases are updated.
|
||||
|
||||
-- Step 1: Add column if not exists (for DBs created before FK was added to DDL)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'trading'
|
||||
AND table_name = 'price_alerts'
|
||||
AND column_name = 'symbol_id'
|
||||
) THEN
|
||||
ALTER TABLE trading.price_alerts ADD COLUMN symbol_id UUID;
|
||||
RAISE NOTICE 'Column symbol_id added to trading.price_alerts';
|
||||
ELSE
|
||||
RAISE NOTICE 'Column symbol_id already exists in trading.price_alerts';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 2: Add FK constraint if not exists
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_schema = 'trading'
|
||||
AND table_name = 'price_alerts'
|
||||
AND constraint_name = 'fk_price_alerts_symbol'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_schema = 'trading'
|
||||
AND table_name = 'price_alerts'
|
||||
AND constraint_name = 'price_alerts_symbol_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE trading.price_alerts
|
||||
ADD CONSTRAINT fk_price_alerts_symbol
|
||||
FOREIGN KEY (symbol_id) REFERENCES trading.symbols(id) ON DELETE CASCADE;
|
||||
RAISE NOTICE 'FK constraint fk_price_alerts_symbol added';
|
||||
ELSE
|
||||
RAISE NOTICE 'FK constraint for symbol_id already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 3: Create index if not exists
|
||||
CREATE INDEX IF NOT EXISTS idx_price_alerts_symbol_id
|
||||
ON trading.price_alerts(symbol_id);
|
||||
|
||||
-- Step 4: Migration script for existing data
|
||||
-- If there's a legacy 'symbol' varchar column, migrate data to symbol_id
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'trading'
|
||||
AND table_name = 'price_alerts'
|
||||
AND column_name = 'symbol'
|
||||
AND data_type IN ('character varying', 'text')
|
||||
) THEN
|
||||
-- Migrate data from symbol (varchar) to symbol_id (uuid)
|
||||
UPDATE trading.price_alerts pa
|
||||
SET symbol_id = s.id
|
||||
FROM trading.symbols s
|
||||
WHERE pa.symbol = s.symbol
|
||||
AND pa.symbol_id IS NULL;
|
||||
|
||||
RAISE NOTICE 'Data migrated from symbol column to symbol_id';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 5: Verify integrity after migration
|
||||
DO $$
|
||||
DECLARE
|
||||
orphan_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO orphan_count
|
||||
FROM trading.price_alerts
|
||||
WHERE symbol_id IS NULL;
|
||||
|
||||
IF orphan_count > 0 THEN
|
||||
RAISE WARNING 'Found % price_alerts with NULL symbol_id - manual review required', orphan_count;
|
||||
ELSE
|
||||
RAISE NOTICE 'All price_alerts have valid symbol_id references';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Optional: Make NOT NULL after confirming all data is migrated
|
||||
-- Uncomment after verifying no orphan records:
|
||||
-- ALTER TABLE trading.price_alerts ALTER COLUMN symbol_id SET NOT NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- Verification query (run manually to confirm):
|
||||
-- SELECT
|
||||
-- COUNT(*) as total_alerts,
|
||||
-- COUNT(symbol_id) as with_symbol_id,
|
||||
-- COUNT(*) - COUNT(symbol_id) as missing_symbol_id
|
||||
-- FROM trading.price_alerts;
|
||||
-- ============================================================================
|
||||
Loading…
Reference in New Issue
Block a user