329 lines
10 KiB
PL/PgSQL
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;
|