trading-platform-database-v2/ddl/schemas/broker_integration/tables/004_trade_execution.sql
rckrdmrd 4631a58b42 [DDL] feat: Sprint 6 - Add broker_integration and portfolio_management schemas
broker_integration (5 tables):
- broker_accounts: Cuentas MT4/MT5/API conectadas
- broker_prices: Precios en tiempo real
- spread_statistics: Estadisticas historicas de spread
- price_adjustment_model: Modelos de ajuste de precio
- trade_execution: Ejecucion de ordenes

portfolio_management (6 tables):
- portfolios: Portafolios de inversion
- portfolio_accounts: Cuentas vinculadas a portafolios
- investment_goals: Metas de inversion
- rebalance_suggestions: Sugerencias de rebalanceo
- portfolio_snapshots: Snapshots historicos
- monte_carlo_projections: Proyecciones Monte Carlo

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 20:19:27 -06:00

259 lines
8.7 KiB
PL/PgSQL

-- ============================================================================
-- 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;