diff --git a/ddl/schemas/education/tables/18-course_tags.sql b/ddl/schemas/education/tables/18-course_tags.sql new file mode 100644 index 0000000..23bb63f --- /dev/null +++ b/ddl/schemas/education/tables/18-course_tags.sql @@ -0,0 +1,47 @@ +-- ===================================================== +-- TABLE: education.course_tags +-- ===================================================== +-- Proyecto: OrbiQuant IA (Trading Platform) +-- Modulo: OQI-002 - Education +-- Purpose: Tags for categorizing and searching courses +-- Related: OQI-002 Education Module, GAP-005 +-- Created: 2026-02-03 +-- ===================================================== + +CREATE TABLE IF NOT EXISTS education.course_tags ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(50) NOT NULL, + slug VARCHAR(50) NOT NULL UNIQUE, + description TEXT, + color VARCHAR(7) DEFAULT '#6B7280', -- Hex color for UI + icon VARCHAR(50), -- Icon identifier + is_featured BOOLEAN NOT NULL DEFAULT false, + usage_count INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT course_tags_name_not_empty CHECK (length(trim(name)) > 0), + CONSTRAINT course_tags_slug_format CHECK (slug ~ '^[a-z0-9-]+$'), + CONSTRAINT course_tags_color_format CHECK (color ~ '^#[0-9A-Fa-f]{6}$'), + CONSTRAINT course_tags_usage_positive CHECK (usage_count >= 0) +); + +-- Indexes +CREATE INDEX idx_course_tags_slug ON education.course_tags(slug); +CREATE INDEX idx_course_tags_name ON education.course_tags(name); +CREATE INDEX idx_course_tags_featured ON education.course_tags(is_featured) WHERE is_featured = true; +CREATE INDEX idx_course_tags_usage ON education.course_tags(usage_count DESC); + +-- Trigger +CREATE TRIGGER trg_course_tags_updated_at + BEFORE UPDATE ON education.course_tags + FOR EACH ROW + EXECUTE FUNCTION public.update_updated_at(); + +-- Comments +COMMENT ON TABLE education.course_tags IS 'Tags for categorizing courses (e.g., forex, crypto, technical-analysis)'; +COMMENT ON COLUMN education.course_tags.slug IS 'URL-friendly identifier for the tag'; +COMMENT ON COLUMN education.course_tags.color IS 'Hex color code for UI display'; +COMMENT ON COLUMN education.course_tags.icon IS 'Icon identifier for UI display'; +COMMENT ON COLUMN education.course_tags.is_featured IS 'Whether tag appears in featured/promoted sections'; +COMMENT ON COLUMN education.course_tags.usage_count IS 'Denormalized count of courses using this tag'; diff --git a/ddl/schemas/education/tables/19-course_tag_assignments.sql b/ddl/schemas/education/tables/19-course_tag_assignments.sql new file mode 100644 index 0000000..60b5584 --- /dev/null +++ b/ddl/schemas/education/tables/19-course_tag_assignments.sql @@ -0,0 +1,50 @@ +-- ===================================================== +-- TABLE: education.course_tag_assignments +-- ===================================================== +-- Proyecto: OrbiQuant IA (Trading Platform) +-- Modulo: OQI-002 - Education +-- Purpose: Many-to-many relationship between courses and tags +-- Related: OQI-002 Education Module, GAP-005 +-- Created: 2026-02-03 +-- ===================================================== + +CREATE TABLE IF NOT EXISTS education.course_tag_assignments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + course_id UUID NOT NULL REFERENCES education.courses(id) ON DELETE CASCADE, + tag_id UUID NOT NULL REFERENCES education.course_tags(id) ON DELETE CASCADE, + assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + assigned_by UUID REFERENCES auth.users(id), + + CONSTRAINT unique_course_tag UNIQUE (course_id, tag_id) +); + +-- Indexes +CREATE INDEX idx_course_tag_assignments_course ON education.course_tag_assignments(course_id); +CREATE INDEX idx_course_tag_assignments_tag ON education.course_tag_assignments(tag_id); + +-- Function to update usage_count on course_tags +CREATE OR REPLACE FUNCTION education.update_tag_usage_count() +RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'INSERT' THEN + UPDATE education.course_tags SET usage_count = usage_count + 1 WHERE id = NEW.tag_id; + ELSIF TG_OP = 'DELETE' THEN + UPDATE education.course_tags SET usage_count = usage_count - 1 WHERE id = OLD.tag_id; + END IF; + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +-- Trigger to maintain usage_count +CREATE TRIGGER trg_course_tag_usage + AFTER INSERT OR DELETE ON education.course_tag_assignments + FOR EACH ROW + EXECUTE FUNCTION education.update_tag_usage_count(); + +-- Comments +COMMENT ON TABLE education.course_tag_assignments IS 'Junction table for course-tag relationships'; +COMMENT ON COLUMN education.course_tag_assignments.course_id IS 'Reference to the course'; +COMMENT ON COLUMN education.course_tag_assignments.tag_id IS 'Reference to the tag'; +COMMENT ON COLUMN education.course_tag_assignments.assigned_at IS 'When the tag was assigned to the course'; +COMMENT ON COLUMN education.course_tag_assignments.assigned_by IS 'User who assigned the tag (optional)'; +COMMENT ON FUNCTION education.update_tag_usage_count() IS 'Maintains denormalized usage_count in course_tags table'; diff --git a/ddl/schemas/investment/tables/10-agent_executions.sql b/ddl/schemas/investment/tables/10-agent_executions.sql index 187acb3..a99880f 100644 --- a/ddl/schemas/investment/tables/10-agent_executions.sql +++ b/ddl/schemas/investment/tables/10-agent_executions.sql @@ -3,8 +3,11 @@ -- ===================================================== -- Description: Tracking de ejecuciones de trading agents -- Schema: investment --- Gap: GAP-DDL-005 +-- Gap: GAP-DDL-005, GAP-007 +-- Task: ST-3.3 -- Created: 2026-02-03 +-- Updated: 2026-02-03 +-- Related: OQI-004 Investment Accounts -- ===================================================== CREATE TABLE investment.agent_executions ( @@ -21,55 +24,149 @@ CREATE TABLE investment.agent_executions ( execution_type IN ('trade', 'rebalance', 'distribution', 'stop_loss', 'take_profit', 'hedge') ), - -- Detalles del trade (si aplica) + -- Detalles del trade 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 + -- Resultado (P&L) pnl DECIMAL(20,8), pnl_percentage DECIMAL(8,4), fees DECIMAL(20,8) DEFAULT 0, - -- Detalles adicionales + -- Metricas de ejecucion + execution_time_ms INTEGER, + slippage DECIMAL(20,8), + + -- Metricas de riesgo + risk_score DECIMAL(5,4) CHECK (risk_score IS NULL OR risk_score BETWEEN 0 AND 1), + position_size_percent DECIMAL(5,2), + + -- Detalles adicionales (JSON) trade_details JSONB DEFAULT '{}', - market_conditions JSONB, -- volatility, trend, etc. + market_conditions JSONB, -- volatility, trend, liquidity, etc. -- Estado status VARCHAR(20) DEFAULT 'executed' CHECK ( status IN ('pending', 'executed', 'partially_filled', 'cancelled', 'failed') ), + error_code VARCHAR(50), failure_reason TEXT, + notes TEXT, -- Metadata ML/AI - signal_source VARCHAR(50), -- ml_model, llm, manual, scheduled - confidence_score DECIMAL(5,4), + signal_source VARCHAR(50), -- ml_model, llm, manual, scheduled, ensemble + confidence_score DECIMAL(5,4) CHECK (confidence_score IS NULL OR confidence_score BETWEEN 0 AND 1), model_version VARCHAR(50), + model_id UUID, -- Reference to ML model + + -- Referencias externas + external_order_id VARCHAR(100), -- Timestamps executed_at TIMESTAMPTZ DEFAULT NOW(), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + closed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); --- Indices +-- ===================================================== +-- INDEXES +-- ===================================================== + +-- Primary lookup indexes 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 +-- Time-based indexes +CREATE INDEX idx_agent_exec_date ON investment.agent_executions(executed_at DESC); CREATE INDEX idx_agent_exec_account_date ON investment.agent_executions(account_id, executed_at DESC); --- Comentarios +-- Symbol and P&L indexes (partial for efficiency) +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 DESC NULLS LAST) + WHERE pnl IS NOT NULL; + +-- Performance analysis indexes +CREATE INDEX idx_agent_exec_slippage ON investment.agent_executions(slippage) + WHERE slippage IS NOT NULL; +CREATE INDEX idx_agent_exec_risk_score ON investment.agent_executions(risk_score) + WHERE risk_score IS NOT NULL; + +-- External reference indexes +CREATE INDEX idx_agent_exec_external_order ON investment.agent_executions(external_order_id) + WHERE external_order_id IS NOT NULL; +CREATE INDEX idx_agent_exec_model ON investment.agent_executions(model_id) + WHERE model_id IS NOT NULL; + +-- Signal analysis index (composite) +CREATE INDEX idx_agent_exec_signal_confidence ON investment.agent_executions(signal_source, confidence_score DESC NULLS LAST) + WHERE signal_source IS NOT NULL; + +-- ===================================================== +-- TRIGGER +-- ===================================================== + +CREATE TRIGGER trg_agent_executions_updated_at + BEFORE UPDATE ON investment.agent_executions + FOR EACH ROW + EXECUTE FUNCTION public.update_updated_at(); + +-- ===================================================== +-- COMMENTS +-- ===================================================== + COMMENT ON TABLE investment.agent_executions IS 'Tracking de ejecuciones de trading agents (Atlas, Orion, Nova)'; + +-- Agent and execution type 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'; + +-- Trade details +COMMENT ON COLUMN investment.agent_executions.symbol IS 'Trading pair symbol (e.g., BTCUSDT, EURUSD)'; +COMMENT ON COLUMN investment.agent_executions.side IS 'Trade direction: buy or sell'; +COMMENT ON COLUMN investment.agent_executions.quantity IS 'Position size in base currency'; +COMMENT ON COLUMN investment.agent_executions.entry_price IS 'Entry price of the position'; +COMMENT ON COLUMN investment.agent_executions.exit_price IS 'Exit price (null if position still open)'; + +-- P&L +COMMENT ON COLUMN investment.agent_executions.pnl IS 'Profit/Loss in quote currency'; +COMMENT ON COLUMN investment.agent_executions.pnl_percentage IS 'P&L as percentage of position'; +COMMENT ON COLUMN investment.agent_executions.fees IS 'Trading fees/commissions paid'; + +-- Execution metrics +COMMENT ON COLUMN investment.agent_executions.execution_time_ms IS 'Time taken to execute the trade in milliseconds'; +COMMENT ON COLUMN investment.agent_executions.slippage IS 'Price slippage between requested and executed price'; + +-- Risk metrics +COMMENT ON COLUMN investment.agent_executions.risk_score IS 'Risk assessment score at time of execution (0-1)'; +COMMENT ON COLUMN investment.agent_executions.position_size_percent IS 'Position size as percentage of portfolio'; + +-- JSON details +COMMENT ON COLUMN investment.agent_executions.trade_details IS 'JSON with additional trade details (leverage, margin, etc.)'; +COMMENT ON COLUMN investment.agent_executions.market_conditions IS 'Market conditions at execution (volatility, trend, liquidity)'; + +-- Status and errors +COMMENT ON COLUMN investment.agent_executions.status IS 'Execution status: pending, executed, partially_filled, cancelled, failed'; +COMMENT ON COLUMN investment.agent_executions.error_code IS 'Standardized error code for failures'; +COMMENT ON COLUMN investment.agent_executions.failure_reason IS 'Detailed failure description'; +COMMENT ON COLUMN investment.agent_executions.notes IS 'Manual annotations or additional context'; + +-- ML/AI metadata +COMMENT ON COLUMN investment.agent_executions.signal_source IS 'Signal origin: ml_model, llm, manual, scheduled, ensemble'; +COMMENT ON COLUMN investment.agent_executions.confidence_score IS 'ML model confidence score (0-1)'; +COMMENT ON COLUMN investment.agent_executions.model_version IS 'Version of the ML model used'; +COMMENT ON COLUMN investment.agent_executions.model_id IS 'Reference to ML model that generated the signal'; + +-- External references +COMMENT ON COLUMN investment.agent_executions.external_order_id IS 'Order ID from external broker/exchange (Binance, MT4, etc.)'; + +-- Timestamps +COMMENT ON COLUMN investment.agent_executions.executed_at IS 'When the trade was executed'; +COMMENT ON COLUMN investment.agent_executions.closed_at IS 'When the position was closed'; +COMMENT ON COLUMN investment.agent_executions.created_at IS 'Record creation timestamp'; +COMMENT ON COLUMN investment.agent_executions.updated_at IS 'Last modification timestamp'; diff --git a/ddl/schemas/ml/tables/03-predictions.sql b/ddl/schemas/ml/tables/03-predictions.sql index aa6fc33..f3e0eb7 100644 --- a/ddl/schemas/ml/tables/03-predictions.sql +++ b/ddl/schemas/ml/tables/03-predictions.sql @@ -92,3 +92,51 @@ COMMENT ON COLUMN ml.predictions.model_output IS "raw_score": 0.5823, "feature_contributions": {...} }'; + +-- ===================================================== +-- PERFORMANCE COMPOSITE INDEXES (added 2026-02-03, ST-3.4) +-- ===================================================== + +-- Index 1: Symbol + Timeframe + Created (most common query pattern) +-- Use: WHERE symbol = ? AND timeframe = ? ORDER BY created_at DESC +CREATE INDEX idx_predictions_symbol_timeframe_created +ON ml.predictions(symbol, timeframe, created_at DESC); + +-- Index 2: Symbol + Prediction Type + Created (filtered queries) +-- Use: WHERE symbol = ? AND prediction_type = ? ORDER BY created_at DESC +CREATE INDEX idx_predictions_symbol_type_created +ON ml.predictions(symbol, prediction_type, created_at DESC); + +-- Index 3: Model + Symbol + Timeframe (model-specific queries) +-- Use: WHERE model_id = ? AND symbol = ? AND timeframe = ? ORDER BY created_at DESC +CREATE INDEX idx_predictions_model_symbol_timeframe +ON ml.predictions(model_id, symbol, timeframe, created_at DESC); + +-- Index 4: High confidence predictions (partial index) +-- Use: WHERE symbol = ? AND timeframe = ? AND confidence_score >= 0.7 +CREATE INDEX idx_predictions_high_confidence +ON ml.predictions(symbol, timeframe, created_at DESC) +WHERE confidence_score >= 0.7; + +-- Index 5: Recent valid predictions (partial index for active queries) +-- Use: WHERE symbol = ? AND timeframe = ? AND valid_until IS NOT NULL +CREATE INDEX idx_predictions_recent_valid +ON ml.predictions(symbol, timeframe, prediction_type, created_at DESC) +WHERE valid_until IS NOT NULL; + +-- Index 6: Overlay display (for chart rendering) +-- Use: WHERE symbol = ? AND timeframe = ? AND show_on_chart = true +CREATE INDEX idx_predictions_overlay_display +ON ml.predictions(symbol, timeframe, display_priority DESC, created_at DESC) +WHERE (chart_config->>'show_on_chart')::boolean = true; + +-- Index 7: Symbol + Confidence Score (ranking queries) +-- Use: WHERE symbol = ? ORDER BY confidence_score DESC +CREATE INDEX idx_predictions_symbol_confidence +ON ml.predictions(symbol, confidence_score DESC, created_at DESC); + +-- Index 8: Timeframe + Prediction Result (cross-symbol analysis) +-- Use: WHERE timeframe = ? AND prediction_result = ? ORDER BY created_at DESC +CREATE INDEX idx_predictions_timeframe_result_created +ON ml.predictions(timeframe, prediction_result, created_at DESC) +WHERE prediction_result IS NOT NULL; diff --git a/ddl/schemas/trading/00-enums.sql b/ddl/schemas/trading/00-enums.sql index 0b75d8b..bf2205f 100644 --- a/ddl/schemas/trading/00-enums.sql +++ b/ddl/schemas/trading/00-enums.sql @@ -83,3 +83,27 @@ CREATE TYPE trading.bot_status AS ENUM ( 'stopped', 'error' ); + +-- Drawing tool types for chart annotations +CREATE TYPE trading.drawing_tool_type AS ENUM ( + 'trend_line', + 'horizontal_line', + 'vertical_line', + 'ray', + 'extended_line', + 'parallel_channel', + 'fibonacci_retracement', + 'fibonacci_extension', + 'rectangle', + 'ellipse', + 'triangle', + 'arrow', + 'text', + 'price_range', + 'date_range', + 'order_block', + 'fair_value_gap', + 'liquidity_level' +); + +COMMENT ON TYPE trading.drawing_tool_type IS 'Types of drawing tools available on charts'; diff --git a/ddl/schemas/trading/tables/12-drawing_tools.sql b/ddl/schemas/trading/tables/12-drawing_tools.sql new file mode 100644 index 0000000..91c654c --- /dev/null +++ b/ddl/schemas/trading/tables/12-drawing_tools.sql @@ -0,0 +1,77 @@ +-- ============================================================================ +-- Schema: trading +-- File: 12-drawing_tools.sql +-- Description: User drawings and annotations on trading charts +-- Related: OQI-003 Trading Charts, GAP-006 +-- Dependencies: 00-enums.sql, 01-symbols.sql, auth.users +-- ============================================================================ + +CREATE TABLE IF NOT EXISTS trading.drawing_tools ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + symbol_id UUID NOT NULL REFERENCES trading.symbols(id) ON DELETE CASCADE, + timeframe trading.timeframe NOT NULL, + tool_type trading.drawing_tool_type NOT NULL, + name VARCHAR(100), + + -- Coordinates (stored as JSON for flexibility) + points JSONB NOT NULL DEFAULT '[]', -- Array of {time, price} points + + -- Style configuration + style JSONB NOT NULL DEFAULT '{ + "color": "#2196F3", + "lineWidth": 1, + "lineStyle": "solid", + "fillColor": null, + "fillOpacity": 0.2, + "showLabel": true, + "labelPosition": "right" + }', + + -- Fibonacci specific (if applicable) + fib_levels DECIMAL(5,4)[] DEFAULT NULL, -- e.g., {0, 0.236, 0.382, 0.5, 0.618, 0.786, 1} + + -- Text content (for text/label tools) + text_content TEXT, + + -- Visibility and state + is_visible BOOLEAN NOT NULL DEFAULT true, + is_locked BOOLEAN NOT NULL DEFAULT false, + z_index INTEGER NOT NULL DEFAULT 0, + + -- Template/sharing + is_template BOOLEAN NOT NULL DEFAULT false, + is_shared BOOLEAN NOT NULL DEFAULT false, + + -- Metadata + metadata JSONB DEFAULT '{}', + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT drawing_tools_points_not_empty CHECK (jsonb_array_length(points) >= 1) +); + +-- Indexes +CREATE INDEX idx_drawing_tools_user_id ON trading.drawing_tools(user_id); +CREATE INDEX idx_drawing_tools_symbol_id ON trading.drawing_tools(symbol_id); +CREATE INDEX idx_drawing_tools_user_symbol ON trading.drawing_tools(user_id, symbol_id, timeframe); +CREATE INDEX idx_drawing_tools_visible ON trading.drawing_tools(user_id, is_visible) WHERE is_visible = true; +CREATE INDEX idx_drawing_tools_templates ON trading.drawing_tools(is_template) WHERE is_template = true; +CREATE INDEX idx_drawing_tools_shared ON trading.drawing_tools(is_shared) WHERE is_shared = true; +CREATE INDEX idx_drawing_tools_points ON trading.drawing_tools USING GIN(points); + +-- Trigger +CREATE TRIGGER trg_drawing_tools_updated_at + BEFORE UPDATE ON trading.drawing_tools + FOR EACH ROW + EXECUTE FUNCTION public.update_updated_at(); + +-- Comments +COMMENT ON TABLE trading.drawing_tools IS 'User drawings and annotations on trading charts'; +COMMENT ON COLUMN trading.drawing_tools.points IS 'Array of coordinate points [{time: ISO string, price: number}]'; +COMMENT ON COLUMN trading.drawing_tools.style IS 'Visual style configuration (color, line width, fill, etc.)'; +COMMENT ON COLUMN trading.drawing_tools.fib_levels IS 'Fibonacci levels for retracement/extension tools'; +COMMENT ON COLUMN trading.drawing_tools.z_index IS 'Z-index for layering multiple drawings'; +COMMENT ON COLUMN trading.drawing_tools.is_template IS 'Whether this drawing can be used as a template'; +COMMENT ON COLUMN trading.drawing_tools.is_shared IS 'Whether this drawing is visible to other users'; diff --git a/ddl/schemas/trading/tables/13-drawing_templates.sql b/ddl/schemas/trading/tables/13-drawing_templates.sql new file mode 100644 index 0000000..cfb00ff --- /dev/null +++ b/ddl/schemas/trading/tables/13-drawing_templates.sql @@ -0,0 +1,45 @@ +-- ============================================================================ +-- Schema: trading +-- File: 13-drawing_templates.sql +-- Description: Reusable drawing templates and presets +-- Related: OQI-003 Trading Charts, GAP-006 +-- Dependencies: 00-enums.sql, auth.users +-- ============================================================================ + +CREATE TABLE IF NOT EXISTS trading.drawing_templates ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, -- NULL = system template + name VARCHAR(100) NOT NULL, + description TEXT, + tool_type trading.drawing_tool_type NOT NULL, + style JSONB NOT NULL, + fib_levels DECIMAL(5,4)[] DEFAULT NULL, + is_system BOOLEAN NOT NULL DEFAULT false, + is_public BOOLEAN NOT NULL DEFAULT false, + usage_count INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT drawing_templates_name_not_empty CHECK (length(trim(name)) > 0) +); + +-- Indexes +CREATE INDEX idx_drawing_templates_user ON trading.drawing_templates(user_id); +CREATE INDEX idx_drawing_templates_tool_type ON trading.drawing_templates(tool_type); +CREATE INDEX idx_drawing_templates_public ON trading.drawing_templates(is_public) WHERE is_public = true; +CREATE INDEX idx_drawing_templates_system ON trading.drawing_templates(is_system) WHERE is_system = true; + +-- Trigger +CREATE TRIGGER trg_drawing_templates_updated_at + BEFORE UPDATE ON trading.drawing_templates + FOR EACH ROW + EXECUTE FUNCTION public.update_updated_at(); + +-- Comments +COMMENT ON TABLE trading.drawing_templates IS 'Reusable drawing templates and presets for chart annotations'; +COMMENT ON COLUMN trading.drawing_templates.user_id IS 'Owner of template, NULL for system templates'; +COMMENT ON COLUMN trading.drawing_templates.style IS 'Default style configuration for the template'; +COMMENT ON COLUMN trading.drawing_templates.fib_levels IS 'Default Fibonacci levels for retracement/extension templates'; +COMMENT ON COLUMN trading.drawing_templates.is_system IS 'System-provided template (cannot be deleted by users)'; +COMMENT ON COLUMN trading.drawing_templates.is_public IS 'Template is visible to all users'; +COMMENT ON COLUMN trading.drawing_templates.usage_count IS 'Number of times this template has been applied'; diff --git a/migrations/2026-02-03_add_ml_composite_indexes.sql b/migrations/2026-02-03_add_ml_composite_indexes.sql new file mode 100644 index 0000000..ac7552d --- /dev/null +++ b/migrations/2026-02-03_add_ml_composite_indexes.sql @@ -0,0 +1,108 @@ +-- ===================================================== +-- MIGRATION: Add composite indexes to ml.predictions for performance +-- ===================================================== +-- Date: 2026-02-03 +-- Task: ST-3.4, GAP-008 +-- Description: Optimize query performance with composite indexes +-- ===================================================== + +-- =========================================== +-- COMPOSITE INDEX 1: Symbol + Timeframe + Created +-- =========================================== +-- Query pattern: SELECT * FROM ml.predictions +-- WHERE symbol = ? AND timeframe = ? +-- ORDER BY created_at DESC LIMIT ? +-- Use case: Most common query for fetching latest predictions for a symbol/timeframe +CREATE INDEX IF NOT EXISTS idx_predictions_symbol_timeframe_created +ON ml.predictions(symbol, timeframe, created_at DESC); + +-- =========================================== +-- COMPOSITE INDEX 2: Symbol + Prediction Type + Created +-- =========================================== +-- Query pattern: SELECT * FROM ml.predictions +-- WHERE symbol = ? AND prediction_type = ? +-- ORDER BY created_at DESC +-- Use case: Filtered queries by prediction type (e.g., only DIRECTION predictions) +CREATE INDEX IF NOT EXISTS idx_predictions_symbol_type_created +ON ml.predictions(symbol, prediction_type, created_at DESC); + +-- =========================================== +-- COMPOSITE INDEX 3: Model + Symbol + Timeframe + Created +-- =========================================== +-- Query pattern: SELECT * FROM ml.predictions +-- WHERE model_id = ? AND symbol = ? AND timeframe = ? +-- ORDER BY created_at DESC +-- Use case: Model-specific prediction queries for analysis and comparison +CREATE INDEX IF NOT EXISTS idx_predictions_model_symbol_timeframe +ON ml.predictions(model_id, symbol, timeframe, created_at DESC); + +-- =========================================== +-- COMPOSITE INDEX 4: High Confidence Predictions (Partial) +-- =========================================== +-- Query pattern: SELECT * FROM ml.predictions +-- WHERE symbol = ? AND timeframe = ? AND confidence_score >= 0.7 +-- ORDER BY created_at DESC +-- Use case: Fetch only high-confidence predictions for display/alerts +CREATE INDEX IF NOT EXISTS idx_predictions_high_confidence +ON ml.predictions(symbol, timeframe, created_at DESC) +WHERE confidence_score >= 0.7; + +-- =========================================== +-- COMPOSITE INDEX 5: Recent Valid Predictions (Partial) +-- =========================================== +-- Query pattern: SELECT * FROM ml.predictions +-- WHERE symbol = ? AND timeframe = ? AND valid_until > NOW() +-- ORDER BY prediction_type, created_at DESC +-- Use case: Active predictions that haven't expired yet +-- Note: Uses immutable predicate-safe pattern (valid_until compared at query time) +CREATE INDEX IF NOT EXISTS idx_predictions_recent_valid +ON ml.predictions(symbol, timeframe, prediction_type, created_at DESC) +WHERE valid_until IS NOT NULL; + +-- =========================================== +-- COMPOSITE INDEX 6: Overlay Display for Charts +-- =========================================== +-- Query pattern: SELECT * FROM ml.predictions +-- WHERE symbol = ? AND timeframe = ? +-- AND (chart_config->>'show_on_chart')::boolean = true +-- ORDER BY display_priority DESC, created_at DESC +-- Use case: Chart rendering - fetch predictions configured for display +CREATE INDEX IF NOT EXISTS idx_predictions_overlay_display +ON ml.predictions(symbol, timeframe, display_priority DESC, created_at DESC) +WHERE (chart_config->>'show_on_chart')::boolean = true; + +-- =========================================== +-- COMPOSITE INDEX 7: Symbol + Confidence Score (Descending) +-- =========================================== +-- Query pattern: SELECT * FROM ml.predictions +-- WHERE symbol = ? +-- ORDER BY confidence_score DESC +-- Use case: Ranking predictions by confidence for a symbol +CREATE INDEX IF NOT EXISTS idx_predictions_symbol_confidence +ON ml.predictions(symbol, confidence_score DESC, created_at DESC); + +-- =========================================== +-- COMPOSITE INDEX 8: Timeframe + Prediction Result + Created +-- =========================================== +-- Query pattern: SELECT * FROM ml.predictions +-- WHERE timeframe = ? AND prediction_result = ? +-- ORDER BY created_at DESC +-- Use case: Cross-symbol analysis of specific prediction results (e.g., all BUY signals on 1H) +CREATE INDEX IF NOT EXISTS idx_predictions_timeframe_result_created +ON ml.predictions(timeframe, prediction_result, created_at DESC) +WHERE prediction_result IS NOT NULL; + +-- =========================================== +-- DOCUMENTATION NOTES +-- =========================================== +-- Performance considerations: +-- 1. These indexes are designed for read-heavy workloads +-- 2. Partial indexes reduce storage and improve write performance +-- 3. DESC ordering on timestamps optimizes LIMIT queries for recent data +-- 4. Run ANALYZE after creation in production: +-- ANALYZE ml.predictions; +-- +-- Index maintenance: +-- - Monitor pg_stat_user_indexes for usage statistics +-- - Consider REINDEX CONCURRENTLY if fragmentation occurs +-- - Review query plans with EXPLAIN ANALYZE periodically diff --git a/migrations/2026-02-03_complete_agent_executions.sql b/migrations/2026-02-03_complete_agent_executions.sql new file mode 100644 index 0000000..97856e4 --- /dev/null +++ b/migrations/2026-02-03_complete_agent_executions.sql @@ -0,0 +1,124 @@ +-- ===================================================== +-- Migration: Complete agent_executions table +-- ===================================================== +-- Description: Add missing columns for detailed execution tracking +-- Date: 2026-02-03 +-- Task: ST-3.3, GAP-007 +-- Related: OQI-004 Investment Accounts +-- ===================================================== + +-- ===================================================== +-- ADD MISSING COLUMNS +-- ===================================================== + +-- Execution performance metrics +ALTER TABLE investment.agent_executions +ADD COLUMN IF NOT EXISTS execution_time_ms INTEGER, +ADD COLUMN IF NOT EXISTS slippage DECIMAL(20,8); + +-- Risk metrics +ALTER TABLE investment.agent_executions +ADD COLUMN IF NOT EXISTS risk_score DECIMAL(5,4) CHECK (risk_score IS NULL OR risk_score BETWEEN 0 AND 1), +ADD COLUMN IF NOT EXISTS position_size_percent DECIMAL(5,2); + +-- External references +ALTER TABLE investment.agent_executions +ADD COLUMN IF NOT EXISTS external_order_id VARCHAR(100), +ADD COLUMN IF NOT EXISTS model_id UUID; + +-- Additional timestamps for lifecycle tracking +ALTER TABLE investment.agent_executions +ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ, +ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); + +-- Error tracking (complement existing failure_reason) +ALTER TABLE investment.agent_executions +ADD COLUMN IF NOT EXISTS error_code VARCHAR(50); + +-- Notes field for manual annotations +ALTER TABLE investment.agent_executions +ADD COLUMN IF NOT EXISTS notes TEXT; + +-- ===================================================== +-- ADD PERFORMANCE INDEXES +-- ===================================================== + +-- Index for slippage analysis +CREATE INDEX IF NOT EXISTS idx_agent_exec_slippage +ON investment.agent_executions(slippage) +WHERE slippage IS NOT NULL; + +-- Index for risk score analysis +CREATE INDEX IF NOT EXISTS idx_agent_exec_risk_score +ON investment.agent_executions(risk_score) +WHERE risk_score IS NOT NULL; + +-- Index for external order lookups +CREATE INDEX IF NOT EXISTS idx_agent_exec_external_order +ON investment.agent_executions(external_order_id) +WHERE external_order_id IS NOT NULL; + +-- Index for model performance tracking +CREATE INDEX IF NOT EXISTS idx_agent_exec_model +ON investment.agent_executions(model_id) +WHERE model_id IS NOT NULL; + +-- Composite index for signal source analysis +CREATE INDEX IF NOT EXISTS idx_agent_exec_signal_confidence +ON investment.agent_executions(signal_source, confidence_score DESC NULLS LAST) +WHERE signal_source IS NOT NULL; + +-- ===================================================== +-- ADD TRIGGER FOR updated_at +-- ===================================================== + +-- Create trigger function if not exists (reuse common function) +DO $$ +BEGIN + -- Check if public.update_updated_at exists, if not use a local one + IF NOT EXISTS ( + SELECT 1 FROM pg_proc p + JOIN pg_namespace n ON p.pronamespace = n.oid + WHERE n.nspname = 'public' AND p.proname = 'update_updated_at' + ) THEN + CREATE OR REPLACE FUNCTION public.update_updated_at() + RETURNS TRIGGER AS $func$ + BEGIN + NEW.updated_at = NOW(); + RETURN NEW; + END; + $func$ LANGUAGE plpgsql; + END IF; +END $$; + +-- Create or replace trigger +DROP TRIGGER IF EXISTS trg_agent_executions_updated_at ON investment.agent_executions; + +CREATE TRIGGER trg_agent_executions_updated_at + BEFORE UPDATE ON investment.agent_executions + FOR EACH ROW + EXECUTE FUNCTION public.update_updated_at(); + +-- ===================================================== +-- ADD COMMENTS FOR NEW COLUMNS +-- ===================================================== + +COMMENT ON COLUMN investment.agent_executions.execution_time_ms IS 'Time taken to execute the trade in milliseconds'; +COMMENT ON COLUMN investment.agent_executions.slippage IS 'Price slippage between requested and executed price'; +COMMENT ON COLUMN investment.agent_executions.risk_score IS 'Risk assessment score at time of execution (0-1)'; +COMMENT ON COLUMN investment.agent_executions.position_size_percent IS 'Position size as percentage of portfolio'; +COMMENT ON COLUMN investment.agent_executions.external_order_id IS 'Order ID from external broker/exchange'; +COMMENT ON COLUMN investment.agent_executions.model_id IS 'Reference to ML model that generated the signal'; +COMMENT ON COLUMN investment.agent_executions.closed_at IS 'Timestamp when position was closed'; +COMMENT ON COLUMN investment.agent_executions.updated_at IS 'Last modification timestamp'; +COMMENT ON COLUMN investment.agent_executions.error_code IS 'Standardized error code for failures'; +COMMENT ON COLUMN investment.agent_executions.notes IS 'Manual annotations or additional context'; + +-- ===================================================== +-- VERIFICATION QUERY (for manual check) +-- ===================================================== +-- SELECT column_name, data_type, is_nullable +-- FROM information_schema.columns +-- WHERE table_schema = 'investment' +-- AND table_name = 'agent_executions' +-- ORDER BY ordinal_position; diff --git a/seeds/prod/education/02-course_tags.sql b/seeds/prod/education/02-course_tags.sql new file mode 100644 index 0000000..3a677f1 --- /dev/null +++ b/seeds/prod/education/02-course_tags.sql @@ -0,0 +1,21 @@ +-- ===================================================== +-- SEED: Initial course tags +-- ===================================================== +-- Proyecto: OrbiQuant IA (Trading Platform) +-- Modulo: OQI-002 - Education +-- Related: ST-3.1 - Course Tags System +-- Created: 2026-02-03 +-- ===================================================== + +INSERT INTO education.course_tags (name, slug, description, color, is_featured) VALUES +('Forex', 'forex', 'Foreign exchange trading', '#10B981', true), +('Crypto', 'crypto', 'Cryptocurrency trading', '#F59E0B', true), +('Technical Analysis', 'technical-analysis', 'Chart patterns and indicators', '#3B82F6', true), +('Fundamental Analysis', 'fundamental-analysis', 'Economic and financial analysis', '#8B5CF6', false), +('Risk Management', 'risk-management', 'Position sizing and risk control', '#EF4444', true), +('Psychology', 'psychology', 'Trading mindset and emotions', '#EC4899', false), +('Beginner', 'beginner', 'Courses for new traders', '#6EE7B7', true), +('Advanced', 'advanced', 'Advanced trading concepts', '#FCD34D', false), +('ICT', 'ict', 'Inner Circle Trader concepts', '#818CF8', true), +('Smart Money', 'smart-money', 'Institutional trading concepts', '#F472B6', false) +ON CONFLICT (slug) DO NOTHING;