-- ============================================================================ -- SCHEMA: broker_integration -- TABLE: broker_accounts -- DESCRIPTION: Cuentas de broker conectadas (MT4, MT5, etc.) -- VERSION: 1.0.0 -- CREATED: 2026-01-16 -- SPRINT: Sprint 6 - DDL Implementation Roadmap Q1-2026 -- ============================================================================ -- Crear schema CREATE SCHEMA IF NOT EXISTS broker_integration; COMMENT ON SCHEMA broker_integration IS 'Integracion con brokers de trading (MT4, MT5, API de brokers)'; -- Enum para tipo de broker DO $$ BEGIN CREATE TYPE broker_integration.broker_type AS ENUM ( 'mt4', 'mt5', 'ctrader', 'fix_api', 'rest_api', 'binance', 'interactive_brokers', 'oanda', 'alpaca', 'custom' ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para tipo de cuenta DO $$ BEGIN CREATE TYPE broker_integration.account_type AS ENUM ( 'demo', 'live', 'contest' ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para estado de conexion DO $$ BEGIN CREATE TYPE broker_integration.connection_status AS ENUM ( 'connected', 'disconnected', 'connecting', 'error', 'maintenance', 'suspended', 'expired' ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Tabla de Cuentas de Broker CREATE TABLE IF NOT EXISTS broker_integration.broker_accounts ( -- 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, -- Broker info broker_type broker_integration.broker_type NOT NULL, broker_name VARCHAR(100) NOT NULL, -- "XM", "IC Markets" broker_server VARCHAR(255), -- Server name/address -- Cuenta account_type broker_integration.account_type NOT NULL DEFAULT 'demo', account_number VARCHAR(50) NOT NULL, account_name VARCHAR(100), -- Conexion connection_status broker_integration.connection_status NOT NULL DEFAULT 'disconnected', connection_provider VARCHAR(50), -- "metaapi", "direct" provider_account_id VARCHAR(100), -- ID en el proveedor -- Credenciales (encriptadas) login_encrypted TEXT, password_encrypted TEXT, investor_password_encrypted TEXT, api_token_encrypted TEXT, additional_credentials JSONB DEFAULT '{}'::JSONB, -- Configuracion leverage INTEGER, currency VARCHAR(10) NOT NULL DEFAULT 'USD', timezone VARCHAR(50) DEFAULT 'UTC', -- Balance y equity balance DECIMAL(15, 2), equity DECIMAL(15, 2), margin DECIMAL(15, 2), free_margin DECIMAL(15, 2), margin_level DECIMAL(10, 2), -- Configuracion de trading enable_trading BOOLEAN NOT NULL DEFAULT FALSE, max_positions INTEGER, max_lot_size DECIMAL(10, 2), min_lot_size DECIMAL(10, 4) DEFAULT 0.01, lot_step DECIMAL(10, 4) DEFAULT 0.01, -- Permisos allow_copy_trading BOOLEAN NOT NULL DEFAULT FALSE, allow_signals BOOLEAN NOT NULL DEFAULT TRUE, allow_auto_trading BOOLEAN NOT NULL DEFAULT FALSE, -- Restricciones allowed_symbols VARCHAR(20)[], -- NULL = todos blocked_symbols VARCHAR(20)[], allowed_directions VARCHAR(10)[], -- ['long', 'short'] trading_hours JSONB, -- Estado is_active BOOLEAN NOT NULL DEFAULT TRUE, is_default BOOLEAN NOT NULL DEFAULT FALSE, is_verified BOOLEAN NOT NULL DEFAULT FALSE, -- Ultima sincronizacion last_sync_at TIMESTAMPTZ, last_sync_error TEXT, sync_interval_seconds INTEGER DEFAULT 60, -- Estadisticas total_trades INTEGER NOT NULL DEFAULT 0, open_positions INTEGER NOT NULL DEFAULT 0, total_profit DECIMAL(15, 2) NOT NULL DEFAULT 0, today_profit DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Metadata metadata JSONB DEFAULT '{}'::JSONB, notes TEXT, -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), connected_at TIMESTAMPTZ, disconnected_at TIMESTAMPTZ, verified_at TIMESTAMPTZ, -- Constraints CONSTRAINT broker_accounts_unique UNIQUE (tenant_id, broker_type, account_number) ); COMMENT ON TABLE broker_integration.broker_accounts IS 'Cuentas de broker conectadas para trading real o demo'; COMMENT ON COLUMN broker_integration.broker_accounts.enable_trading IS 'Si TRUE, permite ejecutar ordenes reales en esta cuenta'; -- Indices CREATE INDEX IF NOT EXISTS idx_broker_accounts_tenant ON broker_integration.broker_accounts(tenant_id); CREATE INDEX IF NOT EXISTS idx_broker_accounts_user ON broker_integration.broker_accounts(user_id); CREATE INDEX IF NOT EXISTS idx_broker_accounts_type ON broker_integration.broker_accounts(broker_type); CREATE INDEX IF NOT EXISTS idx_broker_accounts_status ON broker_integration.broker_accounts(connection_status); CREATE INDEX IF NOT EXISTS idx_broker_accounts_active ON broker_integration.broker_accounts(user_id, is_active, connection_status) WHERE is_active = TRUE; CREATE INDEX IF NOT EXISTS idx_broker_accounts_default ON broker_integration.broker_accounts(user_id, is_default) WHERE is_default = TRUE; CREATE INDEX IF NOT EXISTS idx_broker_accounts_sync ON broker_integration.broker_accounts(last_sync_at) WHERE is_active = TRUE AND connection_status = 'connected'; -- Funcion de timestamp CREATE OR REPLACE FUNCTION broker_integration.update_broker_timestamp() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at := NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- Trigger para updated_at DROP TRIGGER IF EXISTS broker_account_updated_at ON broker_integration.broker_accounts; CREATE TRIGGER broker_account_updated_at BEFORE UPDATE ON broker_integration.broker_accounts FOR EACH ROW EXECUTE FUNCTION broker_integration.update_broker_timestamp(); -- Trigger para asegurar solo una cuenta default CREATE OR REPLACE FUNCTION broker_integration.ensure_single_default_broker() RETURNS TRIGGER AS $$ BEGIN IF NEW.is_default = TRUE THEN UPDATE broker_integration.broker_accounts SET is_default = FALSE WHERE user_id = NEW.user_id AND id != NEW.id AND is_default = TRUE; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS broker_account_single_default ON broker_integration.broker_accounts; CREATE TRIGGER broker_account_single_default BEFORE INSERT OR UPDATE OF is_default ON broker_integration.broker_accounts FOR EACH ROW WHEN (NEW.is_default = TRUE) EXECUTE FUNCTION broker_integration.ensure_single_default_broker(); -- Vista de cuentas activas CREATE OR REPLACE VIEW broker_integration.v_active_accounts AS SELECT id, user_id, broker_type, broker_name, account_type, account_number, connection_status, balance, equity, leverage, currency, open_positions, is_default, last_sync_at FROM broker_integration.broker_accounts WHERE is_active = TRUE ORDER BY is_default DESC, broker_name; -- RLS Policies ALTER TABLE broker_integration.broker_accounts ENABLE ROW LEVEL SECURITY; CREATE POLICY broker_accounts_tenant ON broker_integration.broker_accounts FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY broker_accounts_user ON broker_integration.broker_accounts FOR ALL USING (user_id = current_setting('app.current_user_id', true)::UUID); -- Grants GRANT USAGE ON SCHEMA broker_integration TO trading_app; GRANT SELECT, INSERT, UPDATE, DELETE ON broker_integration.broker_accounts TO trading_app; GRANT SELECT ON broker_integration.broker_accounts TO trading_readonly; GRANT SELECT ON broker_integration.v_active_accounts TO trading_app;