<?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: Juan Carlos Isaza</title>
    <description>The latest articles on DEV Community by Juan Carlos Isaza (@isazajuancarlos).</description>
    <link>https://dev.to/isazajuancarlos</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%2F4059817%2F1ee73c68-4e15-4926-951a-2c512db40d71.png</url>
      <title>DEV Community: Juan Carlos Isaza</title>
      <link>https://dev.to/isazajuancarlos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isazajuancarlos"/>
    <language>en</language>
    <item>
      <title>From idea to MVP: launch your product in 2 weeks with Python | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Fri, 23 Oct 2026 18:38:51 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/from-idea-to-mvp-launch-your-product-in-2-weeks-with-python-xiliux-blog-30ca</link>
      <guid>https://dev.to/isazajuancarlos/from-idea-to-mvp-launch-your-product-in-2-weeks-with-python-xiliux-blog-30ca</guid>
      <description>&lt;p&gt;You have an idea. The temptation is to build it "complete" before showing it to anyone. It's the most expensive mistake you can make: you spend months on features that maybe nobody wants. An &lt;strong&gt;MVP&lt;/strong&gt; (Minimum Viable Product) turns that around.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an MVP is (and isn't)
&lt;/h2&gt;

&lt;p&gt;An MVP is the &lt;strong&gt;smallest version&lt;/strong&gt; of your product that lets you &lt;strong&gt;validate a hypothesis&lt;/strong&gt; with real users. It isn't a fake prototype nor the final product trimmed down: it's just enough to learn whether people want it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What an MVP should NOT have:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An elaborate admin panel.&lt;/li&gt;
&lt;li&gt;Multiple "just in case" integrations.&lt;/li&gt;
&lt;li&gt;Optimizations for a scale you don't yet have.&lt;/li&gt;
&lt;li&gt;Every feature the competition has.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What it should:&lt;/strong&gt; the core flow that solves the problem, and a way to measure whether it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Python speeds this up
&lt;/h2&gt;

&lt;p&gt;A modern Python stack lets you build a functional MVP in &lt;strong&gt;1–2 weeks&lt;/strong&gt; instead of months:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; for the logic and the APIs: fast and with automatic validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NiceGUI&lt;/strong&gt; for the interface without writing JavaScript (see &lt;a href="https://dev.to/blog/nicegui-web-app-python-sin-javascript"&gt;this article&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; or PostgreSQL for the data: start simple, migrate when you need to.&lt;/li&gt;
&lt;li&gt;Deployment on a modest VPS behind nginx.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single developer can cover backend, frontend, and deployment, without coordinating three specialists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The process
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define the hypothesis:&lt;/strong&gt; "users will pay for X to solve Y".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cut without mercy:&lt;/strong&gt; what is the minimum flow that tests that?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build fast&lt;/strong&gt; with tools that don't slow you down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch to real users&lt;/strong&gt; and measure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate&lt;/strong&gt; on what you learned.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal isn't perfect code: it's &lt;strong&gt;learning cheap&lt;/strong&gt;. An MVP that tells you "this doesn't work" in two weeks saves you months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you have an idea to validate?
&lt;/h2&gt;

&lt;p&gt;At Xiliux I develop MVPs in Python from end to end, with fixed price and timeline. Tell me your idea and I'll tell you how to turn it into a viable product in weeks: &lt;a href="https://dev.to/#contacto"&gt;get a quote for your MVP&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Need something like this built? At Xiliux we build custom MVPs and tools in Python and Rust. &lt;a href="https://dev.to/en/services"&gt;See our services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>RAG explained: how to give an LLM your own information | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Fri, 23 Oct 2026 18:16:52 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/rag-explained-how-to-give-an-llm-your-own-information-xiliux-blog-25ah</link>
      <guid>https://dev.to/isazajuancarlos/rag-explained-how-to-give-an-llm-your-own-information-xiliux-blog-25ah</guid>
      <description>&lt;p&gt;A language model knows a great deal about the world, but &lt;strong&gt;knows nothing about your company&lt;/strong&gt;: your manuals, your policies, your products. And if you ask it about something it doesn't know, it may &lt;em&gt;invent&lt;/em&gt; an answer that sounds convincing. &lt;strong&gt;RAG&lt;/strong&gt; (Retrieval-Augmented Generation) solves both problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Instead of expecting the model to "already know" your information, you &lt;strong&gt;give it to it at the moment of the question&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Index&lt;/strong&gt; your documents: split them into fragments and turn them into &lt;em&gt;embeddings&lt;/em&gt; (numeric vectors that capture the meaning).&lt;/li&gt;
&lt;li&gt;When a question comes in, &lt;strong&gt;search&lt;/strong&gt; for the fragments most similar to that question.&lt;/li&gt;
&lt;li&gt;Pass the LLM the question &lt;strong&gt;together with those fragments&lt;/strong&gt; and ask it to answer using them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model no longer guesses: it answers from a real context that you control. And you can ask it to &lt;strong&gt;cite&lt;/strong&gt; where each fact came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern in Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. Indexado (una vez): fragmentos -&amp;gt; embeddings -&amp;gt; base vectorial
#    (con librerías como sentence-transformers + FAISS, o un servicio gestionado)
&lt;/span&gt;
&lt;span class="c1"&gt;# 2. En cada pregunta: recuperar los fragmentos relevantes
&lt;/span&gt;&lt;span class="n"&gt;fragmentos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_vectorial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buscar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pregunta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;contexto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fragmentos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Generar la respuesta con el contexto
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anthropic&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-4-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Responde SOLO con la información del contexto. Si no está, dilo.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Contexto:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;contexto&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Pregunta: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pregunta&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the instruction in the &lt;code&gt;system&lt;/code&gt; prompt: asking it to answer &lt;strong&gt;only&lt;/strong&gt; with the context and to admit when it doesn't know is what drastically reduces hallucinations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is it good for?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Support assistants&lt;/strong&gt; that answer with your real documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal search&lt;/strong&gt; in natural language over your manuals or wikis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding&lt;/strong&gt;: a chatbot that knows your processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Details that make the difference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How you split the documents&lt;/strong&gt; (chunking) has a big effect on quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good embeddings&lt;/strong&gt; and a suitable vector database matter more than the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Citing the sources&lt;/strong&gt; builds trust and allows verification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RAG is today one of the most cost-effective ways to apply AI in a business: you retrain nothing, and the assistant improves just by updating your documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want an assistant with your data?
&lt;/h2&gt;

&lt;p&gt;At Xiliux I build RAG systems in Python from end to end. Tell me what information you want to put to work: &lt;a href="https://dev.to/#contacto"&gt;contact&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>Automate repetitive tasks with a Python bot | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Thu, 22 Oct 2026 18:38:39 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/automate-repetitive-tasks-with-a-python-bot-xiliux-blog-4h1m</link>
      <guid>https://dev.to/isazajuancarlos/automate-repetitive-tasks-with-a-python-bot-xiliux-blog-4h1m</guid>
      <description>&lt;p&gt;Every manual, repetitive task you do each week is time (and money) a Python script can recover for you. Automation isn't just for large companies.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: what is worth automating?
&lt;/h2&gt;

&lt;p&gt;Look for &lt;strong&gt;repetitive, rule-based, and frequent&lt;/strong&gt; tasks. Some common examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downloading and consolidating reports every morning.&lt;/li&gt;
&lt;li&gt;Copying data between a website and a spreadsheet.&lt;/li&gt;
&lt;li&gt;Sending reminders or alerts.&lt;/li&gt;
&lt;li&gt;Watching prices or changes on a page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A rule of thumb: if you can explain the task as a list of steps with no exceptions, it can probably be automated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Python ecosystem's tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;requests / httpx&lt;/strong&gt; to talk to APIs and websites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BeautifulSoup / Playwright&lt;/strong&gt; for web scraping (Playwright when the page loads with JavaScript).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pandas&lt;/strong&gt; to transform data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APScheduler&lt;/strong&gt; or &lt;strong&gt;cron&lt;/strong&gt; to run it on a schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram or Discord&lt;/strong&gt; bots to receive alerts where you already are.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A minimal example
&lt;/h2&gt;

&lt;p&gt;Watch a page's title and alert if it changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ...run by cron every hour...
&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Changed!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# here you would send a Telegram message
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  From script to reliable bot
&lt;/h2&gt;

&lt;p&gt;A script that runs on your laptop is a good start, but a &lt;strong&gt;reliable&lt;/strong&gt; bot lives on a server, logs what it does, handles errors (retries, timeouts), and alerts you if something fails. That leap —from experiment to a tool you trust— is where a developer adds the most value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Got a task you hate doing by hand?
&lt;/h2&gt;

&lt;p&gt;It can probably be automated. Tell me what it is and I'll tell you how to approach it: &lt;a href="https://dev.to/#contacto"&gt;contact&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Need something like this built? At Xiliux we build custom MVPs and tools in Python and Rust. &lt;a href="https://dev.to/en/services"&gt;See our services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NiceGUI: build a web application in Python without writing JavaScript | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Thu, 22 Oct 2026 18:19:17 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/nicegui-build-a-web-application-in-python-without-writing-javascript-xiliux-blog-14b2</link>
      <guid>https://dev.to/isazajuancarlos/nicegui-build-a-web-application-in-python-without-writing-javascript-xiliux-blog-14b2</guid>
      <description>&lt;p&gt;If you know Python but the frontend holds you back, &lt;strong&gt;NiceGUI&lt;/strong&gt; is one of the best pieces of news of recent years: it lets you build complete web applications —with buttons, forms, charts, and navigation— using only Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is NiceGUI?
&lt;/h2&gt;

&lt;p&gt;It's a framework built on top of &lt;strong&gt;FastAPI&lt;/strong&gt; (backend) and &lt;strong&gt;Quasar/Vue&lt;/strong&gt; (frontend). You write Python; NiceGUI generates the interface in the browser and keeps the state synchronised for you. You don't need HTML, CSS, or JavaScript to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  How simple it is
&lt;/h2&gt;

&lt;p&gt;An app with a button that shows a message is literally four lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nicegui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;

&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Greet&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_click&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hierarchy of the interface is expressed with &lt;em&gt;context managers&lt;/em&gt;, which reflect how the elements nest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;card&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sign in&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text-xl font-bold&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Enter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_click&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sign_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding reactive state, forms, tables, or routes (&lt;code&gt;@ui.page('/panel')&lt;/code&gt;) is just as direct.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is it ideal for?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MVPs and prototypes:&lt;/strong&gt; validate an idea in days, not weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboards&lt;/strong&gt; — internal ones and data panels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal tools&lt;/strong&gt; for your team, without setting up a separate frontend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demos&lt;/strong&gt; of AI models or scripts that need an interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When is it NOT the best option?
&lt;/h2&gt;

&lt;p&gt;NiceGUI renders on the client, so for sites where the &lt;strong&gt;content's SEO&lt;/strong&gt; is critical (a blog, a public landing page that has to rank) it's worth complementing it with good meta tags and structured data, or considering server-side rendering. For applications, dashboards, and internal tools it's an excellent and highly productive choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and deployment
&lt;/h2&gt;

&lt;p&gt;A NiceGUI app is deployed like any FastAPI/Uvicorn app, usually behind &lt;strong&gt;nginx&lt;/strong&gt; with systemd. Serve the static files from nginx and use &lt;code&gt;ui.run(show=False)&lt;/code&gt; in production so as not to open a browser on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want an app like this?
&lt;/h2&gt;

&lt;p&gt;This very portfolio is built with NiceGUI. If you need an MVP, a dashboard, or an internal tool in Python, &lt;a href="https://dev.to/#contacto"&gt;write to me&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Need something like this built? At Xiliux we build custom MVPs and tools in Python and Rust. &lt;a href="https://dev.to/en/services"&gt;See our services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to integrate an LLM (Claude or GPT) into your Python application | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:44:48 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/how-to-integrate-an-llm-claude-or-gpt-into-your-python-application-xiliux-blog-3mn9</link>
      <guid>https://dev.to/isazajuancarlos/how-to-integrate-an-llm-claude-or-gpt-into-your-python-application-xiliux-blog-3mn9</guid>
      <description>&lt;p&gt;Integrating a language model (LLM) into a Python application is easier today than it looks, and it opens the door to chatbots, internal assistants, data extraction, and natural-language automation. In this guide you'll see the complete pattern, with real code.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Choose the provider
&lt;/h2&gt;

&lt;p&gt;The three most used are &lt;strong&gt;Anthropic (Claude)&lt;/strong&gt;, &lt;strong&gt;OpenAI (GPT)&lt;/strong&gt;, and &lt;strong&gt;Google (Gemini)&lt;/strong&gt;. They all expose an HTTP API with an official Python SDK, and your app's logic barely changes between them. In the examples I'll use Claude, but the pattern is identical in the others.&lt;/p&gt;

&lt;p&gt;Install the SDK and store your key in an &lt;strong&gt;environment variable&lt;/strong&gt;, never in the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;anthropic
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. The minimal call
&lt;/h2&gt;

&lt;p&gt;The pattern is always the same: you send a list of messages and receive a response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reads ANTHROPIC_API_KEY from the environment
&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-4-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarise in one sentence: photosynthesis...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two important details: &lt;code&gt;resp.content&lt;/code&gt; is a &lt;strong&gt;list of blocks&lt;/strong&gt; (check &lt;code&gt;.type&lt;/code&gt; before reading &lt;code&gt;.text&lt;/code&gt;), and &lt;code&gt;max_tokens&lt;/code&gt; limits the length of the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Streaming for a good experience
&lt;/h2&gt;

&lt;p&gt;In an interface, waiting for all the text to be generated feels slow. &lt;strong&gt;Streaming&lt;/strong&gt; shows the response token by token, as in ChatGPT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-4-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a welcome email.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For long outputs, streaming also keeps the request from exceeding the connection timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Structured output (reliable JSON)
&lt;/h2&gt;

&lt;p&gt;If you need the model to return data in an exact format (for example to store it in a database), ask for a JSON schema instead of parsing free text. This eliminates the most common class of bugs when integrating AI: responses with an unpredictable format.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Best practices for production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Control the cost:&lt;/strong&gt; set a reasonable &lt;code&gt;max_tokens&lt;/code&gt; and cache repeated responses. Modern SDKs support &lt;em&gt;prompt caching&lt;/em&gt;, which greatly cheapens reused context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle errors and retries:&lt;/strong&gt; APIs fail; the official SDKs already retry 429 and 5xx errors with exponential &lt;em&gt;backoff&lt;/em&gt;. Catch the typed exceptions (&lt;code&gt;RateLimitError&lt;/code&gt;, etc.) instead of comparing text strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protect your keys:&lt;/strong&gt; environment variables, periodic rotation, and minimal permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose the model according to the task:&lt;/strong&gt; a large model for complex reasoning, a smaller and faster one for high-volume classification.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Need help?
&lt;/h2&gt;

&lt;p&gt;At Xiliux I integrate LLMs into Python applications from end to end: chatbots, AI automation, and internal assistants. If you have an idea, &lt;a href="https://dev.to/#contacto"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>When your classifier flickers: hysteresis for signals that oscillate | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:24:16 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/when-your-classifier-flickers-hysteresis-for-signals-that-oscillate-xiliux-blog-47e1</link>
      <guid>https://dev.to/isazajuancarlos/when-your-classifier-flickers-hysteresis-for-signals-that-oscillate-xiliux-blog-47e1</guid>
      <description>&lt;p&gt;You have a signal that at every observation tells you which state you're in: a health monitor that says &lt;code&gt;OK&lt;/code&gt; or &lt;code&gt;CAÍDO&lt;/code&gt;, a connectivity detector, a mode classifier. And near the threshold it &lt;strong&gt;oscillates&lt;/strong&gt;: &lt;code&gt;OK, CAÍDO, OK, CAÍDO, OK&lt;/code&gt;. Every change fires something —an alert, a failover, entering or exiting a position— and suddenly your system is shaking from noise, not from a real transition.&lt;/p&gt;

&lt;p&gt;It's the same problem your home thermostat has been solving for a century, and the solution has a name: &lt;strong&gt;hysteresis&lt;/strong&gt;. Don't change state until the new one has held.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule, in one sentence
&lt;/h2&gt;

&lt;p&gt;A new state is only &lt;strong&gt;confirmed&lt;/strong&gt; after it repeats for &lt;em&gt;N&lt;/em&gt; consecutive observations. If the candidate changes or reverts before reaching &lt;em&gt;N&lt;/em&gt;, the count resets. The current state stays stable; the flapping is ignored.&lt;/p&gt;

&lt;p&gt;I packaged it as a library —&lt;code&gt;hysteresis-state&lt;/code&gt;, pure Python, no dependencies— because I kept rewriting it over and over:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hysteresis_state&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HysteresisState&lt;/span&gt;

&lt;span class="n"&gt;estado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HysteresisState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;confirmations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;lectura&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                 &lt;span class="c1"&gt;# "OK" / "CAIDO"
&lt;/span&gt;    &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lectura&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# only changes after 3 readings in a row
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                 &lt;span class="c1"&gt;# did this reading cause the transition?
&lt;/span&gt;        &lt;span class="nf"&gt;alertar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed it &lt;code&gt;OK, CAÍDO, OK, CAÍDO, OK&lt;/code&gt; and nothing happens: no candidate held. It takes three &lt;code&gt;CAÍDO&lt;/code&gt; in a row for the change to be confirmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that's almost always missing: asymmetric hysteresis
&lt;/h2&gt;

&lt;p&gt;A single threshold has a subtle problem. If you require 3 confirmations to &lt;em&gt;enter&lt;/em&gt; failure, you also take 3 to &lt;em&gt;leave&lt;/em&gt; it — and sometimes you want exactly the opposite: &lt;strong&gt;fall fast to safety, return slowly to risk&lt;/strong&gt;. It's the behaviour of an electrical circuit breaker: it trips at the first fault, and re-arms with caution.&lt;/p&gt;

&lt;p&gt;It's solved by letting the threshold depend on the transition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1 confirmation to fall to "CAIDO", 5 to return to "OK"
&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;desde&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hacia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hacia&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAIDO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;estado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HysteresisState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;confirmations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAIDO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# falls instantly
# ...now it takes 5 "OK" in a row to return
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why "averaging" isn't enough
&lt;/h2&gt;

&lt;p&gt;The temptation is to smooth with a moving average and threshold that. It works for numeric signals, but it breaks with &lt;strong&gt;discrete states&lt;/strong&gt; (you don't average &lt;code&gt;OK&lt;/code&gt; and &lt;code&gt;CAÍDO&lt;/code&gt;), it introduces lag into &lt;em&gt;every&lt;/em&gt; transition equally, and it doesn't give you the asymmetry above. Confirmation-based hysteresis is the right tool when what oscillates is a &lt;em&gt;label&lt;/em&gt;, not a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it came from
&lt;/h2&gt;

&lt;p&gt;This was born inside a trading bot. Its regime brain classified the market into &lt;em&gt;trend / range / chaos&lt;/em&gt;, and near the thresholds it flapped: each flap froze or reactivated trading, which is exactly what you don't want happening because of noise. Hysteresis calmed it down. But the mechanism knows nothing about markets —it's anti-flapping for any discrete signal—, so I released it.&lt;/p&gt;

&lt;p&gt;It's free software. Since August 2026 the code isn't published to registries or on GitHub —we work in security, and having our source extracted would be an argument against the product—: it's delivered on request, signed and with its SHA-256, at &lt;a href="mailto:contacto@xiliux.com"&gt;contacto@xiliux.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Code and tests (with the counting and asymmetry edge cases, verified by mutation): &lt;em&gt;*&lt;/em&gt;*.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Two ways a backtest lies to you (and how to avoid them) | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Tue, 20 Oct 2026 18:41:16 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/two-ways-a-backtest-lies-to-you-and-how-to-avoid-them-xiliux-blog-5cp8</link>
      <guid>https://dev.to/isazajuancarlos/two-ways-a-backtest-lies-to-you-and-how-to-avoid-them-xiliux-blog-5cp8</guid>
      <description>&lt;p&gt;You test a strategy, or a model, on historical data. The backtest gives a pretty number. And then, live, it doesn't show up. It's almost always one of these two illusions — and both are ruled out with very little code.&lt;/p&gt;

&lt;p&gt;I packaged the two corrections as a library: &lt;code&gt;honest-eval&lt;/code&gt;, pure Python, no dependencies. They came out of a trading bot, but the rigor has nothing specific to trading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Illusion 1: the model saw the future
&lt;/h2&gt;

&lt;p&gt;Splitting the data with the classic random &lt;code&gt;train_test_split&lt;/code&gt; is correct for independent data. On a &lt;strong&gt;time series&lt;/strong&gt; it's a silent disaster: it puts tomorrow's samples into the training set, and the model "predicts" in the test things that in production wouldn't have happened yet. The metric comes out inflated, and you trust an edge that doesn't exist.&lt;/p&gt;

&lt;p&gt;The honest test is always the &lt;strong&gt;future&lt;/strong&gt;: the most recent stretch in time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;honest_eval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;temporal_split&lt;/span&gt;

&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;temporal_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_frac&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embargo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X_tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_te&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns indices, so you apply it to numpy, pandas or lists alike.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;embargo&lt;/strong&gt; closes a subtler leak: if your label looks &lt;code&gt;h&lt;/code&gt; steps ahead, a training sample within &lt;code&gt;h&lt;/code&gt; of the cut already knows part of the test's outcome. &lt;code&gt;embargo=h&lt;/code&gt; discards that edge. The metric drops — but it's finally the real &lt;em&gt;out-of-sample&lt;/em&gt; one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Illusion 2: the variant won by luck
&lt;/h2&gt;

&lt;p&gt;You have several variants and you want the best. You pick the one with the highest mean. Mistake: with few samples, &lt;strong&gt;that rewards variance, not edge&lt;/strong&gt;. The noisiest variant usually ends up on top by chance.&lt;/p&gt;

&lt;p&gt;Two corrections, both inside &lt;code&gt;select_best_variant&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pair them.&lt;/strong&gt; Measure variant and baseline on the &lt;em&gt;same&lt;/em&gt; trial and work with &lt;code&gt;δ = variant − baseline&lt;/code&gt;. The trial's common variance cancels in the subtraction, and you're left with the signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Require a lower confidence bound &amp;gt; 0.&lt;/strong&gt; Promote a variant only if &lt;code&gt;mean − z·SE &amp;gt; 0&lt;/code&gt;: "even being pessimistic within the confidence margin, it stays above the baseline".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;honest_eval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;select_best_variant&lt;/span&gt;

&lt;span class="n"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chandelier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;momentum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;# high mean, very dispersed
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;elegido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;select_best_variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.6449&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_effective_n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;politica&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;elegido&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elegido&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;baseline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;momentum&lt;/code&gt; may have the highest mean and still &lt;strong&gt;fail to promote&lt;/strong&gt;: its dispersion sinks the lower bound. That's exactly what you want to happen — it's the mechanism that rejects the lucky winner.&lt;/p&gt;

&lt;h3&gt;
  
  
  When recent data weighs more
&lt;/h3&gt;

&lt;p&gt;If the process changes over time, weight by recency. &lt;strong&gt;Kish's effective sample size&lt;/strong&gt; &lt;code&gt;(Σw)² / Σw²&lt;/code&gt; keeps a few heavily-weighted samples from passing themselves off as many:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;honest_eval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;halflife_weight&lt;/span&gt;

&lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;halflife_weight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;halflife&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;exit_ts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chandelier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deltas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  They don't create signal — they stop manufacturing it
&lt;/h2&gt;

&lt;p&gt;Neither of them invents edge: that's data and good features. What they do is &lt;strong&gt;stop manufacturing it where there is none&lt;/strong&gt;. They're the minimum for not lying to yourself before risking something real.&lt;/p&gt;

&lt;p&gt;It's free software. Since August 2026 the code is not published to registries or on GitHub — we work in security, and having our source extracted would be an argument against the product —: it's delivered on request, signed and with a SHA-256, at &lt;a href="mailto:contacto@xiliux.com"&gt;contacto@xiliux.com&lt;/a&gt;. The &lt;a href="https://github.com/isazajuancarlos/honest-eval" rel="noopener noreferrer"&gt;honest-eval&lt;/a&gt; repo has the description and how to request it.&lt;/p&gt;

</description>
      <category>python</category>
      <category>trading</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Why your bot gets a 403 from Cloudflare (and how to harden a ccxt client) | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Tue, 20 Oct 2026 18:16:17 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/why-your-bot-gets-a-403-from-cloudflare-and-how-to-harden-a-ccxt-client-xiliux-blog-32od</link>
      <guid>https://dev.to/isazajuancarlos/why-your-bot-gets-a-403-from-cloudflare-and-how-to-harden-a-ccxt-client-xiliux-blog-32od</guid>
      <description>&lt;p&gt;If you automate an exchange with &lt;a href="https://github.com/ccxt/ccxt" rel="noopener noreferrer"&gt;ccxt&lt;/a&gt;, sooner or later you'll see it in the logs: short bursts of &lt;strong&gt;&lt;code&gt;403 Forbidden&lt;/code&gt;&lt;/strong&gt; hitting &lt;code&gt;fetch_balance&lt;/code&gt;, the OHLCV calls or the earn balance, which clear up on their own after a few minutes. It's not that your API key is wrong. It's the WAF (Cloudflare) that many exchanges put in front of their REST API, challenging something that "looks like a bot".&lt;/p&gt;

&lt;p&gt;And your bot &lt;em&gt;is&lt;/em&gt; a bot — but a &lt;strong&gt;legitimate&lt;/strong&gt; one, operating your own account against the official API. The problem isn't permissions, it's HTTP client reputation. This is about reducing the WAF's false positives, not evading any access control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two layers that mitigate it
&lt;/h2&gt;

&lt;p&gt;I pulled this pattern out of my own OKX bot after several bouts of 403s, and released it as a library: &lt;code&gt;ccxt-resilience&lt;/code&gt; (Apache-2.0).&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Make the WAF challenge less: &lt;code&gt;harden&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A default ccxt client announces itself as what it is. Setting a browser &lt;code&gt;User-Agent&lt;/code&gt;, the &lt;code&gt;Accept-Language&lt;/code&gt; header and a generous timeout makes Cloudflare challenge it less often:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ccxt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ccxt_resilience&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;harden&lt;/span&gt;

&lt;span class="n"&gt;exchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;harden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ccxt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;okx&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;harden&lt;/code&gt; acts on an &lt;strong&gt;already-built&lt;/strong&gt; client, returns the same object (chainable) and never breaks its construction: if setting an attribute fails, it leaves it as it was.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Retry &lt;strong&gt;only&lt;/strong&gt; what you should: &lt;code&gt;with_retry&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The temptation is to wrap everything in a &lt;code&gt;try/except&lt;/code&gt; that retries. It's a trap: retrying a credentials or funds error only wastes time, ends just as badly, and &lt;strong&gt;hides logic bugs&lt;/strong&gt; behind waits.&lt;/p&gt;

&lt;p&gt;The key is to retry only the transient — 403/Cloudflare, 429, timeouts — with exponential backoff and jitter, and re-raise the real errors on the spot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ccxt_resilience&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;with_retry&lt;/span&gt;

&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch_balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ohlcv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch_ohlcv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTC/USDT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeframe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An authentication error is re-raised immediately, without retrying. And if the attempts run out, the &lt;strong&gt;last&lt;/strong&gt; exception is re-raised, so your fail-safe handler stays in charge (for example, returning the last cached value).&lt;/p&gt;

&lt;p&gt;The wait for attempt &lt;code&gt;i&lt;/code&gt; is &lt;code&gt;min(max_s, base * 2**i) + rand()*base&lt;/code&gt;: it grows in a bounded way and the jitter keeps multiple clients from retrying in sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  The classification is explicit
&lt;/h2&gt;

&lt;p&gt;What makes an exception retryable or not is not hidden: it's a function you can inspect and replace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ccxt_resilience&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;is_transient_error&lt;/span&gt;

&lt;span class="c1"&gt;# A Cloudflare 403? Yes. An invalid key? No.
&lt;/span&gt;&lt;span class="nf"&gt;is_transient_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;403 Forbidden Cloudflare&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;span class="nf"&gt;is_transient_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid api key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;            &lt;span class="c1"&gt;# False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;a href="https://github.com/ccxt/ccxt" rel="noopener noreferrer"&gt;ccxt&lt;/a&gt; installed it also classifies by exception type (&lt;code&gt;DDoSProtection&lt;/code&gt;, &lt;code&gt;RequestTimeout&lt;/code&gt;…); without it, by the message. That's why &lt;code&gt;ccxt&lt;/code&gt; is a &lt;strong&gt;soft&lt;/strong&gt; dependency: the library works with or without it. And if your case is a different API, &lt;code&gt;with_retry&lt;/code&gt; accepts your own retry predicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;It's free software. Since August 2026 the code is not published to registries or on GitHub — we work in security, and having our source extracted would be an argument against the product —: it's delivered on request, signed and with a SHA-256, at &lt;a href="mailto:contacto@xiliux.com"&gt;contacto@xiliux.com&lt;/a&gt;. The &lt;a href="https://github.com/isazajuancarlos/ccxt-resilience" rel="noopener noreferrer"&gt;ccxt-resilience&lt;/a&gt; repo has the description and how to request it. It's small, stateless infrastructure; use it piece by piece.&lt;/p&gt;

</description>
      <category>python</category>
      <category>trading</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>The rounding that makes your bot risk 10 times what you think | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Mon, 19 Oct 2026 18:25:16 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/the-rounding-that-makes-your-bot-risk-10-times-what-you-think-xiliux-blog-4lhj</link>
      <guid>https://dev.to/isazajuancarlos/the-rounding-that-makes-your-bot-risk-10-times-what-you-think-xiliux-blog-4lhj</guid>
      <description>&lt;p&gt;Almost every trading bot has one line that decides &lt;strong&gt;how much to buy&lt;/strong&gt;. It usually looks trivial: if I risk 1% of my balance and my stop is 1000 dollars away, the quantity comes out of a division. The arithmetic is grade-school.&lt;/p&gt;

&lt;p&gt;What isn't grade-school is making that number &lt;strong&gt;fit the exchange's constraints&lt;/strong&gt;. That's where money is lost, and in ways that don't show up in the logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern that multiplies your risk
&lt;/h2&gt;

&lt;p&gt;This is in countless bots, and was in one of mine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cantidad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cantidad_teorica&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;contract_size&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intent is defensive: "never let it come out zero". The effect is the opposite.&lt;/p&gt;

&lt;p&gt;If the theoretical quantity comes out to &lt;code&gt;0.1&lt;/code&gt; contracts, &lt;code&gt;int()&lt;/code&gt; truncates it to &lt;code&gt;0&lt;/code&gt;, and then &lt;code&gt;max(1, ...)&lt;/code&gt; forces it to &lt;strong&gt;one&lt;/strong&gt;. You've just opened a position &lt;strong&gt;ten times larger&lt;/strong&gt; than the one you authorised.&lt;/p&gt;

&lt;p&gt;With concrete numbers: 500 USD balance, 1% risk (5 USD), entry at 50,000, stop at 45,000, 0.01 contracts. The real risk of that single contract is &lt;strong&gt;50 USD&lt;/strong&gt; — ten times the budget. And it happens silently: the order is accepted, the bot carries on, there's no exception to catch.&lt;/p&gt;

&lt;p&gt;The worst part is that it's not a rare case. It happens &lt;strong&gt;whenever&lt;/strong&gt; the balance is small or the stop is wide, that is, exactly when you have the least room for error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other two that cost money
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ignoring the minimum notional.&lt;/strong&gt; The exchange rejects the order for minimum value, the bot logs it as a network error, and nobody realises that signal was never traded. The backtest counted it; the account didn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not reserving for fees.&lt;/strong&gt; With a tight stop, round-trip fees can be &lt;strong&gt;half the real risk&lt;/strong&gt;. If you size against the distance to the stop and nothing else, you systematically risk more than you think.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I solved it
&lt;/h2&gt;

&lt;p&gt;I pulled the calculation out of the bot and released it as a library: &lt;code&gt;position-sizing&lt;/code&gt; (Apache-2.0, no dependencies).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;position_sizing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MarketSpec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size_for_risk&lt;/span&gt;

&lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MarketSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;amount_step&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;min_amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;min_notional&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;size_for_risk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;risk_fraction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;49000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fee_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0005&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;buy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sin operar: %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;ccxt&lt;/code&gt; you don't have to write the &lt;code&gt;MarketSpec&lt;/code&gt; by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;position_sizing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;spec_from_ccxt&lt;/span&gt;
&lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spec_from_ccxt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;market&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTC/USDT:USDT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Two decisions that matter more than the code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Decimal&lt;/code&gt;, not &lt;code&gt;float&lt;/code&gt;, and always round down.&lt;/strong&gt; The bug is born of an accidental truncation. With decimal and explicit rounding, the direction stops being an accident of the type and becomes a decision — and the right decision is &lt;strong&gt;down&lt;/strong&gt;, because the error is asymmetric: going over budget is paid in money, falling short only costs a little performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure function: it takes a &lt;code&gt;MarketSpec&lt;/code&gt;, not an exchange.&lt;/strong&gt; It tests without a network, works for any exchange and — most importantly — &lt;strong&gt;computes the same in backtest as in production&lt;/strong&gt;. A backtest that assumes fractional positions the market would never have accepted inflates the return; using the same function on both sides, that lie disappears. (That's what &lt;a href="https://dev.to/en/blog/dos-formas-en-que-un-backtest-te-miente"&gt;another article&lt;/a&gt; is about.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Refusing to trade is also a result
&lt;/h2&gt;

&lt;p&gt;When the market's minimum quantity doesn't fit the risk budget, the library &lt;strong&gt;doesn't trade&lt;/strong&gt;. But it doesn't just return &lt;code&gt;None&lt;/code&gt;: it says how much balance would be needed for it to fit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the market's minimum quantity risks more than budgeted;
5000.00 of balance would be needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;None&lt;/code&gt; forces you to reconstruct the why by reading the code six months later. A reason with the figure inside is read in the log and acted on. Refusing is fine; refusing with an explanation is better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does NOT do
&lt;/h2&gt;

&lt;p&gt;It sizes orders. &lt;strong&gt;It doesn't decide when to trade, nor where to put the stop, nor in which direction.&lt;/strong&gt; It's arithmetic over exchange constraints, not a strategy.&lt;/p&gt;

&lt;p&gt;That boundary is deliberate: the part that gives an edge isn't published, but the plumbing we all rewrite wrong is. If your bot has a &lt;code&gt;max(1, int(...))&lt;/code&gt; in the order path, review it today.&lt;/p&gt;

</description>
      <category>python</category>
      <category>trading</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>I switched off the bot: five strategies, 77,000 candles and zero net edge over fees | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Mon, 19 Oct 2026 18:16:17 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/i-switched-off-the-bot-five-strategies-77000-candles-and-zero-net-edge-over-fees-xiliux-blog-20dm</link>
      <guid>https://dev.to/isazajuancarlos/i-switched-off-the-bot-five-strategies-77000-candles-and-zero-net-edge-over-fees-xiliux-blog-20dm</guid>
      <description>&lt;p&gt;I had a trading bot running on a server. I switched it off, and the reason wasn't a losing streak: it was a measurement.&lt;/p&gt;

&lt;p&gt;Five strategies, &lt;strong&gt;77,000 one-minute candles&lt;/strong&gt; of BTC and ETH, fees subtracted. The best of them all returned &lt;strong&gt;+0.0135%&lt;/strong&gt; per trade, with a &lt;strong&gt;t = 0.16&lt;/strong&gt; statistic.&lt;/p&gt;

&lt;p&gt;That's zero. Not "a little": zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was measured, and against what
&lt;/h2&gt;

&lt;p&gt;The five families anyone starting out tends to try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Momentum&lt;/strong&gt; — if it went up, it'll keep going up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reversion&lt;/strong&gt; — if it went up too much, it'll come back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order-book imbalance (OBI)&lt;/strong&gt; — if there's more buying pressure than selling, the price moves that way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grid&lt;/strong&gt; — laddered orders that capture oscillation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funding carry&lt;/strong&gt; — collecting the funding rate on perpetuals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And a volatility-based exit rule of the kind that does have good literature behind it.&lt;/p&gt;

&lt;p&gt;Each was measured &lt;strong&gt;subtracting the account's real fees&lt;/strong&gt;, not an ideal fee or zero. That detail is what decides the experiment, and it's the one most often left out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fees flip the sign
&lt;/h2&gt;

&lt;p&gt;A strategy on one-minute candles trades a lot. Each trade pays twice: on entry and on exit. If your gross edge per trade is on the order of thousandths of a percentage point and your round-trip fee is on the order of hundredths, there's no strategy: there's a machine for transferring money to the exchange with intermediate steps.&lt;/p&gt;

&lt;p&gt;The fee-free backtest of those same five strategies showed curves that went up. That's the whole trap in one sentence: &lt;strong&gt;a backtest without fees doesn't measure a strategy, it measures market noise&lt;/strong&gt;, and noise always has stretches that go up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The t-statistic, which is the uncomfortable part
&lt;/h2&gt;

&lt;p&gt;A positive average return says nothing on its own. Flip a coin a thousand times and you'll find streaks. The question is whether your result is distinguishable from chance, and that's what the t-statistic answers: how many standard deviations separate your mean from zero.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;t = 0.16&lt;/strong&gt;, the answer is that it isn't distinguishable. Not by a long way. To talk about a signal you need to be around 2, and even then with caution given the number of strategies tested —testing five and keeping the best &lt;strong&gt;already biases the result upward&lt;/strong&gt;, so the real bar is higher, not lower.&lt;/p&gt;

&lt;p&gt;Another of the strategies gave an information coefficient of &lt;strong&gt;+0.00%&lt;/strong&gt;. It's hard to find a more honest number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second measurement: the fixed cost
&lt;/h2&gt;

&lt;p&gt;Even if the edge had been small but real, there was a calculation almost nobody makes: &lt;strong&gt;the server costs money every month&lt;/strong&gt;, edge or no edge. With a small account, the annual cost of the infrastructure exceeded the expected return of a marginal edge.&lt;/p&gt;

&lt;p&gt;That is, even in the optimistic scenario —real but tiny edge— the whole system lost money. Not because of the market: because of the bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was done
&lt;/h2&gt;

&lt;p&gt;The bot and the data collector were switched off, and the capital moved to a savings product with a published rate. It yields little and yields what it says.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Switching off isn't giving up.&lt;/strong&gt; Keeping a system running with an edge measured at zero isn't a neutral position: it pays fees, it pays infrastructure and —most expensive of all— it consumes the attention that would be producing elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I take away
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measure with fees from the very first backtest.&lt;/strong&gt; If they show up at the end, they'll flip conclusions you already took as settled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demand a statistic, not a curve.&lt;/strong&gt; A rising curve is the easiest thing to produce by accident in this trade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count the fixed cost inside the result&lt;/strong&gt;, not beside it. A system that needs a server has to pay for it before it can be considered profitable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A negative result is a result.&lt;/strong&gt; These measurements saved months of tuning parameters on something that didn't exist.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>Trusted timestamping: how to date digital evidence so nobody can dispute the date | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:20:52 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/trusted-timestamping-how-to-date-digital-evidence-so-nobody-can-dispute-the-date-xiliux-blog-ed2</link>
      <guid>https://dev.to/isazajuancarlos/trusted-timestamping-how-to-date-digital-evidence-so-nobody-can-dispute-the-date-xiliux-blog-ed2</guid>
      <description>&lt;p&gt;A digital signature proves two things: what the file is (by its hash) and who signed it (by the key). It doesn't prove a third that almost always matters: &lt;strong&gt;when&lt;/strong&gt;. A signed hash is a point with no date. And without a verifiable date, anyone can sign a document today and claim it's a year old —or the reverse—.&lt;/p&gt;

&lt;p&gt;The reflex is to look at the file's date. It doesn't work, for a simple reason: whoever holds the file controls it. The modification date on disk is changed with a command; it isn't a witness, it's a note the interested party writes themselves.&lt;/p&gt;

&lt;p&gt;Timestamping solves this by bringing in a &lt;strong&gt;third party that cannot backdate&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a TSA works
&lt;/h2&gt;

&lt;p&gt;The classic standard is RFC 3161. The flow is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you compute your file's hash (you don't send the file, only its fingerprint),&lt;/li&gt;
&lt;li&gt;you send it to a timestamping authority (TSA),&lt;/li&gt;
&lt;li&gt;the TSA adds the time from a reliable source and &lt;strong&gt;signs the pair&lt;/strong&gt; (your hash + the time) with its own key,&lt;/li&gt;
&lt;li&gt;it returns that stamp.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From there, anyone verifies the stamp with the TSA's public key and confirms two things: that this exact hash existed at that time, and that it hasn't been touched since. The file's content never leaves your side —the TSA only ever sees the fingerprint— which matters when the file is sensitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The alternative without a third party: chaining
&lt;/h2&gt;

&lt;p&gt;You don't always want to depend on an external service. The other path is a &lt;strong&gt;verifiable append-only log&lt;/strong&gt;: each new entry includes the hash of the previous one, forming a chain (or a Merkle tree). Inserting something with a past date would force recomputing everything that came after, and that is detectable. It's the mechanism behind transparency logs and, at bottom, the same one a blockchain uses to date its transactions without anyone having to trust a central clock.&lt;/p&gt;

&lt;p&gt;The two approaches combine: you stamp with a TSA for the point-in-time proof and chain into an append-only log for the full journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  What your software must do
&lt;/h2&gt;

&lt;p&gt;If the system produces evidence someone might dispute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;don't use the system date as proof —record it if you want, but don't present it as a certain date—,&lt;/li&gt;
&lt;li&gt;stamp each item's hash against an independent time source (a TSA, or a chained log you control but can't rewrite without it showing),&lt;/li&gt;
&lt;li&gt;and store the stamp alongside the signature and the hash: the three pieces together are what make digital evidence hold up when the date, and not just the content, comes into dispute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A hash says what. A signature says who. Timestamping says when —and without the when, the other two prove less than they seem.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>What digital chain of custody really is (and what your software must do) | Xiliux Blog</title>
      <dc:creator>Juan Carlos Isaza</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:20:16 +0000</pubDate>
      <link>https://dev.to/isazajuancarlos/what-digital-chain-of-custody-really-is-and-what-your-software-must-do-xiliux-blog-2hhl</link>
      <guid>https://dev.to/isazajuancarlos/what-digital-chain-of-custody-really-is-and-what-your-software-must-do-xiliux-blog-2hhl</guid>
      <description>&lt;p&gt;When digital evidence has to hold up before a third party —an audit, a legal process, a forensic review— the question isn't «does the hash match?». It's broader: «how do I know this file is the same one that was collected, that it was produced by who it claims, and that it existed when it claims?». That is digital chain of custody: not a value, but a &lt;strong&gt;continuous proof&lt;/strong&gt; that travels with the file from its origin.&lt;/p&gt;

&lt;p&gt;It rests on four guarantees, and they are distinct from one another:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Integrity — the content didn't change
&lt;/h2&gt;

&lt;p&gt;This is what the hash gives you: a cryptographic fingerprint (SHA-256, say) that changes completely if a single bit changes. You recompute the hash and compare; if they match, the two copies are identical.&lt;/p&gt;

&lt;p&gt;But integrity alone is fragile as proof: if someone can replace the file, they can also recompute and replace the hash. The hash proves two copies are equal; it doesn't prove nobody changed both of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Authenticity — who produced it
&lt;/h2&gt;

&lt;p&gt;This is where the digital signature comes in: the producer signs the hash with their private key, and anyone verifies with the public one. Now the file isn't just intact, it's attributable. Replacing file and hash is no longer enough: you'd need the signer's private key.&lt;/p&gt;

&lt;p&gt;The signature adds a third layer almost nobody looks at: &lt;strong&gt;trust that the public key really belongs to who it claims&lt;/strong&gt;. Cryptography alone doesn't settle that —a PKI, a certificate or a registry does— and it's where most integrity reports fall apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Verifiable date — when it existed
&lt;/h2&gt;

&lt;p&gt;A hash and a signature are a point with no date. They prove the file is this one and that this person signed it, but not that it existed on Tuesday and not Thursday. For that you need a &lt;strong&gt;timestamp&lt;/strong&gt;: a third party that attests to the date and cannot backdate it. Without one, anyone can sign a document today and present it as a year old.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Traceability — who touched it afterwards
&lt;/h2&gt;

&lt;p&gt;The first three describe the file. The fourth describes its journey: a &lt;strong&gt;tamper-evident, append-only event log&lt;/strong&gt; of who collected, received, moved, stored and accessed it, with the date. If that log can be edited without a trace, the chain breaks exactly where nobody looks. A log you can rewrite isn't custody, it's decoration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for your software
&lt;/h2&gt;

&lt;p&gt;A system that «stores the hash» covers one of the four. One that genuinely sustains chain of custody:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;computes and stores each item's hash (integrity),&lt;/li&gt;
&lt;li&gt;signs it with a key whose ownership can be verified (authenticity),&lt;/li&gt;
&lt;li&gt;stamps it with a time from a trusted third party (date),&lt;/li&gt;
&lt;li&gt;and writes every access to an append-only log that can't be edited (traceability).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of the four is optional if the goal is for the evidence to survive a challenge. And the one most often forgotten is the last, because it's the only one you can't see in the file.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
