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>
259 lines
8.7 KiB
PL/PgSQL
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;
|