-- ============================================================================ -- SCHEMA: trading -- TABLE: bots -- DESCRIPTION: Agentes de trading automatizado (Atlas, Orion, Nova) -- VERSION: 1.0.0 -- CREATED: 2026-01-16 -- SPRINT: Sprint 3 - DDL Implementation Roadmap Q1-2026 -- ============================================================================ -- Enum para estado del bot DO $$ BEGIN CREATE TYPE trading.bot_status AS ENUM ( 'inactive', -- Inactivo 'starting', -- Iniciando 'running', -- Ejecutando 'paused', -- Pausado temporalmente 'stopping', -- Deteniendo 'error', -- Error 'maintenance' -- En mantenimiento ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Enum para nivel de riesgo del bot DO $$ BEGIN CREATE TYPE trading.bot_risk_level AS ENUM ( 'conservative', -- Conservador (bajo riesgo) 'moderate', -- Moderado 'aggressive', -- Agresivo 'ultra_aggressive' -- Ultra agresivo ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Tabla de Bots de Trading CREATE TABLE IF NOT EXISTS trading.bots ( -- Identificadores id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, strategy_id UUID REFERENCES trading.strategies(id), -- Identificacion code VARCHAR(20) NOT NULL, -- 'ATLAS', 'ORION', 'NOVA' name VARCHAR(100) NOT NULL, description TEXT, avatar_url TEXT, -- Tipo y version version VARCHAR(20) NOT NULL DEFAULT '1.0.0', risk_level trading.bot_risk_level NOT NULL DEFAULT 'moderate', -- Estado status trading.bot_status NOT NULL DEFAULT 'inactive', status_message TEXT, last_status_change TIMESTAMPTZ DEFAULT NOW(), -- Conexion MT4/MT5 mt_account_id VARCHAR(50), mt_server VARCHAR(100), mt_connected BOOLEAN NOT NULL DEFAULT FALSE, mt_last_heartbeat TIMESTAMPTZ, -- Configuracion de trading trading_config JSONB NOT NULL DEFAULT '{ "enabled": false, "symbols": ["EURUSD", "GBPUSD", "XAUUSD"], "timeframes": ["H1", "H4"], "max_trades_per_day": 5, "max_concurrent_positions": 3, "trading_hours": { "enabled": false, "start": "08:00", "end": "20:00", "timezone": "America/New_York" } }'::JSONB, -- Configuracion de riesgo risk_config JSONB NOT NULL DEFAULT '{ "capital_allocation": 10000, "risk_per_trade_percent": 1, "max_daily_loss_percent": 3, "max_drawdown_percent": 10, "lot_size_mode": "fixed", "fixed_lot_size": 0.1, "use_compounding": false }'::JSONB, -- Configuracion de senales signal_config JSONB NOT NULL DEFAULT '{ "min_confidence": 70, "require_confirmation": true, "confirmation_timeframe": "M15", "filter_by_trend": true, "filter_by_volatility": true }'::JSONB, -- Capital y rendimiento initial_capital DECIMAL(15, 2) NOT NULL DEFAULT 0, current_capital DECIMAL(15, 2) NOT NULL DEFAULT 0, allocated_capital DECIMAL(15, 2) NOT NULL DEFAULT 0, available_margin DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Estadisticas de rendimiento total_trades INTEGER NOT NULL DEFAULT 0, winning_trades INTEGER NOT NULL DEFAULT 0, losing_trades INTEGER NOT NULL DEFAULT 0, win_rate DECIMAL(5, 2) DEFAULT 0, profit_factor DECIMAL(10, 4) DEFAULT 0, sharpe_ratio DECIMAL(10, 4) DEFAULT 0, max_drawdown DECIMAL(15, 4) DEFAULT 0, max_drawdown_percent DECIMAL(5, 2) DEFAULT 0, -- P&L total_profit DECIMAL(15, 4) NOT NULL DEFAULT 0, total_loss DECIMAL(15, 4) NOT NULL DEFAULT 0, net_profit DECIMAL(15, 4) NOT NULL DEFAULT 0, daily_pnl DECIMAL(15, 4) NOT NULL DEFAULT 0, weekly_pnl DECIMAL(15, 4) NOT NULL DEFAULT 0, monthly_pnl DECIMAL(15, 4) NOT NULL DEFAULT 0, -- Rendimiento porcentual total_return_percent DECIMAL(10, 4) DEFAULT 0, daily_return_percent DECIMAL(10, 4) DEFAULT 0, weekly_return_percent DECIMAL(10, 4) DEFAULT 0, monthly_return_percent DECIMAL(10, 4) DEFAULT 0, -- Posiciones actuales open_positions_count INTEGER NOT NULL DEFAULT 0, open_positions_value DECIMAL(15, 4) NOT NULL DEFAULT 0, unrealized_pnl DECIMAL(15, 4) NOT NULL DEFAULT 0, -- Ultima actividad last_trade_at TIMESTAMPTZ, last_signal_at TIMESTAMPTZ, last_error_at TIMESTAMPTZ, last_error_message TEXT, -- Subscribers (usuarios que invierten en este bot) subscriber_count INTEGER NOT NULL DEFAULT 0, total_aum DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Assets Under Management -- Disponibilidad is_public BOOLEAN NOT NULL DEFAULT FALSE, is_premium BOOLEAN NOT NULL DEFAULT FALSE, min_investment DECIMAL(15, 2) DEFAULT 100, max_investment DECIMAL(15, 2), -- Comisiones performance_fee_percent DECIMAL(5, 2) DEFAULT 20, -- Fee sobre ganancias management_fee_percent DECIMAL(5, 2) DEFAULT 0, -- Fee anual sobre AUM -- Metadata tags VARCHAR(50)[], metadata JSONB DEFAULT '{}'::JSONB, -- Timestamps started_at TIMESTAMPTZ, stopped_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Constraints CONSTRAINT bots_unique_code UNIQUE (tenant_id, code), CONSTRAINT bots_capital_check CHECK (current_capital >= 0), CONSTRAINT bots_win_rate_check CHECK (win_rate BETWEEN 0 AND 100) ); COMMENT ON TABLE trading.bots IS 'Agentes de trading automatizado con configuracion y estadisticas'; COMMENT ON COLUMN trading.bots.code IS 'Codigo unico del bot: ATLAS (conservador), ORION (moderado), NOVA (agresivo)'; -- Indices CREATE INDEX IF NOT EXISTS idx_bots_tenant ON trading.bots(tenant_id); CREATE INDEX IF NOT EXISTS idx_bots_code ON trading.bots(tenant_id, code); CREATE INDEX IF NOT EXISTS idx_bots_status ON trading.bots(status); CREATE INDEX IF NOT EXISTS idx_bots_running ON trading.bots(tenant_id, status) WHERE status = 'running'; CREATE INDEX IF NOT EXISTS idx_bots_public ON trading.bots(is_public, net_profit DESC) WHERE is_public = TRUE; CREATE INDEX IF NOT EXISTS idx_bots_performance ON trading.bots(tenant_id, total_return_percent DESC); -- Trigger para updated_at DROP TRIGGER IF EXISTS bot_updated_at ON trading.bots; CREATE TRIGGER bot_updated_at BEFORE UPDATE ON trading.bots FOR EACH ROW EXECUTE FUNCTION trading.update_trading_timestamp(); -- Trigger para registrar cambios de estado CREATE OR REPLACE FUNCTION trading.log_bot_status_change() RETURNS TRIGGER AS $$ BEGIN IF NEW.status IS DISTINCT FROM OLD.status THEN NEW.last_status_change := NOW(); IF NEW.status = 'running' AND OLD.status != 'running' THEN NEW.started_at := NOW(); ELSIF NEW.status IN ('inactive', 'stopped') AND OLD.status = 'running' THEN NEW.stopped_at := NOW(); ELSIF NEW.status = 'error' THEN NEW.last_error_at := NOW(); END IF; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS bot_status_change ON trading.bots; CREATE TRIGGER bot_status_change BEFORE UPDATE OF status ON trading.bots FOR EACH ROW EXECUTE FUNCTION trading.log_bot_status_change(); -- Funcion para actualizar estadisticas del bot CREATE OR REPLACE FUNCTION trading.recalculate_bot_stats(p_bot_id UUID) RETURNS VOID AS $$ DECLARE v_stats RECORD; BEGIN SELECT COUNT(*) AS total, COUNT(*) FILTER (WHERE profit_loss > 0) AS wins, COUNT(*) FILTER (WHERE profit_loss < 0) AS losses, COALESCE(SUM(profit_loss) FILTER (WHERE profit_loss > 0), 0) AS total_profit, COALESCE(SUM(ABS(profit_loss)) FILTER (WHERE profit_loss < 0), 0) AS total_loss, COALESCE(SUM(profit_loss), 0) AS net_pnl INTO v_stats FROM trading.positions WHERE bot_id = p_bot_id AND status IN ('closed', 'stopped', 'target_hit'); UPDATE trading.bots SET total_trades = v_stats.total, winning_trades = v_stats.wins, losing_trades = v_stats.losses, win_rate = CASE WHEN v_stats.total > 0 THEN (v_stats.wins::DECIMAL / v_stats.total * 100) ELSE 0 END, profit_factor = CASE WHEN v_stats.total_loss > 0 THEN v_stats.total_profit / v_stats.total_loss ELSE 0 END, total_profit = v_stats.total_profit, total_loss = v_stats.total_loss, net_profit = v_stats.net_pnl, total_return_percent = CASE WHEN initial_capital > 0 THEN (v_stats.net_pnl / initial_capital * 100) ELSE 0 END WHERE id = p_bot_id; END; $$ LANGUAGE plpgsql; -- Vista de bots activos CREATE OR REPLACE VIEW trading.v_active_bots AS SELECT id, tenant_id, code, name, description, avatar_url, risk_level, status, win_rate, profit_factor, total_return_percent, max_drawdown_percent, subscriber_count, total_aum, is_premium, min_investment FROM trading.bots WHERE is_public = TRUE AND status IN ('running', 'paused') ORDER BY total_return_percent DESC; -- Vista de rendimiento de bots CREATE OR REPLACE VIEW trading.v_bot_performance AS SELECT b.id, b.code, b.name, b.status, b.total_trades, b.win_rate, b.profit_factor, b.sharpe_ratio, b.net_profit, b.total_return_percent, b.daily_return_percent, b.weekly_return_percent, b.monthly_return_percent, b.max_drawdown_percent, b.open_positions_count, b.unrealized_pnl, b.last_trade_at FROM trading.bots b ORDER BY b.tenant_id, b.total_return_percent DESC; -- RLS Policy para multi-tenancy ALTER TABLE trading.bots ENABLE ROW LEVEL SECURITY; CREATE POLICY bots_tenant_isolation ON trading.bots FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Grants GRANT SELECT, INSERT, UPDATE, DELETE ON trading.bots TO trading_app; GRANT SELECT ON trading.bots TO trading_readonly; GRANT SELECT ON trading.v_active_bots TO trading_app; GRANT SELECT ON trading.v_bot_performance TO trading_app; GRANT EXECUTE ON FUNCTION trading.recalculate_bot_stats TO trading_app;