<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jesus Corredor</title>
    <description>The latest articles on DEV Community by Jesus Corredor (@jesusaco).</description>
    <link>https://dev.to/jesusaco</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4089844%2F43b302f3-0c51-4566-95bd-2b72f6657850.png</url>
      <title>DEV Community: Jesus Corredor</title>
      <link>https://dev.to/jesusaco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jesusaco"/>
    <language>en</language>
    <item>
      <title>El día que descubrí que mi bot no tenía las protecciones que creía tener</title>
      <dc:creator>Jesus Corredor</dc:creator>
      <pubDate>Sun, 04 Oct 2026 21:08:26 +0000</pubDate>
      <link>https://dev.to/jesusaco/el-dia-que-descubri-que-mi-bot-no-tenia-las-protecciones-que-creia-tener-2h1g</link>
      <guid>https://dev.to/jesusaco/el-dia-que-descubri-que-mi-bot-no-tenia-las-protecciones-que-creia-tener-2h1g</guid>
      <description>&lt;p&gt;Diario de un bot que opera con dinero real — Entrada #3&lt;/p&gt;

&lt;p&gt;Durante varios días, mi bot de trading creyó tener órdenes de stop-loss que en realidad no existían en el exchange.&lt;/p&gt;

&lt;p&gt;No fallaban de forma visible. En los registros, cada posición tenía su stop "gestionado". El bot estaba convencido de que cada operación tenía su red de seguridad. Pero esa red no estaba donde debía estar: en Binance.&lt;/p&gt;

&lt;p&gt;En ese tiempo, posiciones apalancadas —donde una caída fuerte puede liquidarte— operaron sin la protección que yo creía activa. Si el bot se caía en el momento equivocado, no había nada abajo.&lt;/p&gt;

&lt;p&gt;Esta es la tercera entrada de la serie, y la más inquietante. No fue un bug ruidoso ni un componente fantasma. Fue peor: la plataforma cambió las reglas —lo anunció, pero yo no lo vi— y mi bot siguió reportando que todo estaba bajo control mientras, en el exchange, las posiciones corrían desnudas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El síntoma: un error que no debería existir&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Todo comenzó con un mensaje nuevo en los registros al intentar colocar un stop:&lt;br&gt;
&lt;strong&gt;HTTP 400 | code: -4120 | Order type not supported for this endpoint.&lt;br&gt;
Please use the Algo Order API endpoints instead.&lt;/strong&gt;&lt;br&gt;
Mi código enviaba la orden de la forma que, según tutoriales y ejemplos, era la correcta. Entonces, ¿por qué Binance rechazaba algo que medio internet daba por válido?&lt;/p&gt;

&lt;p&gt;La pista incómoda: mi código estaba bien escrito, pero el contrato había cambiado. Cuando tu petición es correcta y aun así la rechazan, el problema no está solo en tu lado: está en que las reglas del otro extremo ya no son las mismas.&lt;br&gt;
&lt;strong&gt;La investigación: lo que el bot creía vs. lo que pasaba&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx0pzkfnp6ym1h6yhxgsn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx0pzkfnp6ym1h6yhxgsn.png" alt=" " width="586" height="156"&gt;&lt;/a&gt;&lt;br&gt;
Binance había migrado las órdenes condicionales (stop-loss, take-profit, trailing stop) al nuevo "Algo Order API". El endpoint viejo quedó bloqueado para esos casos. El cambio estaba en el changelog oficial semanas antes, pero yo no lo vi.&lt;/p&gt;

&lt;p&gt;(Nota: no fui el único en chocar con esto. Cuando Binance hizo el cambio —a finales de 2025—, frameworks de trading enteros como Freqtrade y nautilus_trader amanecieron con exactamente este error, documentado en sus repos de GitHub. En mi caso llegué tarde a la fiesta: mi bot empezó a operar después del cambio, así que heredé el problema sin saberlo. Mi código enviaba las órdenes al endpoint viejo —que ya no servía para stops— desde el primer día, y yo creía que las protecciones se colocaban. Hasta que un día revisé de verdad y descubrí que nunca habían estado ahí.)&lt;/p&gt;

&lt;p&gt;Lo más grave no era el error -4120 visible. Era lo que pasaba por debajo: el bot sí calculaba y llevaba el stop en su propio estado interno —tenía su nivel de salida bien definido—, pero ese stop vivía solo en la memoria del bot, no en el exchange. En la interfaz de Binance, el campo de protección de esas posiciones decía, literalmente, -- / --. Mientras el bot corriera, la gestión interna disimulaba el problema. Pero un reinicio, una caída, un corte de red en el momento equivocado, y la posición se quedaba sin absolutamente nada que la protegiera. Yo creía tener una red de seguridad en dos capas; en realidad tenía media.&lt;br&gt;
&lt;strong&gt;Por qué esto es peor que un bug normal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Un bug propio lo puedes leer, probar y arreglar. Pero cuando dependes de una API externa, una parte de tu sistema vive en un servidor que no controlas. Pueden cambiar un endpoint, un parámetro o una validación, y tu código —sin modificar una sola línea— empieza a comportarse distinto.&lt;/p&gt;

&lt;p&gt;El fallo más peligroso no es el error claro, sino la falsa confianza: cuando crees que tienes una protección y en realidad no está donde importa. Un stop que vive solo en tu código, pero no en el exchange, es exactamente eso — una red pintada en el suelo.&lt;br&gt;
&lt;strong&gt;El fix: migrar y verificar de verdad&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La solución tuvo dos pasos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrar al nuevo endpoint:&lt;/strong&gt; reescribir la lógica de stops y trailing para usar /algoOrder, con los parámetros que ahora espera. Marqué cada cambio con la etiqueta [ALGO-MIGRATION] para que en el futuro yo —o quien lea el código— sepa qué se movió y por qué. Para las posiciones que ya estaban corriendo sin stop nativo, escribí un script que las recorrió una por una y les colocó retroactivamente la protección que les faltaba.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verificar la existencia real de la orden:&lt;/strong&gt; dejar de confiar en que "el bot lo tiene gestionado" o en que "no dio error" signifique que la protección existe. Ahora el bot consulta directamente al exchange si la orden está viva. Solo cuando Binance confirma que el stop está ahí, el bot lo da por colocado. Hay una diferencia enorme entre "mi petición no dio error" y "la orden está viva en el exchange". El bug vivía justo en esa brecha.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La lección para llevarte&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"No dio error" no es lo mismo que "funcionó". Verifica el efecto, no la ausencia de queja.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Si construyes sobre APIs de terceros —y hoy casi todo software lo hace—, esta historia deja tres recordatorios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No controlas tus dependencias externas&lt;/strong&gt;. El código que llama a una API vive a medias en un servidor ajeno. Pueden cambiar las reglas —a veces lo avisan en un changelog que nadie lee, a veces el aviso pasa desapercibido— y tu código intacto empezará a comportarse distinto. Cuando el comportamiento cambia pero tu código no, mira hacia afuera: al otro extremo de la conexión, y a los canales donde esa plataforma anuncia sus cambios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lee los changelogs de tu exchange&lt;/strong&gt; — no hacerlo es un error muy común. Binance (y cualquier exchange serio) publica sus cambios de API en un changelog oficial, a veces con semanas de aviso. Este cambio estaba anunciado ahí. Pero casi nadie lo lee: montas el bot, funciona, y te olvidas de que del otro lado hay un equipo cambiando cosas. Yo no lo leía. Freqtrade y nautilus_trader tienen equipos enteros y aun así los pilló. La información estaba disponible; solo que nadie la miró a tiempo. Revisar el changelog cada cierto tiempo —o, mejor, automatizar una alerta cuando cambie— convierte una sorpresa silenciosa en un aviso con margen para reaccionar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verifica el efecto, no la respuesta&lt;/strong&gt;. No asumas que algo se hizo porque la llamada no lanzó una excepción, ni porque tu propio código "lo tiene controlado". Si la operación importa —una orden de protección, un pago, un cambio de permisos— confirma que el efecto realmente ocurrió en el sistema externo, no en tu versión de la realidad.&lt;/p&gt;

&lt;p&gt;Ese día entendí que lo más frágil de mi sistema no era mi código, sino el supuesto de que la API seguiría igual que la última vez que la revisé —y que mi estado interno reflejaba el del exchange. Ninguna de las dos cosas era cierta. Y mi bot había estado operando con una red de seguridad imaginaria.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;En la próxima entrada&lt;/strong&gt; empieza la parte más incómoda de todo el viaje. Descubrí que mi herramienta de backtest —la que usaba para decidir si una estrategia servía— me estaba mintiendo. Tenía un "edge" fabuloso… en un horario en el que el bot ni siquiera operaba. Cómo una sola línea mal ajustada en el tiempo puede convertir meses de "resultados prometedores" en un espejismo.&lt;/p&gt;

&lt;p&gt;¿Alguna vez una API de terceros cambió bajo tus pies y te enteraste tarde? ¿Cómo lo detectaste? Cuéntamelo en los comentarios.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>trading</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Por qué getenv("X") or default es una trampa</title>
      <dc:creator>Jesus Corredor</dc:creator>
      <pubDate>Sun, 13 Sep 2026 20:10:05 +0000</pubDate>
      <link>https://dev.to/jesusaco/por-que-getenvx-or-default-es-una-trampa-2ab6</link>
      <guid>https://dev.to/jesusaco/por-que-getenvx-or-default-es-una-trampa-2ab6</guid>
      <description>&lt;p&gt;Diario de un bot que opera con dinero real — Entrada #2:&lt;/p&gt;

&lt;p&gt;En la entrada anterior conté cómo un componente de mi sistema estuvo meses operando con un capital que nadie le asignó — un "par fantasma" que creía haber eliminado y seguía vivo. Toda la causa cabía en una línea de código. Una línea que probablemente tienes en tu proyecto ahora mismo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqhn1pv9gcuotbchilp1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqhn1pv9gcuotbchilp1.png" alt=" " width="414" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se ve inofensiva. Se ve hasta responsable — "si la variable no está, uso un valor por defecto y el programa no se rompe". Ese razonamiento es la trampa. Te cuento por qué.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qué hace ese or&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;En cristiano: "usa el valor de la variable; si no hay valor, usa &lt;strong&gt;133&lt;/strong&gt;".&lt;/p&gt;

&lt;p&gt;Suena bien. El problema es qué cuenta como "no hay valor". Si por accidente borras esa variable —un despliegue que pisa el archivo, un backup que restauras, un typo en el nombre— &lt;strong&gt;el programa no se detiene, no da error, no avisa&lt;/strong&gt;. Simplemente empieza a usar 133 como si alguien lo hubiera decidido.&lt;/p&gt;

&lt;p&gt;En mi caso, ese &lt;strong&gt;133&lt;/strong&gt; era el capital con el que un bot operaba con dinero real. La variable que debía definirlo desapareció en un restore, y el default la reemplazó en silencio. El sistema no falló ruidosamente. Falló callado — que es peor, porque no deja rastro y lo descubres meses después.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La parte que engaña de verdad: el valor cero&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aquí está el detalle que casi me muerde. Supón que quieres desactivar ese componente poniendo su valor en cero. Lo natural: defines la variable como &lt;strong&gt;0&lt;/strong&gt;. Pues bien — dependiendo de dónde pongas el &lt;strong&gt;or&lt;/strong&gt;, obtienes cosas opuestas:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcyhnxzced5xdbmra3nfq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcyhnxzced5xdbmra3nfq.png" alt=" " width="665" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En el segundo caso pusiste &lt;strong&gt;0&lt;/strong&gt; y el programa te devolvió &lt;strong&gt;133&lt;/strong&gt;. ¿Por qué? Porque para el &lt;strong&gt;or&lt;/strong&gt;, el número &lt;strong&gt;0&lt;/strong&gt; cuenta como &lt;strong&gt;"vacío"&lt;/strong&gt; (en jerga: es falsy). Entonces el &lt;strong&gt;or&lt;/strong&gt; lo descarta y salta al default. Intentaste apagar algo poniéndolo en cero, y conseguiste justo lo contrario.&lt;/p&gt;

&lt;p&gt;Lo peligroso: &lt;strong&gt;este bug solo aparece con el valor cero&lt;/strong&gt;. Todos tus tests con valores normales pasan. El fallo espera callado hasta que alguien prueba el caso límite.&lt;/p&gt;

&lt;p&gt;La lección de fondo, en una frase: or &lt;strong&gt;no distingue entre "no hay valor" y "el valor es cero"&lt;/strong&gt;. Para configuración, esas dos cosas son totalmente distintas — pero &lt;strong&gt;or&lt;/strong&gt; las trata igual. Ahí vive el bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No es solo Python&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si usas otro lenguaje, el mismo patrón te espera con otra cara. En JavaScript, &lt;strong&gt;process.env.VAR || "133"&lt;/strong&gt; tiene el problema idéntico con &lt;strong&gt;0&lt;/strong&gt;, "" y &lt;strong&gt;false&lt;/strong&gt;. (Por eso JS moderno introdujo ??, el &lt;strong&gt;nullish coalescing&lt;/strong&gt;, que solo cae al default cuando el valor es realmente nulo — creado justo para arreglar esto.) En &lt;strong&gt;Go&lt;/strong&gt; se disfraza de &lt;strong&gt;if val == "" { val = "133" }&lt;/strong&gt;, mezclando "no existe" con "cadena vacía".&lt;/p&gt;

&lt;p&gt;El patrón es universal porque el impulso que lo crea es universal: quiero un valor sensato si la config falla. Buena intención. Mala ejecución.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qué hacer en su lugar&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La regla: &lt;strong&gt;si un valor es importante, su ausencia no debe ser silenciosa&lt;/strong&gt;. Tres opciones, de más a menos estricta:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Si es obligatorio, que el programa grite&lt;/strong&gt;. No inventes default; exige que exista.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7lu2p23nzf4ys8y2s1bi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7lu2p23nzf4ys8y2s1bi.png" alt=" " width="444" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Un programa que se niega a arrancar sin su config es mucho más seguro que uno que arranca con un valor fantasma.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Si quieres un default, usa el argumento correcto&lt;/strong&gt; &lt;strong&gt;(no&lt;/strong&gt; or*&lt;em&gt;)&lt;/em&gt;*.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa1e3l0l5bw0pfizvjvh1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa1e3l0l5bw0pfizvjvh1.png" alt=" " width="414" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El segundo argumento de getenv solo actúa cuando la variable no existe — no cuando vale cero. Que es lo que de verdad querías.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Si el default es a propósito, hazlo visible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj9w3gvq4mxtmdkv4pu7m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj9w3gvq4mxtmdkv4pu7m.png" alt=" " width="591" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Así, si un restore borra la variable, no tienes un bug silencioso: tienes una línea en el log que te dice exactamente qué pasó.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Esto no es solo trading&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mi caso fue un bot con capital real, pero el patrón vive en cualquier sistema donde un valor equivocado importa: un &lt;strong&gt;límite de acceso&lt;/strong&gt; que cae a un default permisivo, una &lt;strong&gt;configuración de seguridad&lt;/strong&gt; que desaparece y se reemplaza sin avisar, un &lt;strong&gt;umbral&lt;/strong&gt; que silenciosamente vuelve a un valor de fábrica. En todos, un default silencioso convierte "se borró la config" en "el sistema hace algo que nadie pidió" — sin dejar rastro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La lección para llevarte&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Un default silencioso cambia un fallo que ves por uno que no ves&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;El &lt;strong&gt;getenv("X") or default&lt;/strong&gt; parece robustez, pero es fragilidad disfrazada: en vez de detenerse y avisarte cuando falta algo, sigue adelante con un valor que nadie decidió.&lt;/p&gt;

&lt;p&gt;Un programa que se niega a arrancar te cuesta cinco minutos. Uno que arranca con el valor equivocado te cuesta encontrarlo cuando ya hizo daño. En mi caso, ese cambio de una línea fue la diferencia entre semanas de fuga silenciosa y un arranque limpio.&lt;/p&gt;

&lt;p&gt;Los valores por defecto no son gratis. Son una decisión sobre qué pasa cuando el mundo no es como esperabas — y esa decisión merece más que un or al final de una línea.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;En la próxima entrada cambio de escenario&lt;/strong&gt;: Binance movió silenciosamente uno de sus endpoints de la API, y mi bot llevaba días colocando órdenes de protección que en realidad no existían. Cómo se detecta que una plataforma de la que dependes cambió las reglas sin avisar — y cómo migrar sin quedarte expuesto.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;¿Cuál es el default silencioso más peligroso que has encontrado en código ajeno (o propio)? Los leo en los comentarios.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>trading</category>
      <category>python</category>
      <category>showdev</category>
      <category>spanish</category>
    </item>
    <item>
      <title>"El par fantasma: cómo un bot operaba con capital que nadie le asignó"</title>
      <dc:creator>Jesus Corredor</dc:creator>
      <pubDate>Sat, 29 Aug 2026 02:52:20 +0000</pubDate>
      <link>https://dev.to/jesusaco/diario-de-un-bot-que-opera-con-dinero-real-entrada-1-1gip</link>
      <guid>https://dev.to/jesusaco/diario-de-un-bot-que-opera-con-dinero-real-entrada-1-1gip</guid>
      <description>&lt;p&gt;Diario de un bot que opera con dinero real — Entrada #1: &lt;/p&gt;

&lt;p&gt;Hace unos meses eliminé un par de trading de mi bot de futuros. O eso creía.&lt;/p&gt;

&lt;p&gt;La semana pasada, durante una auditoría de rutina, encontré ese mismo par abriendo posiciones, reservando capital, y bloqueando a los demás. No lo había resucitado. Nunca se había ido.&lt;/p&gt;

&lt;p&gt;Esta es la primera de una serie de historias reales sobre un sistema que opera con dinero de verdad — donde los bugs no son un test que falla, sino capital moviéndose. Empiezo por esta: un bug silencioso que vivió meses en producción, y por qué "eliminar" algo de una forma frágil es peor que no eliminarlo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El síntoma&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Estaba revisando los logs del bot cuando una línea me detuvo:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[PAR-X] SNAP | ... | cap=135.34 | lev=3× | pos=FLAT&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ese par no debía estar ahí. Lo había sacado de la operación hacía meses. Y sin embargo, ahí estaba, con un capital asignado de ~$135 que yo no recordaba haber configurado. Peor: un par de ciclos después, otro par intentó abrir una posición y falló:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[PAR-X] ENTRY SKIP | USDT insuficiente: free=130.98 &amp;lt; needed=138.04&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;El par fantasma estaba acaparando capital, y por su culpa los pares que sí debían operar se quedaban sin fondos. Un componente que creía muerto competía por recursos con los vivos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La investigación&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lo primero fue ir a la fuente de configuración. El bot lee el capital de cada par desde un archivo de entorno (.env). Busqué la variable de ese par:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkj74f7zl2mjkwskmwl47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkj74f7zl2mjkwskmwl47.png" alt=" " width="237" height="29"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vacío. La variable no existía en el .env. Entonces, ¿de dónde salían los $135?&lt;/p&gt;

&lt;p&gt;La respuesta estaba en el código. El bot arma su lista de pares desde un diccionario de configuración, y cada par lee su capital así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkoc13jyolob98u0rvo5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkoc13jyolob98u0rvo5b.png" alt=" " width="504" height="34"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahí estaba el problema. &lt;code&gt;os.getenv("PAR_X_USDT")&lt;/code&gt; devuelve None cuando la variable no existe. Y None or "133" evalúa a "133". El bot, al no encontrar la variable en el .env, &lt;strong&gt;caía a un valor por defecto hardcodeado de $133&lt;/strong&gt; que alguien (yo, meses atrás) había dejado en el código como fallback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La causa raíz — y aquí está la lección real&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El bug no era solo el fallback. Era &lt;strong&gt;cómo había "eliminado" el par meses atrás.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;En su momento, para desactivarlo, había puesto su capital en cero en el &lt;code&gt;.env (PAR_X_USDT=0)&lt;/code&gt;. Funcionó… hasta que un día restauré un backup del código por otra razón. Ese backup era anterior a mi cambio, o el .env se sobrescribió en el proceso. La variable desapareció. Y sin la variable, el código volvió a caer al fallback de $133.&lt;/p&gt;

&lt;p&gt;El par nunca estuvo eliminado de verdad. Seguía en el diccionario de configuración que el bot recorre en cada ciclo. Yo solo le había bajado el capital por una vía frágil — una variable de entorno — que un restore podía borrar. La eliminación real habría sido sacarlo del diccionario de configuración, en el código. No lo hice. Y el sistema, silenciosamente, lo revivió.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El fix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La corrección definitiva fue eliminar el bloque del par directamente del diccionario de configuración en el código, no del .env. Con el protocolo de siempre:&lt;/p&gt;

&lt;p&gt;Verificar que el par no tuviera posición abierta (revisar su archivo de estado).&lt;br&gt;
Backup del archivo con timestamp.&lt;br&gt;
Eliminar el bloque de configuración.&lt;br&gt;
Validar la sintaxis &lt;code&gt;(python3 -c "import ast; ast.parse(...)")&lt;/code&gt;.&lt;br&gt;
Confirmar con grep que el par ya no aparecía en ningún lado.&lt;br&gt;
Reinicio manual.&lt;/p&gt;

&lt;p&gt;Tras el reinicio, el banner de arranque lo confirmó: el bot ahora recorría solo los pares correctos. El fantasma se había ido de verdad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El cuerpo del delito&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Quedó un detalle que cierra la historia. Tras eliminar el par, su archivo de estado local quedó huérfano en el directorio. Lo abrí. Ahí seguía, congelado, con su última sesión registrada:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gs7f8bsp9o1qikz9wub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gs7f8bsp9o1qikz9wub.png" alt=" " width="257" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;initial_capital: 133. La evidencia física del fallback, escrita en disco. El par había estado operando todo ese tiempo con un capital que ningún archivo de configuración le asignó — solo un valor por defecto olvidado en una línea de código.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La lección para llevarte&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Antes de las lecciones técnicas, la pregunta que me perseguía mientras lo corregía: &lt;strong&gt;¿cómo sobrevivió esto a las auditorías anteriores?&lt;/strong&gt; Había revisado este bot varias veces. ¿Cómo no lo vi?&lt;/p&gt;

&lt;p&gt;La respuesta me pareció más valiosa que el bug en sí. No lo detecté antes &lt;strong&gt;porque estaba auditando el código, y el código estaba bien&lt;/strong&gt;. El fallback or "133" no es un error de sintaxis, ni una excepción, ni nada que un grep de "bug" encuentre. Es una línea perfectamente válida que hace exactamente lo que dice — el problema no vivía en el código, vivía en la brecha entre el código y la configuración. El .env decía una cosa (nada), el código asumía otra (un default), y ninguna revisión de un solo lado podía ver la contradicción, porque cada lado, por separado, era correcto.&lt;/p&gt;

&lt;p&gt;El bug solo era visible cruzando ambos contra lo que el sistema hacía en vivo — leyendo los logs de ejecución real y preguntándose "¿por qué este par tiene capital si yo no se lo di?". Lo encontré no porque revisara mejor el código, sino porque miré lo que el bot realmente estaba haciendo y no cuadraba con lo que yo creía haber configurado.&lt;/p&gt;

&lt;p&gt;Esa es la lección de fondo: &lt;strong&gt;una auditoría que solo lee el código estático nunca atrapa un bug de configuración fantasma&lt;/strong&gt;. Los sistemas mienten por omisión. La verdad no está en el código ni en la config por separado, sino en el comportamiento observado. Si algo opera distinto de como crees haberlo configurado, esa discrepancia es el bug, aunque cada archivo por separado se vea impecable.&lt;/p&gt;

&lt;p&gt;De ahí, tres trampas concretas que me llevo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Los defaults hardcodeados son deuda oculta&lt;/strong&gt;. Un &lt;code&gt;os.getenv("X")&lt;/code&gt; or "133" parece defensivo, pero convierte la ausencia de configuración en un valor silencioso. Si la variable desaparece, el sistema no se detiene ni avisa: sigue con un número que nadie decidió.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Desactivar por la vía frágil no es desactivar&lt;/strong&gt;. Bajar un valor en un .env es reversible por accidente — un restore, una sobrescritura, un despliegue. Si algo debe estar fuera, sácalo de la estructura que el sistema recorre, no de un parámetro que la alimenta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un backup puede resucitar tus bugs&lt;/strong&gt;. Restaurar código viejo no solo revierte features; revierte fixes. Cada restore debería venir con la pregunta: ¿qué correcciones posteriores estoy deshaciendo con esto?&lt;/p&gt;

&lt;p&gt;El par fantasma no me costó dinero — estaba en FLAT cuando lo encontré. Pero pudo haberlo hecho. Y lo más inquietante no es el bug en sí, sino cuánto tiempo vivió sin que ninguna auditoría lo viera. A veces lo más peligroso no es lo que falla ruidosamente, sino lo que funciona en silencio haciendo algo que no autorizaste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;En la próxima entrada desarmo&lt;/strong&gt; la línea exacta que causó todo esto ese inocente &lt;code&gt;os.getenv("X") or default&lt;/code&gt; — y te muestro por qué es una trampa que probablemente tienes en tu propio código ahora mismo. Incluido el caso que casi me muerde a mí: cómo intentar apagar algo poniéndolo en cero puede encenderlo.&lt;/p&gt;

&lt;p&gt;¿Te ha pasado encontrar un componente haciendo algo que juraste haber desactivado? Cuéntamelo en los comentarios — colecciono estas historias.&lt;/p&gt;

</description>
      <category>trading</category>
      <category>spanish</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Empecé este bot por desconfianza, no por avaricia.</title>
      <dc:creator>Jesus Corredor</dc:creator>
      <pubDate>Sat, 22 Aug 2026 15:13:11 +0000</pubDate>
      <link>https://dev.to/jesusaco/empece-este-bot-por-desconfianza-no-por-avaricia-4f0a</link>
      <guid>https://dev.to/jesusaco/empece-este-bot-por-desconfianza-no-por-avaricia-4f0a</guid>
      <description>&lt;p&gt;Diario de un bot que opera con dinero real — Entrada #0: el origen&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Todo empezó con un tuit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Uno de esos que seguramente también has visto: una captura de una wallet, "$100 convertidos en $10.000 en 24 horas con este bot de trading", flechas verdes, emojis de cohetes, y un "sígueme para más". Debajo, cientos de likes y gente pidiendo el enlace.&lt;/p&gt;

&lt;p&gt;Mi primera reacción no fue "quiero eso". Fue "eso es mentira".&lt;/p&gt;

&lt;p&gt;Y no hace falta ser matemático para verlo. Un retorno del 10.000% en un día no es una estrategia — es un billete de lotería premiado que alguien presenta como si fuera un método repetible. Si de verdad tuvieras un sistema que multiplica tu dinero por cien cada 24 horas, no lo estarías publicando en X pidiendo likes. Lo estarías usando en silencio hasta comprar una isla. El que regala el mapa del tesoro es porque el tesoro no existe — el verdadero producto que se vende en esos tuits no es el bot: eres tú, tu like, tu follow, tu atención.&lt;/p&gt;

&lt;p&gt;Así que no le di like. Pero me quedé pensando.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La pregunta que sí valía la pena&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Descartado el humo, quedaba una pregunta honesta debajo: despojado de la mentira del 10.000%, ¿hay algo real ahí?&lt;/p&gt;

&lt;p&gt;Porque los bots de trading existen. La automatización de estrategias es legítima. Los mercados operan 24/7 y un programa no duerme ni entra en pánico. La idea de fondo —dejar que un sistema ejecute una estrategia con disciplina, sin la emoción que arruina las decisiones humanas— no es una estafa. La estafa es el número. La estafa es prometer un retorno imposible para vender seguidores.&lt;/p&gt;

&lt;p&gt;Entonces me hice la pregunta que inició todo esto: &lt;strong&gt;¿qué pasa si alguien escéptico construye un bot de trading de verdad, con expectativas sobrias, y documenta la verdad completa — incluida la parte donde todavía no sabe si funciona?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Esa es la serie que estás empezando a leer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lo que es, y lo que no es&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para que no haya malentendidos, porque tú y yo ya sabemos cómo suele terminar este tipo de contenido:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Esto no es&lt;/strong&gt; un tutorial de "hazte rico". No voy a mostrarte una wallet inflada. No hay enlace de afiliado, no hay curso al final, no voy a decirte que copies mi estrategia y te retires en un año.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Esto sí es&lt;/strong&gt; el registro honesto de construir un sistema real que mueve dinero real: los bugs que encontré (algunos que me pusieron los pelos de punta), las auditorías, las decisiones tomadas con datos en vez de con ilusión, y los errores que documenté justamente para no repetirlos.&lt;/p&gt;

&lt;p&gt;Y aquí viene la parte que ningún tuit de captura de wallet te va a decir jamás: &lt;strong&gt;al momento de escribir esto, mi bot no ha demostrado que gane dinero de forma sostenida&lt;/strong&gt;. Tiene meses de trabajo de ingeniería sólida detrás, sobrevive a fallos que tumbarían a otros, y hay evidencia de que su estrategia podría tener una ventaja real. Pero "podría" no es "tiene". Estoy en pleno proceso de validar, con dinero real y en vivo, si esa ventaja existe de verdad o si es un espejismo de los datos históricos.&lt;/p&gt;

&lt;p&gt;Eso es lo más honesto que puedo ofrecerte, y es exactamente lo contrario del tuit que me trajo aquí. El estafador te vende certeza que no tiene. Yo te ofrezco un proceso honesto cuyo desenlace ni yo conozco todavía.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Por qué escribo esto&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Porque el espacio está tan lleno de humo que la verdad, por aburrida que sea a veces, es refrescante.&lt;/p&gt;

&lt;p&gt;Cada entrada de esta serie es un post-mortem real: algo que salió mal, cómo lo investigué, qué aprendí. No hay finales felices garantizados. Hay un componente que operó meses con dinero que nadie le asignó. Hay protecciones que el bot creía tener y no tenía. Hay una fuga silenciosa que tardé meses en detectar. Y hay decisiones —como cerrar proyectos que no funcionaban— tomadas sin dejar que el orgullo o el dinero ya invertido nublaran el juicio.&lt;/p&gt;

&lt;p&gt;Si buscabas el bot que convierte $100 en $10.000, esta no es tu serie, y con cariño te digo que esa serie no existe en ningún lado — solo existe el tuit que te la promete.&lt;/p&gt;

&lt;p&gt;Pero si te interesa cómo se construye de verdad un sistema que opera con dinero, con toda la honestidad de sus errores y la incertidumbre honesta de su resultado, quédate. Empecé esto por desconfianza. Y resulta que la desconfianza, bien usada, es el mejor punto de partida para construir algo real.&lt;br&gt;
&lt;strong&gt;En la próxima entrada empieza lo bueno:&lt;/strong&gt; cómo encontré un componente de mi bot operando con un capital que nadie le asignó — un "par fantasma" que creía haber eliminado meses atrás, y que seguía vivo, acaparando recursos, escondido a plena vista.&lt;/p&gt;

&lt;p&gt;¿Tú también llegaste a los bots de trading a través de una promesa que sabías que era falsa? ¿Qué te hizo dudar? Cuéntamelo en los comentarios.&lt;/p&gt;

</description>
      <category>trading</category>
      <category>python</category>
      <category>spanish</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
