Si has comprado un cBot y lo ejecutas en tu ordenador, dejas de operar cada vez que cierras el portátil, se va la luz o Windows decide actualizarse. Este artículo explica cómo montar un servidor que lo ejecute de forma continua, aislado, vigilado y reproducible — el mismo esquema con el que mantenemos cinco cBots en marcha 24/5 alimentando las estadísticas en vivo de nuestras fichas.
No es teoría: cada decisión de aquí viene de algo que nos costó un incidente.
0. ¿Por qué no cTrader Cloud, que es gratis?
Es la primera pregunta y merece respuesta directa. cTrader Cloud funciona bien para muchos casos, pero tiene una limitación que quizá te importe:
Los cBots ejecutados en la nube de cTrader no tienen acceso a internet.
Eso significa que cualquier notificación externa —Telegram, webhooks, tu propio servidor— no funciona. En nuestros logs se ve así:
📣 avisos activos → chat 123456789 · presupuesto 200/día
⚠ aviso NO entregado: HTTP 503
La configuración es correcta; lo que falla es la salida de red. Notifications.SendEmail() tampoco funciona allí. Si te basta con mirar la plataforma de vez en cuando, Cloud es perfecto y te ahorras esta guía. Si quieres que el bot te avise al móvil cuando abre, cierra o salta un guard de riesgo, necesitas ejecutarlo tú.
Las tres vías con acceso a red son: tu ordenador, cTrader CLI en tu propio servidor (lo que explica esta guía) y una VPS con cTrader de escritorio por escritorio remoto (más caro y frágil).
1. Qué necesitas
| mínimo | recomendado | por qué | |
|---|---|---|---|
| RAM | 2 GB | 4 GB | cada motor .NET come 300-600 MB. Con 2 GB corres uno; con 4, tres o cuatro |
| CPU | 1 core | 2 cores | los cBots son event-driven: casi 0% en reposo, picos cortos al cierre de vela |
| Disco | 20 GB | 40 GB | la imagen Docker pesa ~1 GB; el resto son logs |
| SO | Ubuntu 22.04 | Ubuntu 22.04/24.04 LTS | es donde la CLI está probada |
| Ubicación | — | cerca del bróker | Londres o Nueva York según tu bróker; ahorra latencia |
Cuesta entre 5 y 15 €/mes en cualquier proveedor (Hetzner, Contabo, DigitalOcean, Hostinger…).
No necesitas datos históricos. A diferencia de un backtest, el motor en vivo pide las velas de calentamiento al bróker al conectar. Un paso menos.
2. Instalar Docker
ssh root@TU_IP
apt update && apt upgrade -y
apt install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
> /etc/apt/sources.list.d/docker.list
apt update && apt install -y docker-ce docker-ce-cli containerd.io
docker run --rm hello-world # debe imprimir "Hello from Docker!"
3. La imagen: ctrader-console
cTrader publica una CLI oficial que ejecuta cBots sin interfaz gráfica. Es la pieza clave.
docker pull spotware/ctrader-console:latest
⛔ Y aquí va el consejo más importante de toda la guía: fija la versión
# 1) mira qué versión te has bajado
docker images spotware/ctrader-console
# 2) etiquétala como tuya y NO vuelvas a hacer pull sobre ella
docker tag spotware/ctrader-console:latest ctrader-console:certified
# 3) guarda una copia en disco, por si esa versión desaparece del registro
docker save ctrader-console:certified | gzip > /root/ctrader-console-backup.tar.gz
Por qué. A nosotros nos pasó esto: la versión 5.8.3 empezó a rechazar las modificaciones de stop loss (InvalidStopLossTakeProfit) — 1.090 rechazos de 2.676 intentos, con exactamente la misma entrada que en 5.7.10 daba 0 de 3.780. Y la 5.9 directamente hace caer la instancia de forma determinista. Nosotros seguimos pineados a 5.7.10, y esa versión ya no está en el registro público: si no la hubiéramos guardado, la habríamos perdido.
Un docker pull automático puede cambiarte el motor bajo un bot que está operando con tu dinero. Usa :certified, nunca :latest.
4. Estructura de ficheros
mkdir -p /opt/cbots/{mibot,logs}
cd /opt/cbots
Necesitas tres cosas por cada cBot:
/opt/cbots/mibot/
├── bot.algo ← el fichero que compraste/compilaste
├── params.cbotset ← preset de parámetros (opcional)
└── ctrader-cli.pwd ← la contraseña de tu cTID, permisos 600
# el fichero de contraseña, con permisos restrictivos desde el primer momento
install -m600 /dev/stdin /opt/cbots/mibot/ctrader-cli.pwd <<< 'TU_PASSWORD_CTID'
⚠️ Una sola copia de la contraseña. Nosotros llegamos a tener el mismo password duplicado en cinco directorios distintos. Cada copia es un sitio más donde puede filtrarse y uno más que actualizar cuando cambie. Si tienes varios bots, deja el fichero en un único sitio y móntalo en todos los contenedores en modo lectura.
5. La configuración de cada bot
/opt/cbots/mibot/run.env:
# ── Cuenta ────────────────────────────────────────────────
[email protected]
BROKER=Skilling # el nombre EXACTO del bróker en cTrader
ACCOUNT=1234567 # número de cuenta
# ── Gráfico anfitrión ─────────────────────────────────────
SYMBOL=GOLD # ⚠️ LEE EL AVISO DE ABAJO
PERIOD=m1
# ── Recursos ──────────────────────────────────────────────
MEMORY=1g
CPUSET=1
⚠️ El SYMBOL es el nombre LITERAL de tu bróker, no el que use el cBot
Este es el error que más veces mata un despliegue. El gráfico anfitrión lo resuelve la plataforma, no el cBot: si pones un símbolo que tu bróker no tiene, el contenedor no arranca — y con reinicio automático, entras en un bucle infinito.
En Skilling el oro se llama GOLD, no XAUUSD. El DAX es Germany 40, con espacio. En IC Markets, DE40 y el petróleo XTIUSD; en Skilling, OIL WTI. Míralo en el buscador de símbolos de tu plataforma y cópialo tal cual.
(Nosotros tuvimos cuatro de cinco configuraciones apuntando a XAUUSD en un bróker que no lo tiene. Se descubrió en la revisión previa, antes de arrancar. De ahí el paso 6.)
El PERIOD debe ser el del backtest certificado
Si el resultado que compraste se midió en m1, pon m1. Cambiarlo altera el ritmo al que el bot evalúa y deja de reproducir la curva publicada.
6. Comprobación previa: valida antes de arrancar
La CLI trae dos comandos que cuestan segundos y ahorran horas:
docker run --rm -v /opt/cbots/mibot:/mnt ctrader-console:certified \
accounts --ctid [email protected] --password-file /mnt/ctrader-cli.pwd
docker run --rm -v /opt/cbots/mibot:/mnt ctrader-console:certified \
symbols --ctid [email protected] --password-file /mnt/ctrader-cli.pwd \
--broker Skilling --account 1234567
El primero confirma que tus credenciales funcionan; el segundo te da la lista exacta de símbolos de tu bróker, que es de donde debes copiar el SYMBOL y con la que comprobar que los mercados de tu cBot existen ahí.
Hazlo siempre antes de arrancar nada. Un bot que arranca contra una cuenta mal configurada no falla con un error claro: se queda en bucle.
7. El lanzador
/opt/cbots/run_cbot.sh:
#!/usr/bin/env bash
set -euo pipefail
BOT="$1"
DIR="/opt/cbots/$BOT"
set -a; source "$DIR/run.env"; set +a
docker rm -f "cbot_$BOT" 2>/dev/null || true
exec docker run --rm --name "cbot_$BOT" \
-v "$DIR:/mnt/Robots:ro" \
--memory="${MEMORY:-1g}" --memory-swap="${MEMORY:-1g}" \
--cpuset-cpus="${CPUSET:-0}" --cpu-shares=128 \
--log-driver=json-file --log-opt max-size=50m --log-opt max-file=3 \
ctrader-console:certified \
run /mnt/Robots/bot.algo \
--ctid "$CTID" --password-file /mnt/Robots/ctrader-cli.pwd \
--broker "$BROKER" --account "$ACCOUNT" \
--symbol "$SYMBOL" --period "$PERIOD" \
--exit-on-stop
chmod +x /opt/cbots/run_cbot.sh
Detalles que importan:
--memorycon--memory-swapigual — sin esto, un contenedor con fuga de memoria se come el swap y tumba el servidor entero. Empieza generoso (1 GB) y apriétalo después a (uso medido × 1,6).--cpu-shares=128(el valor por defecto es 1024) — peso bajo, para que si algo más compite por CPU, el bot ceda en vez de ahogar la máquina.max-size=50m— sin límite de log, Docker te llena el disco en semanas.:ro— el contenedor no necesita escribir en tus ficheros.
8. Prueba piloto: mide, no estimes
Antes de convertirlo en servicio, arráncalo a mano:
free -h # memoria ANTES
bash /opt/cbots/run_cbot.sh mibot # y déjalo correr
En otra sesión SSH:
docker stats --no-stream cbot_mibot # RAM y CPU reales
free -h # cuánto ha consumido de verdad
Qué debes ver para dar el piloto por bueno:
- Conecta sin error de autenticación.
- Todos los mercados del cBot resuelven. Busca en el log las líneas de resolución de símbolos; si alguna dice que no encuentra un símbolo, esa estrategia queda desactivada y no operará — y el bot seguirá funcionando con las demás, produciendo un resultado que no es el que compraste.
- RAM estable entre 300 y 600 MB.
- CPU cercano a 0 en reposo.
⚠️ Cero operaciones en las primeras horas NO es un fallo. Un cBot de cartera con marcos de 1h a 8h puede hacer menos de dos operaciones al día. Para saber si está vivo, mira el consumo del contenedor, no el log.
9. Convertirlo en servicio con systemd
/etc/systemd/system/[email protected]:
[Unit]
Description=cTrader cBot %i
After=docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/opt/cbots/run_cbot.sh %i
ExecStop=/usr/bin/docker stop -t 30 cbot_%i
TimeoutStopSec=45
Restart=always
RestartSec=30
StartLimitBurst=5
StartLimitIntervalSec=600
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now cbot@mibot
journalctl -u cbot@mibot -f
El StartLimitBurst=5 no es decorativo. Con Restart=always a secas, una cuenta mal configurada reintentaría eternamente en silencio. Con el límite, tras 5 intentos en 10 minutos el servicio pasa a failed — y un servicio en failed sí se puede vigilar.
Un solo fichero de servicio sirve para todos tus bots: cbot@gold, cbot@nasdaq… Toda la diferencia vive en cada run.env, así que no tienes cinco ficheros que se desincronicen.
10. Vigilancia: lo que de verdad te va a salvar
Aquí va la lección más cara que aprendimos. Teníamos un bot con OnFailure configurado para avisarnos si el servicio caía. Estuvo cinco semanas sin operar y no avisó, porque el servicio nunca cayó: su token había caducado, así que arrancaba, fallaba al autenticar, reintentaba, y systemd lo veía "activo".
Un servicio "activo" no significa un bot que opera. Y un monitor callado se lee como salud.
Vigila dos cosas distintas:
a) Que el proceso esté vivo — por consumo, no por logs
#!/usr/bin/env bash
# /opt/cbots/watchdog.sh — cada 20 minutos por cron
for BOT in mibot; do
if ! docker ps --format '{{.Names}}' | grep -q "^cbot_$BOT$"; then
curl -s "https://api.telegram.org/bot$TG_TOKEN/sendMessage" \
-d chat_id="$TG_CHAT" -d text="⚠️ cbot_$BOT NO está corriendo"
continue
fi
RX=$(docker stats --no-stream --format '{{.NetIO}}' "cbot_$BOT")
echo "$(date -Is) $BOT $RX" >> /opt/cbots/logs/heartbeat.log
done
Compara los bytes recibidos entre lecturas: si no crecen, el bot no está hablando con el bróker aunque el contenedor exista. Contar líneas de log no sirve — un bot sano puede pasar horas sin escribir nada.
b) Que la cuenta se mueva — desde fuera
Lo ideal es un segundo proceso que consulte tu cuenta por la API del bróker (solo lectura) y te mande un parte diario: saldo, posiciones abiertas, operaciones del día. Así detectas el caso peligroso: el bot corre, pero no opera.
11. Mantenimiento
# ver qué hace ahora mismo
journalctl -u cbot@mibot -f
# reiniciarlo (cierra limpio, con 30s de margen)
systemctl restart cbot@mibot
# pararlo del todo
systemctl stop cbot@mibot
# ¿cuánto consume?
docker stats --no-stream
# actualizar el .algo a una versión nueva del cBot
systemctl stop cbot@mibot
cp nuevo.algo /opt/cbots/mibot/bot.algo
systemctl start cbot@mibot
Al actualizar el cBot
- Guarda el
.algoanterior antes de sobrescribirlo. Volver atrás debe costar uncp. - Comprueba el log de arranque tras actualizar: que resuelven todos los símbolos y que el preset es el que esperas.
- No actualices la imagen de cTrader al mismo tiempo. Si algo se rompe, no sabrás cuál de los dos cambios fue.
12. Errores que vas a cometer (nosotros los cometimos)
| error | qué verás | arreglo |
|---|---|---|
SYMBOL que el bróker no tiene | el contenedor arranca y muere en bucle | cópialo de symbols (§6) |
:latest en vez de versión fija | un día el bot deja de modificar stops, o cae | :certified (§3) |
Restart=always sin StartLimitBurst | reintentos infinitos en silencio | §9 |
Sin --memory | una fuga tumba el servidor entero | §7 |
Sin max-size en los logs | disco lleno en semanas | §7 |
| Vigilar por logs | falsos positivos y negativos | vigila consumo (§10) |
Fiarte de systemctl is-active | 5 semanas sin operar y "activo" | vigila la cuenta (§10b) |
| Esperar Telegram desde cTrader Cloud | HTTP 503 | no es posible: usa tu VPS (§0) |
PERIOD distinto al certificado | curva distinta a la publicada | §5 |
13. ¿Y si tengo varios cBots?
Un contenedor por cBot, siempre. Es tentador meter varios en uno, pero:
- Si uno falla, caen todos.
- No puedes reiniciar uno sin tocar los demás.
- No puedes limitar la memoria por bot.
- Los logs se mezclan y dejan de ser diagnosticables.
Con un contenedor por bot, systemctl restart cbot@gold no molesta a cbot@nasdaq. Nosotros corremos cinco así, en unos 860 MB en total.
Reparte los cores con CPUSET en cada run.env y arranca de uno en uno, dejando el primero 24 horas estable antes de añadir el segundo.
Resumen
# 1. Docker apt install docker-ce
# 2. Imagen FIJADA docker tag …:latest ctrader-console:certified
# 3. Ficheros bot.algo + run.env + ctrader-cli.pwd (600)
# 4. Comprobar ANTES accounts + symbols
# 5. Piloto en primer plano medir RAM/CPU reales
# 6. systemd cbot@mibot con StartLimitBurst
# 7. Vigilar DOS cosas proceso vivo + cuenta que se mueve
Con esto tienes tus cBots operando de forma continua, aislados unos de otros, con la versión del motor congelada, con límites de recursos y con avisos que llegan a tu móvil — que es justo lo que cTrader Cloud no puede darte.
Guía basada en el montaje que mantenemos en producción para las estadísticas en vivo de nuestros productos: cinco cBots, cinco cuentas, funcionando 24/5 desde julio de 2026.