-- ============================================================================ -- SCHEMA: broker_integration -- TABLE: trade_execution -- DESCRIPTION: Ejecucion de ordenes en broker -- VERSION: 1.0.0 -- CREATED: 2026-01-16 -- SPRINT: Sprint 6 - DDL Implementation Roadmap Q1-2026 -- ============================================================================ -- Enum para estado de ejecucion DO $$ BEGIN CREATE TYPE broker_integration.execution_status AS ENUM ( 'pending', -- Pendiente de enviar 'submitted', -- Enviada al broker 'accepted', -- Aceptada por broker 'filled', -- Ejecutada completamente 'partial_fill', -- Ejecutada parcialmente 'rejected', -- Rechazada 'cancelled', -- Cancelada 'expired', -- Expirada 'error' -- Error de sistema ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para tipo de orden DO $$ BEGIN CREATE TYPE broker_integration.order_type AS ENUM ( 'market', 'limit', 'stop', 'stop_limit', 'trailing_stop' ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para direccion DO $$ BEGIN CREATE TYPE broker_integration.trade_direction AS ENUM ( 'buy', 'sell' ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Tabla de Ejecucion de Trades CREATE TABLE IF NOT EXISTS broker_integration.trade_execution ( -- Identificadores id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE, -- Referencias broker_account_id UUID NOT NULL REFERENCES broker_integration.broker_accounts(id) ON DELETE CASCADE, signal_id UUID, -- Senal que origino position_id UUID, -- Posicion resultante -- Orden order_type broker_integration.order_type NOT NULL, direction broker_integration.trade_direction NOT NULL, symbol VARCHAR(20) NOT NULL, -- TamaƱo lot_size DECIMAL(10, 4) NOT NULL, units INTEGER, -- Precios solicitados requested_price DECIMAL(15, 8), limit_price DECIMAL(15, 8), stop_price DECIMAL(15, 8), -- Stop Loss / Take Profit stop_loss DECIMAL(15, 8), take_profit DECIMAL(15, 8), -- Estado status broker_integration.execution_status NOT NULL DEFAULT 'pending', status_message TEXT, -- Ejecucion executed_price DECIMAL(15, 8), executed_lot_size DECIMAL(10, 4), slippage DECIMAL(10, 4), -- En pips slippage_cost DECIMAL(15, 4), -- En moneda de cuenta -- IDs del broker broker_order_id VARCHAR(100), broker_ticket VARCHAR(100), broker_deal_id VARCHAR(100), -- Timestamps de ejecucion submitted_at TIMESTAMPTZ, accepted_at TIMESTAMPTZ, executed_at TIMESTAMPTZ, cancelled_at TIMESTAMPTZ, -- Latencia submission_latency_ms INTEGER, -- Tiempo hasta submit execution_latency_ms INTEGER, -- Tiempo hasta fill total_latency_ms INTEGER, -- Costos commission DECIMAL(15, 4) DEFAULT 0, swap DECIMAL(15, 4) DEFAULT 0, spread_cost DECIMAL(15, 4) DEFAULT 0, -- Contexto execution_source VARCHAR(50), -- 'manual', 'signal', 'bot', 'copy_trade' execution_context JSONB DEFAULT '{}'::JSONB, -- Errores error_code VARCHAR(50), error_message TEXT, retry_count INTEGER NOT NULL DEFAULT 0, max_retries INTEGER DEFAULT 3, -- Validez valid_until TIMESTAMPTZ, time_in_force VARCHAR(20) DEFAULT 'GTC', -- 'GTC', 'IOC', 'FOK', 'GTD' -- Metadata metadata JSONB DEFAULT '{}'::JSONB, notes TEXT, -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); COMMENT ON TABLE broker_integration.trade_execution IS 'Registro de todas las ejecuciones de ordenes en brokers conectados'; COMMENT ON COLUMN broker_integration.trade_execution.slippage IS 'Diferencia entre precio solicitado y ejecutado en pips'; -- Indices CREATE INDEX IF NOT EXISTS idx_trade_exec_tenant ON broker_integration.trade_execution(tenant_id); CREATE INDEX IF NOT EXISTS idx_trade_exec_user ON broker_integration.trade_execution(user_id); CREATE INDEX IF NOT EXISTS idx_trade_exec_account ON broker_integration.trade_execution(broker_account_id); CREATE INDEX IF NOT EXISTS idx_trade_exec_status ON broker_integration.trade_execution(status); CREATE INDEX IF NOT EXISTS idx_trade_exec_pending ON broker_integration.trade_execution(status, created_at) WHERE status IN ('pending', 'submitted'); CREATE INDEX IF NOT EXISTS idx_trade_exec_symbol ON broker_integration.trade_execution(symbol, executed_at DESC); CREATE INDEX IF NOT EXISTS idx_trade_exec_broker_order ON broker_integration.trade_execution(broker_order_id) WHERE broker_order_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_trade_exec_signal ON broker_integration.trade_execution(signal_id) WHERE signal_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_trade_exec_created ON broker_integration.trade_execution(created_at DESC); -- Trigger para updated_at DROP TRIGGER IF EXISTS trade_exec_updated_at ON broker_integration.trade_execution; CREATE TRIGGER trade_exec_updated_at BEFORE UPDATE ON broker_integration.trade_execution FOR EACH ROW EXECUTE FUNCTION broker_integration.update_broker_timestamp(); -- Trigger para calcular latencias CREATE OR REPLACE FUNCTION broker_integration.calculate_execution_latency() RETURNS TRIGGER AS $$ BEGIN IF NEW.submitted_at IS NOT NULL AND NEW.submission_latency_ms IS NULL THEN NEW.submission_latency_ms := EXTRACT(EPOCH FROM (NEW.submitted_at - NEW.created_at)) * 1000; END IF; IF NEW.executed_at IS NOT NULL AND NEW.submitted_at IS NOT NULL AND NEW.execution_latency_ms IS NULL THEN NEW.execution_latency_ms := EXTRACT(EPOCH FROM (NEW.executed_at - NEW.submitted_at)) * 1000; END IF; IF NEW.executed_at IS NOT NULL AND NEW.total_latency_ms IS NULL THEN NEW.total_latency_ms := EXTRACT(EPOCH FROM (NEW.executed_at - NEW.created_at)) * 1000; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trade_exec_latency ON broker_integration.trade_execution; CREATE TRIGGER trade_exec_latency BEFORE UPDATE OF submitted_at, executed_at ON broker_integration.trade_execution FOR EACH ROW EXECUTE FUNCTION broker_integration.calculate_execution_latency(); -- Vista de ejecuciones recientes CREATE OR REPLACE VIEW broker_integration.v_recent_executions AS SELECT te.id, te.user_id, ba.broker_name, te.symbol, te.direction, te.order_type, te.lot_size, te.status, te.requested_price, te.executed_price, te.slippage, te.total_latency_ms, te.created_at, te.executed_at FROM broker_integration.trade_execution te JOIN broker_integration.broker_accounts ba ON te.broker_account_id = ba.id ORDER BY te.created_at DESC; -- Vista de estadisticas de ejecucion CREATE OR REPLACE VIEW broker_integration.v_execution_stats AS SELECT ba.broker_name, te.symbol, COUNT(*) AS total_executions, COUNT(*) FILTER (WHERE te.status = 'filled') AS filled, COUNT(*) FILTER (WHERE te.status = 'rejected') AS rejected, ROUND((COUNT(*) FILTER (WHERE te.status = 'filled')::DECIMAL / NULLIF(COUNT(*), 0) * 100), 2) AS fill_rate, ROUND(AVG(te.slippage) FILTER (WHERE te.slippage IS NOT NULL)::NUMERIC, 2) AS avg_slippage, ROUND(AVG(te.total_latency_ms) FILTER (WHERE te.total_latency_ms IS NOT NULL)::NUMERIC, 0) AS avg_latency_ms FROM broker_integration.trade_execution te JOIN broker_integration.broker_accounts ba ON te.broker_account_id = ba.id WHERE te.created_at >= NOW() - INTERVAL '30 days' GROUP BY ba.broker_name, te.symbol ORDER BY total_executions DESC; -- RLS ALTER TABLE broker_integration.trade_execution ENABLE ROW LEVEL SECURITY; CREATE POLICY trade_exec_tenant ON broker_integration.trade_execution FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY trade_exec_user ON broker_integration.trade_execution FOR ALL USING (user_id = current_setting('app.current_user_id', true)::UUID); -- Grants GRANT SELECT, INSERT, UPDATE ON broker_integration.trade_execution TO trading_app; GRANT SELECT ON broker_integration.trade_execution TO trading_readonly; GRANT SELECT ON broker_integration.v_recent_executions TO trading_app; GRANT SELECT ON broker_integration.v_execution_stats TO trading_app;