trading-platform-database-v2/ddl/schemas/trading/tables/003_bots.sql

329 lines
10 KiB
PL/PgSQL

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