<?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: matteo adorni</title>
    <description>The latest articles on DEV Community by matteo adorni (@matteo_adorni_948bc601a06).</description>
    <link>https://dev.to/matteo_adorni_948bc601a06</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%2F4142991%2F013b919e-e7f3-43d0-a4f4-52300c70eb70.jpg</url>
      <title>DEV Community: matteo adorni</title>
      <link>https://dev.to/matteo_adorni_948bc601a06</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matteo_adorni_948bc601a06"/>
    <language>en</language>
    <item>
      <title>I tested 7 stop-loss rules on 200 days of crypto data. The loosest one won all 3 markets.</title>
      <dc:creator>matteo adorni</dc:creator>
      <pubDate>Tue, 29 Sep 2026 12:17:04 +0000</pubDate>
      <link>https://dev.to/matteo_adorni_948bc601a06/i-tested-7-stop-loss-rules-on-200-days-of-crypto-data-the-loosest-one-won-all-3-markets-h6f</link>
      <guid>https://dev.to/matteo_adorni_948bc601a06/i-tested-7-stop-loss-rules-on-200-days-of-crypto-data-the-loosest-one-won-all-3-markets-h6f</guid>
      <description>&lt;p&gt;Every trading tutorial gives you the same advice: &lt;strong&gt;always use a stop-loss, and keep it tight.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I did the boring thing and measured it. Seven stop-loss rules, six months of real Binance candles, three pairs, one entry signal, and a fill model that checks stops against &lt;em&gt;5-minute&lt;/em&gt; highs and lows instead of the 15-minute close.&lt;/p&gt;

&lt;p&gt;The result was the opposite of the advice: the wider I made the stop, the more money the bot made (or, more accurately, the less it lost). The tightest stop produced the most trades and the worst returns. And out of 21 rule/pair combinations, &lt;strong&gt;zero&lt;/strong&gt; beat simply buying and holding.&lt;/p&gt;

&lt;p&gt;This is the writeup, the code, and what I actually changed in my live-config Freqtrade bot afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Data: &lt;code&gt;BTC/USDT&lt;/code&gt;, &lt;code&gt;ETH/USDT&lt;/code&gt;, &lt;code&gt;SOL/USDT&lt;/code&gt; on Binance, 15-minute candles, 2026-03-09 → 2026-09-25 (200 days, 19,256 bars per pair). I also pulled the 5-minute candles for the same window (57,770 bars per pair) so I could resolve whether a stop was hit &lt;em&gt;inside&lt;/em&gt; a 15-minute candle rather than assuming the close is the truth.&lt;/p&gt;

&lt;p&gt;Entry signal: one line, no indicator soup. Long when RSI(14) &amp;lt; 30 on the 15-minute close. One position at a time, no pyramiding, no shorting, no leverage.&lt;/p&gt;

&lt;p&gt;Exit: the only thing that changed between runs. All seven rules share a hard 24-hour maximum hold (96 × 15m bars), so "do nothing until you get stopped out" can't silently become the winning strategy.&lt;/p&gt;

&lt;p&gt;Fees: 0.1% per side, subtracted from every single trade. That's 0.2% round trip, and with 200+ trades per rule it matters a lot more than the stop placement does.&lt;/p&gt;

&lt;p&gt;I ran the whole thing with pandas offline — no exchange calls, no per-tick data, reproducible in about a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;This is the exact simulator behind the numbers below, trimmed to the four most interesting rules so it fits in one screen. It's self-contained: if you have freqtrade's data cached (&lt;code&gt;freqtrade download-data --exchange binance --timeframes 15m 5m&lt;/code&gt;), you can paste this into a file and run it.&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;DATA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_data/data/binance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;FEE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.002&lt;/span&gt;                      &lt;span class="c1"&gt;# 0.1% per side
&lt;/span&gt;&lt;span class="n"&gt;MAX_HOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;                    &lt;span class="c1"&gt;# bars of 15m = 24h
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rsi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ewm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adjust&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;dn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;ewm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adjust&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;atr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;tr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&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;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&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;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ewm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adjust&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;simulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;atr_mult&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hi5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lo5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cl5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;
    &lt;span class="n"&gt;t15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df15&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;
    &lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mi"&gt;20&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;MAX_HOLD&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;df15&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rsi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;              &lt;span class="c1"&gt;# entry: RSI(14) &amp;lt; 30 on the 15m close
&lt;/span&gt;            &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;atr_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df15&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;df15&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;atr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sl&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;sl&lt;/span&gt; &lt;span class="nf"&gt;else &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="n"&gt;atr_mult&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;atr_val&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;atr_mult&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n5&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;t5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t15&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;peak&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&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="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;MAX_HOLD&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="n"&gt;n5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 3x5m bars = 1x15m bar
&lt;/span&gt;        &lt;span class="n"&gt;exit_p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;peak&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="n"&gt;peak&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&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;tp&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;hi5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;          &lt;span class="n"&gt;exit_p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;trail&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;lo5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;peak&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;trail&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;     &lt;span class="n"&gt;exit_p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peak&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;trail&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;lo5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                &lt;span class="n"&gt;exit_p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&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;exit_p&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exit_p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cl5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;   &lt;span class="c1"&gt;# 24h max hold -&amp;gt; exit at market
&lt;/span&gt;        &lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exit_p&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;FEE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t15&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t5&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]):&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cumprod&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trades&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;trades&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;sum&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;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trades&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trades&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details in there are the difference between an honest backtest and a marketing screenshot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The inner loop walks &lt;strong&gt;5-minute&lt;/strong&gt; bars, so a stop is triggered by the low that actually traded through it. If you only check 15-minute closes, a stop that was clearly hit gets recorded as still open — and rule #3 below suddenly looks twice as good as it is.&lt;/li&gt;
&lt;li&gt;The 5-minute cursor does not reset on every signal. After an exit, the scan resumes on the bar &lt;em&gt;after&lt;/em&gt; the exit, so the same candle can't be used to close one trade and open the next.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;Seven rules × three pairs = 21 backtests. Here is every one of them, with buy-and-hold for the same 200 days as the benchmark.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;BTC trades / return / win&lt;/th&gt;
&lt;th&gt;ETH trades / return / win&lt;/th&gt;
&lt;th&gt;SOL trades / return / win&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Buy &amp;amp; hold&lt;/td&gt;
&lt;td&gt;0 / &lt;strong&gt;+24.97%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;0 / &lt;strong&gt;+36.08%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;0 / &lt;strong&gt;+43.44%&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TP 2% / SL 10%&lt;/td&gt;
&lt;td&gt;94 / −7.26% / 50.0%&lt;/td&gt;
&lt;td&gt;95 / −23.73% / 50.5%&lt;/td&gt;
&lt;td&gt;100 / &lt;strong&gt;+8.28%&lt;/strong&gt; / 57.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TP 2% / SL 5%&lt;/td&gt;
&lt;td&gt;95 / −7.34% / 50.5%&lt;/td&gt;
&lt;td&gt;101 / −28.51% / 50.5%&lt;/td&gt;
&lt;td&gt;102 / &lt;strong&gt;+7.25%&lt;/strong&gt; / 57.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TP 2% / SL 2%&lt;/td&gt;
&lt;td&gt;111 / −17.85% / 46.8%&lt;/td&gt;
&lt;td&gt;135 / −28.26% / 49.6%&lt;/td&gt;
&lt;td&gt;139 / &lt;strong&gt;+4.09%&lt;/strong&gt; / 55.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TP 2% + trailing 2%&lt;/td&gt;
&lt;td&gt;133 / −29.87% / 39.1%&lt;/td&gt;
&lt;td&gt;172 / −19.12% / 40.1%&lt;/td&gt;
&lt;td&gt;175 / −9.63% / 41.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TP 3% / SL 1.5%&lt;/td&gt;
&lt;td&gt;122 / −29.46% / 36.1%&lt;/td&gt;
&lt;td&gt;147 / −27.18% / 40.1%&lt;/td&gt;
&lt;td&gt;140 / −34.98% / 38.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ATR stop (2 × ATR14)&lt;/td&gt;
&lt;td&gt;206 / −38.83% / 24.8%&lt;/td&gt;
&lt;td&gt;204 / −23.85% / 33.8%&lt;/td&gt;
&lt;td&gt;199 / −6.88% / 38.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TP 1% / SL 0.5%&lt;/td&gt;
&lt;td&gt;235 / −34.27% / 35.3%&lt;/td&gt;
&lt;td&gt;306 / −24.08% / 41.2%&lt;/td&gt;
&lt;td&gt;304 / −29.63% / 39.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four things fall out of that table immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The widest stop was the best rule in all three markets.&lt;/strong&gt; TP 2% / SL 10% printed the smallest loss on BTC and ETH and the only decent gain on SOL. That's not a coincidence of one pair — it's a monotonic pattern: 10% stop beats 5% beats 2% beats 1.5% beats 0.5%, in that order, on every pair I tested. The advice is exactly backwards for a mean-reversion entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The tightest stop traded the most and earned the least.&lt;/strong&gt; TP 1% / SL 0.5% fired 235, 306 and 304 times. Every extra trade paid 0.2% in fees, and the 0.5% stop converted normal noise into realised losses. On ETH it took 306 trades to lose 24% — a very expensive way to be right 41% of the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The ATR stop — the "smart, volatility-aware" choice — was the worst on BTC&lt;/strong&gt; (−38.83%) with a 24.8% win rate. A 2 × ATR14 stop on 15-minute BTC candles sits around 1–1.5%, which is functionally a tight stop. Naming a stop "adaptive" doesn't make it wide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Nothing beat buy-and-hold.&lt;/strong&gt; Not one of the 21 runs. The three positive runs all happened on SOL, where buy-and-hold returned +43.44% — so the bot that "won" gave up 35 points of upside and took on 100 trades of execution risk to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;p&gt;The stop-loss is not an edge. It cannot be. Your entry signal determines which trades you take; the stop only decides how a losing trade is &lt;em&gt;distributed over time&lt;/em&gt;. A tight stop makes the equity curve smooth, the win rate look plausible, and the drawdown small in the backtest — while guaranteeing that anything that goes against you even slightly is locked in as a loss.&lt;/p&gt;

&lt;p&gt;The real problem here is upstream: RSI &amp;lt; 30 on trending crypto is not a mean-reversion signal, it's an early-exit signal. In a market that goes up 25–43% over the window, buying every oversold dip and bailing out at the first 2% wiggle is a machine for paying fees. Adding a tighter stop to a signal with no edge is optimisation of a negative.&lt;/p&gt;

&lt;p&gt;I proved that twice in the same 200 days: the exit rule that "won" still lost to not trading at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed in the bot
&lt;/h2&gt;

&lt;p&gt;Concretely, in &lt;code&gt;config.json&lt;/code&gt; for the Freqtrade bot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"stoploss": -0.10&lt;/code&gt; — the default is 10% for a reason. If your signal needs a 0.5% stop to survive, the signal is the problem, not the stop.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"trailing_stop": true&lt;/code&gt; with &lt;code&gt;"trailing_stop_positive": 0.02&lt;/code&gt; and &lt;code&gt;"trailing_stop_positive_offset": 0.03&lt;/code&gt;. The stop only tightens &lt;em&gt;after&lt;/em&gt; the trade is 3% in profit. This is what actually beat the static tight stop on SOL (−9.63% vs −34.98% for TP 3% / SL 1.5%).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"use_exit_signal": true&lt;/code&gt; and never let a stop be the primary exit. In this dataset, &lt;code&gt;exit_reason == roi&lt;/code&gt; trades were the profitable ones; the &lt;code&gt;stop_loss&lt;/code&gt; exits were where the money went.&lt;/li&gt;
&lt;li&gt;Fees in the backtest config. Freqtrade has a &lt;code&gt;"fee"&lt;/code&gt; field in the backtest config — set it to 0.001 and leave it. A rule that only wins with fees at zero is a rule that loses on the exchange.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the meta-lesson, which cost me the least time and saved the most: &lt;strong&gt;measure the exit rule against buy-and-hold, not against the other exit rules.&lt;/strong&gt; Seven rules can fight each other forever while all of them are losing to a token you never sold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts you'd hit next
&lt;/h2&gt;

&lt;p&gt;If you want to reproduce this, two things will bite you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feather files are per timeframe. &lt;code&gt;BTC_USDT-15m.feather&lt;/code&gt; and &lt;code&gt;BTC_USDT-5m.feather&lt;/code&gt; must cover the &lt;em&gt;same&lt;/em&gt; window, or your 5-minute cursor will silently fall out of range and every trade will exit at the maximum hold.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;np.datetime64&lt;/code&gt; comparisons on timezone-aware pandas timestamps work, but mixing &lt;code&gt;datetime64[ns, UTC]&lt;/code&gt; with &lt;code&gt;datetime64[s]&lt;/code&gt; raises &lt;code&gt;TypeError: Cannot compare&lt;/code&gt; — cast the &lt;code&gt;.values&lt;/code&gt; arrays and compare those, as the snippet does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd rather skip the plumbing, I packaged the exact runner, the data downloader and a working Freqtrade config with the 10%/trailing-stop settings already applied:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://tntofficial.gumroad.com/l/priklq" rel="noopener noreferrer"&gt;Freqtrade Self-Healing Starter Kit&lt;/a&gt;&lt;/strong&gt; — $9.&lt;/p&gt;

&lt;p&gt;The watchdog and process-supervision code that keeps the bot actually running is open source (MIT), no strings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/moon-hacks/proc-watchdog" rel="noopener noreferrer"&gt;https://github.com/moon-hacks/proc-watchdog&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/matteo_adorni_948bc601a06/i-backtested-14-trading-strategies-only-one-passed-my-filters-4e8e"&gt;I backtested 14 trading strategies. Only one passed my filters.&lt;/a&gt; — where this entry signal came from, and why 13 others were rejected before the exits even mattered.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/matteo_adorni_948bc601a06/keep-freqtrade-running-247-systemd-vs-a-zero-dependency-watchdog-2d44"&gt;Keep Freqtrade running 24/7: systemd vs a zero-dependency watchdog&lt;/a&gt; — a strategy with no uptime is a strategy with no P&amp;amp;L.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;All numbers above come from a single offline run on 200 days of Binance 15m and 5m candles (2026-03-09 → 2026-09-25), 0.1% fee per side, one position at a time. Past performance of a backtest is not a strategy; it's a measurement of one.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>trading</category>
      <category>crypto</category>
      <category>automation</category>
    </item>
    <item>
      <title>Keep Freqtrade running 24/7: systemd vs a zero-dependency watchdog</title>
      <dc:creator>matteo adorni</dc:creator>
      <pubDate>Fri, 25 Sep 2026 15:22:31 +0000</pubDate>
      <link>https://dev.to/matteo_adorni_948bc601a06/keep-freqtrade-running-247-systemd-vs-a-zero-dependency-watchdog-2d44</link>
      <guid>https://dev.to/matteo_adorni_948bc601a06/keep-freqtrade-running-247-systemd-vs-a-zero-dependency-watchdog-2d44</guid>
      <description>&lt;p&gt;You built the strategy. You backtested it. You started it with &lt;code&gt;nohup&lt;/code&gt;. Three days later you check the P&amp;amp;L and… the process is gone. It has been gone since roughly two hours after you closed the terminal.&lt;/p&gt;

&lt;p&gt;"Keep Freqtrade running" is the number one operational question in every Freqtrade forum, and the answers split into two camps: &lt;strong&gt;systemd&lt;/strong&gt; and &lt;strong&gt;a cron watchdog&lt;/strong&gt;. I have run both in production-style setups. Here is the honest comparison, and the pattern I actually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: systemd (the "proper" way)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/freqtrade.service
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Freqtrade&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;freqtrade&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/freqtrade&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/freqtrade/.venv/bin/freqtrade trade --config user_data/config.json&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; freqtrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt; automatic restart on crash, restart on reboot, &lt;code&gt;journalctl&lt;/code&gt; logs, clean &lt;code&gt;systemctl stop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catches, in practice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only restarts &lt;strong&gt;the process&lt;/strong&gt;. It does not check that the &lt;em&gt;thing is healthy&lt;/em&gt;. A Freqtrade process can be alive and stuck: exchange connection dropped, a hung API, a wedged websocket. &lt;code&gt;Restart=always&lt;/code&gt; sees a running PID and does nothing.&lt;/li&gt;
&lt;li&gt;No P&amp;amp;L record. You find out about a silent stall by noticing the trade count never moved.&lt;/li&gt;
&lt;li&gt;No risk rule. If your bot is bleeding, systemd faithfully keeps it bleeding.&lt;/li&gt;
&lt;li&gt;It needs root to install, and &lt;code&gt;sudo&lt;/code&gt; on a cheap VPS is a thing you have to think about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;systemd is excellent at "make sure a binary is running". It is not an observability or risk layer, and it never claimed to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: cron + watchdog (the pattern I use)
&lt;/h2&gt;

&lt;p&gt;A tiny script, run every 15 minutes, that does three jobs: restart if dead, record health, and enforce a kill switch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt;/15 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/user/freqtrade/scripts/crypto_watchdog.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The restart detection is the part that matters, and the naive version is a trap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# DO NOT DO THIS&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; pgrep &lt;span class="nt"&gt;-f&lt;/span&gt; freqtrade &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;restart&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pgrep -f&lt;/code&gt; matches the &lt;strong&gt;full command line&lt;/strong&gt;, and your watchdog's own invocation may contain the pattern — so the script can match itself, conclude "all good", or &lt;code&gt;pkill&lt;/code&gt; its own shell. I hit exactly this and wasted an hour on a watchdog that was killing itself.&lt;/p&gt;

&lt;p&gt;Do it properly by scanning &lt;code&gt;/proc&lt;/code&gt; and requiring two independent facts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VENV_PY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/.venv/bin/python"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

find_bot&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;p &lt;span class="k"&gt;in&lt;/span&gt; /proc/[0-9]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;#/proc/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/cmdline"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    &lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/cmdline"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"freqtrade trade --config"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;exe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/exe"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$exe&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VENV_PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/proc/&amp;lt;pid&amp;gt;/cmdline&lt;/code&gt; is the real argv (NUL-separated), not a shell's mangled string.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/proc/&amp;lt;pid&amp;gt;/exe&lt;/code&gt; must resolve to &lt;strong&gt;your&lt;/strong&gt; venv interpreter — so you can never match a stray system Python or the watchdog itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the loop adds the two things systemd does not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_bot&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;nohup&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/.venv/bin/freqtrade"&lt;/span&gt; trade &lt;span class="nt"&gt;--config&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/config.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--strategy&lt;/span&gt; SwingHighToSky &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/logs/trade_stdout.log"&lt;/span&gt; 2&amp;gt;&amp;amp;1 &amp;amp;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;30
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_bot&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; restart FAILED"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"crypto-bot: restarted at &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   &lt;span class="c"&gt;# speaks only when it acts&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/monitor_pnl.py"&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;RC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$RC&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 2 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;                       &lt;span class="c"&gt;# health script says: drawdown &amp;gt;= 10%&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;pid &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;find_bot&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; DRAWDOWN LIMIT HIT -&amp;gt; bot stopped"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;                        &lt;span class="c"&gt;# one P&amp;amp;L line every 15 minutes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;monitor_pnl.py&lt;/code&gt; polls the local Freqtrade REST API (&lt;code&gt;/api/v1/profit&lt;/code&gt;, &lt;code&gt;/api/v1/status&lt;/code&gt;, &lt;code&gt;/api/v1/balance&lt;/code&gt;) and appends a single line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-09-25T15:02:58Z wallet=500.13 closed_pnl=+0.000USDT (+0.00%) all_pnl=-0.000USDT trades=0W/0L wr=0.0% open=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have a &lt;strong&gt;time series&lt;/strong&gt;, not a vibe. You can see the day the behaviour changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparison, honestly
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;systemd&lt;/th&gt;
&lt;th&gt;cron + watchdog&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;restart on crash&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restart on reboot&lt;/td&gt;
&lt;td&gt;✅ (&lt;code&gt;enable&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;✅ (cron is persistent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;needs root&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;detects "alive but stuck"&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (health script)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P&amp;amp;L time series&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;enforced risk rule&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;moves to any machine in 2 files&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither is "better". systemd is the right tool if all you need is "keep the binary up" and you have root. The watchdog wins when you want &lt;strong&gt;evidence and a kill switch&lt;/strong&gt; — which, for anything handling money, is the actual requirement.&lt;/p&gt;

&lt;p&gt;You can also run both: systemd to survive reboots, the watchdog cron for health and risk. They do not conflict.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually kills unattended bots
&lt;/h2&gt;

&lt;p&gt;Ranked by how often I have seen them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The process dies and nobody notices&lt;/strong&gt; — the watchdog fixes this in the first five minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It runs for weeks and you never look at the data&lt;/strong&gt; — the P&amp;amp;L log fixes this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It draws down and you are asleep&lt;/strong&gt; — the kill switch fixes this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machine reboots and nothing restarts it&lt;/strong&gt; — cron/&lt;code&gt;enable&lt;/code&gt; fixes this.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice that three of the four are observability and risk, not supervision.&lt;/p&gt;




&lt;p&gt;I packaged this exact layer — the &lt;code&gt;/proc&lt;/code&gt;-based watchdog, the P&amp;amp;L logger and the drawdown kill switch, with a dry-run config and setup guide — as the &lt;strong&gt;&lt;a href="https://tntofficial.gumroad.com/l/priklq" rel="noopener noreferrer"&gt;Freqtrade Self-Healing Starter Kit&lt;/a&gt;&lt;/strong&gt; ($9). It is the code from this post, ready to drop in, so you skip the hour I lost on the self-killing watchdog.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Related: &lt;a href="https://dev.to/matteo_adorni_948bc601a06/keep-any-long-running-python-process-alive-on-linux-for-0-the-proc-watchdog-pattern-1c9"&gt;Keep any long-running Python process alive on Linux for $0 (the /proc watchdog pattern)&lt;/a&gt; — the same pattern, generalised beyond Freqtrade.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related in this series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/run-a-self-healing-crypto-trading-bot-247-for-0-freqtrade-watchdog-30dh"&gt;Run a self-healing crypto trading bot 24/7 for $0 (Freqtrade + watchdog)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/keep-any-long-running-python-process-alive-on-linux-for-0-the-proc-watchdog-pattern-1c9"&gt;Keep any long-running Python process alive on Linux for $0 (the /proc watchdog pattern)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/i-backtested-14-trading-strategies-only-one-passed-my-filters-4e8e"&gt;I backtested 14 trading strategies. Only one passed my filters.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The watchdog from this post is open source: **&lt;a href="https://github.com/moon-hacks/proc-watchdog" rel="noopener noreferrer"&gt;https://github.com/moon-hacks/proc-watchdog&lt;/a&gt; (MIT).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prefer someone to just do it? I also offer a **done-for-you setup&lt;/em&gt;* (&lt;a href="https://tntofficial.gumroad.com/l/vweza" rel="noopener noreferrer"&gt;https://tntofficial.gumroad.com/l/vweza&lt;/a&gt;) — Freqtrade installed, backtested and left running in dry-run with the watchdog active.*&lt;/p&gt;

</description>
      <category>trading</category>
      <category>linux</category>
      <category>devops</category>
      <category>python</category>
    </item>
    <item>
      <title>I backtested 14 trading strategies. Only one passed my filters.</title>
      <dc:creator>matteo adorni</dc:creator>
      <pubDate>Fri, 25 Sep 2026 15:20:14 +0000</pubDate>
      <link>https://dev.to/matteo_adorni_948bc601a06/i-backtested-14-trading-strategies-only-one-passed-my-filters-4e8e</link>
      <guid>https://dev.to/matteo_adorni_948bc601a06/i-backtested-14-trading-strategies-only-one-passed-my-filters-4e8e</guid>
      <description>&lt;p&gt;The first time I backtested a trading strategy, it returned a 100% win rate. Six trades. I almost shipped it.&lt;/p&gt;

&lt;p&gt;That number told me nothing, and neither does the number you get from any single backtest run. What matters is a &lt;strong&gt;filter&lt;/strong&gt; — a small set of criteria that data has to pass before you are allowed to believe it. This post is the filter, applied to 14 real Freqtrade strategies over a 200-day window with exchange fees included.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup (so you can reproduce it)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Data: 200 days of OHLCV for &lt;code&gt;BTC/USDT&lt;/code&gt;, &lt;code&gt;ETH/USDT&lt;/code&gt;, &lt;code&gt;SOL/USDT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Fees: 0.1% worst-case, included in every result below.&lt;/li&gt;
&lt;li&gt;Window: 2026-03-10 → 2026-09-25, during which the market rose roughly &lt;strong&gt;+31%&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Engine: &lt;code&gt;freqtrade backtesting --timerange 20260310-20260925 --breakdown&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;freqtrade download-data &lt;span class="nt"&gt;--config&lt;/span&gt; config.json &lt;span class="nt"&gt;--days&lt;/span&gt; 200 &lt;span class="nt"&gt;--timeframes&lt;/span&gt; 5m 15m 1h
&lt;span class="k"&gt;for &lt;/span&gt;s &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls &lt;/span&gt;user_data/strategies/&lt;span class="k"&gt;*&lt;/span&gt;.py | xargs &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nb"&gt;basename&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/.py//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;freqtrade backtesting &lt;span class="nt"&gt;--config&lt;/span&gt; config.json &lt;span class="nt"&gt;--strategy&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--timerange&lt;/span&gt; 20260310-20260925 &lt;span class="nt"&gt;--breakdown&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"logs/backtest_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The filter
&lt;/h2&gt;

&lt;p&gt;Three rules. Every one exists because of a specific way a backtest lies.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Profit factor &amp;gt; 1.5.&lt;/strong&gt; Profit factor is gross profit ÷ gross loss. Below 1.5, a handful of lucky trades is doing all the work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max drawdown &amp;lt; 15%.&lt;/strong&gt; A strategy you cannot psychologically survive is a strategy you will disable at the worst moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Win rate &amp;gt; 55% &lt;em&gt;on a usable sample&lt;/em&gt;.&lt;/strong&gt; 100% on six trades is noise. I only count a strategy as a candidate above ~50 trades.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And one meta-rule: &lt;strong&gt;compare against buy-and-hold.&lt;/strong&gt; In a market that rose 31%, a strategy returning +1.6% did not beat the market. It just lost less badly than a coin flip. That does not make it worthless — it makes it &lt;em&gt;honest&lt;/em&gt;, and honesty is what you want in the thing that decides your entries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;strategy&lt;/th&gt;
&lt;th&gt;tf&lt;/th&gt;
&lt;th&gt;trades&lt;/th&gt;
&lt;th&gt;win%&lt;/th&gt;
&lt;th&gt;profit&lt;/th&gt;
&lt;th&gt;profit factor&lt;/th&gt;
&lt;th&gt;max drawdown&lt;/th&gt;
&lt;th&gt;Sharpe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SwingHighToSky&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15m&lt;/td&gt;
&lt;td&gt;108&lt;/td&gt;
&lt;td&gt;68.5%&lt;/td&gt;
&lt;td&gt;+1.60%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5.21&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.37%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.27&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BbandRsi&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;74.2%&lt;/td&gt;
&lt;td&gt;+3.99%&lt;/td&gt;
&lt;td&gt;1.38&lt;/td&gt;
&lt;td&gt;7.92%&lt;/td&gt;
&lt;td&gt;0.62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UniversalMACD&lt;/td&gt;
&lt;td&gt;5m&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;+1.48%&lt;/td&gt;
&lt;td&gt;∞&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;td&gt;0.79&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CombinedBinHAndCluc&lt;/td&gt;
&lt;td&gt;5m&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;+1.41%&lt;/td&gt;
&lt;td&gt;∞&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;td&gt;1.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ClucMay72018&lt;/td&gt;
&lt;td&gt;5m&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;85.7%&lt;/td&gt;
&lt;td&gt;+0.51%&lt;/td&gt;
&lt;td&gt;6.52&lt;/td&gt;
&lt;td&gt;0.09%&lt;/td&gt;
&lt;td&gt;0.73&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read it the way the filter demands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;UniversalMACD&lt;/code&gt; and &lt;code&gt;CombinedBinHAndCluc&lt;/code&gt; are traps.&lt;/strong&gt; Perfect win rates, infinite profit factor — on 6 and 9 trades. An infinite profit factor means &lt;em&gt;zero losing trades happened&lt;/em&gt;, which on a tiny sample is a coincidence, not an edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BbandRsi&lt;/code&gt; earns the most in absolute terms&lt;/strong&gt; (+3.99%) but its profit factor of 1.38 fails rule 1. Big return, fragile distribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SwingHighToSky&lt;/code&gt; is the only one passing all three rules&lt;/strong&gt; with a sample worth trusting: 108 trades, 68.5% win rate, profit factor 5.21, drawdown 0.37%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One strategy out of fourteen. That ratio is the point — not that there is a winner, but that the filter is strict enough to make the winner meaningful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Sharpe column matters more than profit
&lt;/h2&gt;

&lt;p&gt;Profit tells you what happened. Sharpe tells you how rough the ride was. &lt;code&gt;SwingHighToSky&lt;/code&gt; returns less in absolute terms than &lt;code&gt;BbandRsi&lt;/code&gt; but with a Sharpe of 3.27 vs 0.62 — a far smoother equity curve. For a bot you intend to leave running unattended, smoothness is not a luxury; it is what stops you from turning it off.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did with this
&lt;/h2&gt;

&lt;p&gt;I did not pick the highest number. I picked the one that survived the filter, put it in dry-run, and left it running — because the next question is not "which strategy wins" but "can I keep it running, trusted, for two weeks". That is an infrastructure problem, not a strategy problem, and it is the part most backtests never reach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the process has to come back if it dies,&lt;/li&gt;
&lt;li&gt;the P&amp;amp;L has to be recorded so you can compare weeks,&lt;/li&gt;
&lt;li&gt;a risk rule has to be enforced by code, not by willpower.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I packaged exactly that layer — a self-healing watchdog, a P&amp;amp;L logger, and a hard drawdown kill switch — into a small kit so you can put the filter's winner into a survivable setup in about five minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://tntofficial.gumroad.com/l/priklq" rel="noopener noreferrer"&gt;Freqtrade Self-Healing Starter Kit&lt;/a&gt;&lt;/strong&gt; ($9): the exact watchdog and config from this workflow, plus the setup guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Your backtest is a hypothesis generator, not an answer. Write the filter &lt;strong&gt;before&lt;/strong&gt; you look at the results — otherwise you will rationalise the number you like. And always ask what the market did over the same window; a strategy that loses to buy-and-hold in a bull market still might be worth running, but only if you know that is what you are choosing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of a short series on running a trading bot you can actually leave alone.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;The infrastructure layer (watchdog + P&amp;amp;L logger + drawdown kill switch) is packaged here:&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;&lt;a href="https://tntofficial.gumroad.com/l/priklq" rel="noopener noreferrer"&gt;Freqtrade Self-Healing Starter Kit&lt;/a&gt;&lt;/strong&gt; — $9.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related in this series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/run-a-self-healing-crypto-trading-bot-247-for-0-freqtrade-watchdog-30dh"&gt;Run a self-healing crypto trading bot 24/7 for $0 (Freqtrade + watchdog)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/keep-any-long-running-python-process-alive-on-linux-for-0-the-proc-watchdog-pattern-1c9"&gt;Keep any long-running Python process alive on Linux for $0 (the /proc watchdog pattern)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/keep-freqtrade-running-247-systemd-vs-a-zero-dependency-watchdog-2d44"&gt;Keep Freqtrade running 24/7: systemd vs a zero-dependency watchdog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The watchdog from this post is open source: **&lt;a href="https://github.com/moon-hacks/proc-watchdog" rel="noopener noreferrer"&gt;https://github.com/moon-hacks/proc-watchdog&lt;/a&gt; (MIT).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prefer someone to just do it? I also offer a **done-for-you setup&lt;/em&gt;* (&lt;a href="https://tntofficial.gumroad.com/l/vweza" rel="noopener noreferrer"&gt;https://tntofficial.gumroad.com/l/vweza&lt;/a&gt;) — Freqtrade installed, backtested and left running in dry-run with the watchdog active.*&lt;/p&gt;

</description>
      <category>trading</category>
      <category>python</category>
      <category>crypto</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Keep any long-running Python process alive on Linux for $0 (the /proc watchdog pattern)</title>
      <dc:creator>matteo adorni</dc:creator>
      <pubDate>Fri, 25 Sep 2026 15:01:43 +0000</pubDate>
      <link>https://dev.to/matteo_adorni_948bc601a06/keep-any-long-running-python-process-alive-on-linux-for-0-the-proc-watchdog-pattern-1c9</link>
      <guid>https://dev.to/matteo_adorni_948bc601a06/keep-any-long-running-python-process-alive-on-linux-for-0-the-proc-watchdog-pattern-1c9</guid>
      <description>&lt;p&gt;Anything you leave running will eventually stop running. A scraper, a trading bot, an agent, a queue worker — it does not matter how good the code is. What matters is whether you find out in five minutes or in three weeks.&lt;/p&gt;

&lt;p&gt;This is the pattern I use to make long-running processes self-healing, with no orchestrator, no systemd gymnastics, and no paid monitoring. It is a single bash script and a cron line. It works for anything: it does not need to know what your process does, only what it looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap everyone falls into first
&lt;/h2&gt;

&lt;p&gt;The obvious watchdog is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; pgrep &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"mybot"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;restart_mybot
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks correct. It is a landmine.&lt;/p&gt;

&lt;p&gt;The problem: &lt;code&gt;pgrep -f&lt;/code&gt; matches against the &lt;strong&gt;full command line&lt;/strong&gt;, and your watchdog's own command line contains the pattern you are searching for. Depending on how the script is invoked (especially from cron or from a parent that echoes the command), the script can match &lt;strong&gt;itself&lt;/strong&gt; and conclude the process is alive — or worse, &lt;code&gt;pkill -f mybot&lt;/code&gt; will kill the shell that is running the watchdog.&lt;/p&gt;

&lt;p&gt;I hit this exact bug: the watchdog ran, matched its own invocation, and killed itself before doing anything. The log looked like the bot was fine. It was not.&lt;/p&gt;

&lt;p&gt;There is a second, subtler failure: matching a &lt;em&gt;name&lt;/em&gt; is not matching a &lt;em&gt;process&lt;/em&gt;. Any unrelated binary with the same substring satisfies the check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The robust check: scan &lt;code&gt;/proc&lt;/code&gt;, and require two things
&lt;/h2&gt;

&lt;p&gt;Instead of matching a string, walk &lt;code&gt;/proc&lt;/code&gt; and require &lt;strong&gt;both&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the command line contains your exact start command, &lt;strong&gt;and&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the process's resolved executable is the one from &lt;strong&gt;your&lt;/strong&gt; virtualenv.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VENV_PY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/.venv/bin/python"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

find_bot&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;p &lt;span class="k"&gt;in&lt;/span&gt; /proc/[0-9]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;#/proc/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/cmdline"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    &lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/cmdline"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"freqtrade trade --config"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;exe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/exe"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$exe&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VENV_PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this is safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/proc/&amp;lt;pid&amp;gt;/cmdline&lt;/code&gt;&lt;/strong&gt; is the process's actual argv, NUL-separated — not a shell's mangled string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;readlink /proc/&amp;lt;pid&amp;gt;/exe&lt;/code&gt;&lt;/strong&gt; resolves to the real binary. Requiring it to equal your venv's interpreter means you can never match a stray system Python, a shell, or the watchdog itself.&lt;/li&gt;
&lt;li&gt;A process that has already died leaves no &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt; entry, so there is no stale-match window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adapt the &lt;code&gt;case&lt;/code&gt; pattern and the &lt;code&gt;exe&lt;/code&gt; path and this same function works for a Node app, a Go binary, a scraper — anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full watchdog
&lt;/h2&gt;

&lt;p&gt;This is production-shaped: it restarts the process, records a health snapshot on every run, enforces a safety rule, and &lt;strong&gt;stays silent unless it acts&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Generic self-healing watchdog. Silent on success.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/kali/myapp
&lt;span class="nv"&gt;VENV_PY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/.venv/bin/python"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"watchdog: cannot cd &lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;/logs/watchdog.log
ts&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s2"&gt;"%Y-%m-%dT%H:%M:%SZ"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

find_proc&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;p &lt;span class="k"&gt;in&lt;/span&gt; /proc/[0-9]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;#/proc/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/cmdline"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    &lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/cmdline"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"myapp run --config"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;exe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/exe"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$exe&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VENV_PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;MSG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_proc&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; process not running -&amp;gt; restarting"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;nohup&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/.venv/bin/python"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; myapp run &lt;span class="nt"&gt;--config&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/config.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/logs/app_stdout.log"&lt;/span&gt; 2&amp;gt;&amp;amp;1 &amp;amp;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;30                     &lt;span class="c"&gt;# give it time to come up&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_proc&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;MSG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"watchdog: restarted at &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; restart FAILED"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1                     &lt;span class="c"&gt;# exit non-zero so cron mails / your notifier fires&lt;/span&gt;
  &lt;span class="k"&gt;fi
fi&lt;/span&gt;

&lt;span class="c"&gt;# health snapshot + safety rule (exit codes: 0 ok, 2 breach, 3 unreachable)&lt;/span&gt;
&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VENV_PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/health.py"&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;RC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$RC&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 2 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  for &lt;/span&gt;pid &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;find_proc&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; SAFETY RULE TRIPPED -&amp;gt; stopping process"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MSG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MSG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;       &lt;span class="c"&gt;# speak only when something happened&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why "silent on success" is the important part
&lt;/h3&gt;

&lt;p&gt;A watchdog that messages you every fifteen minutes gets muted within a day — and a muted watchdog is worse than none. Emit output &lt;strong&gt;only&lt;/strong&gt; when you restarted something or a rule tripped, and pipe that output to whatever notifier you already use (a Discord webhook, &lt;code&gt;mail&lt;/code&gt;, a Telegram bot). Silence means health.&lt;/p&gt;

&lt;h3&gt;
  
  
  The safety rule lives outside your app
&lt;/h3&gt;

&lt;p&gt;Whatever invariant protects you from disaster — a loss limit for a trading bot, an error-rate ceiling for a scraper, a queue-depth threshold — put it in the watchdog's health script, in &lt;strong&gt;one&lt;/strong&gt; place. Your app's internal guards protect a request; this protects the &lt;em&gt;account&lt;/em&gt;. One threshold, one exit code, one kill switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt;/15 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/kali/myapp/scripts/watchdog.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fifteen minutes is a good default: fast enough to matter, slow enough to be invisible. Make the script idempotent — two runs must not start two copies. The two-condition check above guarantees that: if the process exists, the function returns a PID and nothing is started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it, or you do not have one
&lt;/h2&gt;

&lt;p&gt;A watchdog you have never seen fire is a watchdog you do not have. Do this once, immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. kill the app&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;pid &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;pgrep &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"myapp run --config"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="c"&gt;# 2. run the watchdog by hand&lt;/span&gt;
bash scripts/watchdog.sh
&lt;span class="c"&gt;# 3. confirm BOTH the restart and the log line&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt; logs/watchdog.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it does not restart, you have a path or an &lt;code&gt;exe&lt;/code&gt; mismatch — fix it now, not at 03:00.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost
&lt;/h2&gt;

&lt;p&gt;Zero. A cron line, a bash script, and the filesystem you already have. No agent to install, no dashboard, no token, no monthly fee. For the 95% of solo projects that just need "it keeps running and tells me when it doesn't", this pattern beats a full orchestrator on every axis that matters: it is 60 lines you can read and reason about completely.&lt;/p&gt;

&lt;p&gt;The two ideas worth keeping, whatever you build: &lt;strong&gt;identify processes by &lt;code&gt;/proc&lt;/code&gt; + &lt;code&gt;exe&lt;/code&gt;, never by name&lt;/strong&gt;, and &lt;strong&gt;let the watchdog speak only when it acts&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Want this pre-built?&lt;/strong&gt; I packaged the watchdog, the P&amp;amp;L logger, the backtest runner and the config&lt;br&gt;
into a drop-in kit — &lt;strong&gt;&lt;a href="https://tntofficial.gumroad.com/l/priklq" rel="noopener noreferrer"&gt;Freqtrade Self-Healing Starter Kit&lt;/a&gt;&lt;/strong&gt; ($9).&lt;br&gt;
It is the exact code from this article plus a setup guide with the &lt;code&gt;/proc&lt;/code&gt; gotcha already solved,&lt;br&gt;
so you can go from zero to a self-healing bot in about five minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related in this series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/run-a-self-healing-crypto-trading-bot-247-for-0-freqtrade-watchdog-30dh"&gt;Run a self-healing crypto trading bot 24/7 for $0 (Freqtrade + watchdog)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/i-backtested-14-trading-strategies-only-one-passed-my-filters-4e8e"&gt;I backtested 14 trading strategies. Only one passed my filters.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/keep-freqtrade-running-247-systemd-vs-a-zero-dependency-watchdog-2d44"&gt;Keep Freqtrade running 24/7: systemd vs a zero-dependency watchdog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The watchdog from this post is open source: **&lt;a href="https://github.com/moon-hacks/proc-watchdog" rel="noopener noreferrer"&gt;https://github.com/moon-hacks/proc-watchdog&lt;/a&gt; (MIT).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prefer someone to just do it? I also offer a **done-for-you setup&lt;/em&gt;* (&lt;a href="https://tntofficial.gumroad.com/l/vweza" rel="noopener noreferrer"&gt;https://tntofficial.gumroad.com/l/vweza&lt;/a&gt;) — Freqtrade installed, backtested and left running in dry-run with the watchdog active.*&lt;/p&gt;

</description>
      <category>linux</category>
      <category>bash</category>
      <category>devops</category>
      <category>python</category>
    </item>
    <item>
      <title>Run a self-healing crypto trading bot 24/7 for $0 (Freqtrade + watchdog)</title>
      <dc:creator>matteo adorni</dc:creator>
      <pubDate>Fri, 25 Sep 2026 14:35:55 +0000</pubDate>
      <link>https://dev.to/matteo_adorni_948bc601a06/run-a-self-healing-crypto-trading-bot-247-for-0-freqtrade-watchdog-30dh</link>
      <guid>https://dev.to/matteo_adorni_948bc601a06/run-a-self-healing-crypto-trading-bot-247-for-0-freqtrade-watchdog-30dh</guid>
      <description>&lt;p&gt;Most "run a trading bot" tutorials stop at &lt;em&gt;backtest passed, bot started&lt;/em&gt;. That is the easy 20%. The hard 80% is what happens on day 4 at 03:00 when the process is gone and you only notice three weeks later — with a flat equity curve and a dead clock.&lt;/p&gt;

&lt;p&gt;This post is the part nobody writes: a &lt;strong&gt;production-shaped, zero-cost setup&lt;/strong&gt; where the bot restarts itself, logs its own P&amp;amp;L, and hard-stops on a risk rule you define. Everything below was built and verified in a single afternoon on a $0 budget, using only open-source tooling. The dry-run trades are simulated — no exchange key, no real money, no risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a watchdog matters more than the strategy
&lt;/h2&gt;

&lt;p&gt;A strategy is a hypothesis. A watchdog is what turns a hypothesis into data.&lt;/p&gt;

&lt;p&gt;If your bot silently dies, every metric you were collecting stops: win rate, drawdown, profit factor. You think you are "running a 14-day validation" while in reality the last trade was nine days ago. A watchdog fixes three things cheaply:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Availability&lt;/strong&gt; — the process is restarted automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; — a P&amp;amp;L snapshot is appended to a log on every cycle, so you have a time series instead of a vibe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk&lt;/strong&gt; — a global rule (e.g. &lt;em&gt;stop if drawdown ≥ 10% of the wallet&lt;/em&gt;) is enforced by code, not by willpower.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Linux (or WSL2) with ~3 GB RAM.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uv&lt;/code&gt; for the Python environment. On modern distros &lt;code&gt;pip install&lt;/code&gt; into the system Python is blocked by PEP 668, and &lt;code&gt;uv&lt;/code&gt; sidesteps that cleanly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh
uv venv ~/freqtrade/.venv
&lt;span class="nb"&gt;source&lt;/span&gt; ~/freqtrade/.venv/bin/activate
uv pip &lt;span class="nb"&gt;install &lt;/span&gt;freqtrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A minimal, sane config
&lt;/h2&gt;

&lt;p&gt;Start with a cosmetic config and tune later. The important bits for a safe dry-run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_open_trades"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stake_currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USDT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stake_amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dry_run"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dry_run_wallet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trading_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exchange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"binance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pair_whitelist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"BTC/USDT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ETH/USDT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SOL/USDT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pairlists"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"StaticPairList"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api_server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"listen_ip_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"listen_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hermes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CHANGE_ME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jwt_secret_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CHANGE_ME_TOO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ws_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CHANGE_ME_AS_WELL"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bot_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dryrun"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"initial_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two rules that will save you hours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bind the API to &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/strong&gt;, never &lt;code&gt;0.0.0.0&lt;/code&gt;. The API exposes order and config operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set &lt;code&gt;jwt_secret_key&lt;/code&gt;, &lt;code&gt;ws_token&lt;/code&gt; and a real password&lt;/strong&gt; even in dry-run. Empty secrets are a production foot-gun waiting to be copied into live.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Backtest before you believe anything
&lt;/h2&gt;

&lt;p&gt;Download history and backtest several strategies in their &lt;em&gt;native&lt;/em&gt; timeframe. A common mistake is backtesting a 5-minute scalp strategy on 1-hour candles and concluding it is bad — you are measuring the timeframe, not the strategy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;freqtrade download-data &lt;span class="nt"&gt;--config&lt;/span&gt; config.json &lt;span class="nt"&gt;--days&lt;/span&gt; 200 &lt;span class="nt"&gt;--timeframes&lt;/span&gt; 5m 15m 1h

&lt;span class="k"&gt;for &lt;/span&gt;s &lt;span class="k"&gt;in &lt;/span&gt;SwingHighToSky BbandRsi UniversalMACD&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;freqtrade backtesting &lt;span class="nt"&gt;--config&lt;/span&gt; config.json &lt;span class="nt"&gt;--strategy&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--timerange&lt;/span&gt; 20260310-20260925 &lt;span class="nt"&gt;--breakdown&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A real result set from a 200-day window (fees included) looked like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;strategy&lt;/th&gt;
&lt;th&gt;timeframe&lt;/th&gt;
&lt;th&gt;trades&lt;/th&gt;
&lt;th&gt;win%&lt;/th&gt;
&lt;th&gt;profit&lt;/th&gt;
&lt;th&gt;profit factor&lt;/th&gt;
&lt;th&gt;max drawdown&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SwingHighToSky&lt;/td&gt;
&lt;td&gt;15m&lt;/td&gt;
&lt;td&gt;108&lt;/td&gt;
&lt;td&gt;68.5%&lt;/td&gt;
&lt;td&gt;+1.60%&lt;/td&gt;
&lt;td&gt;5.21&lt;/td&gt;
&lt;td&gt;0.37%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BbandRsi&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;74.2%&lt;/td&gt;
&lt;td&gt;+3.99%&lt;/td&gt;
&lt;td&gt;1.38&lt;/td&gt;
&lt;td&gt;7.92%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UniversalMACD&lt;/td&gt;
&lt;td&gt;5m&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;+1.48%&lt;/td&gt;
&lt;td&gt;∞&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read that table honestly. &lt;code&gt;UniversalMACD&lt;/code&gt; shows a perfect win rate — on &lt;strong&gt;six trades&lt;/strong&gt;. That is not an edge, that is a sample size. &lt;code&gt;BbandRsi&lt;/code&gt; has the highest absolute return but a weak profit factor. &lt;code&gt;SwingHighToSky&lt;/code&gt; is the only one passing &lt;em&gt;all three&lt;/em&gt; filters (PF &amp;gt; 1.5, drawdown &amp;lt; 15%, win rate &amp;gt; 55%) on a statistically usable sample. Pick on the &lt;em&gt;quality of the evidence&lt;/em&gt;, not on the biggest number.&lt;/p&gt;

&lt;p&gt;Also note: none of them beat buy-and-hold over a window where the market rose ~31%. The dry-run's job is to validate &lt;em&gt;operational behaviour&lt;/em&gt;, not to beat the market. Be suspicious of any backtest that does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The watchdog (the actually interesting part)
&lt;/h2&gt;

&lt;p&gt;Here is a working watchdog, designed to run from cron and stay silent unless something is wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# - restarts the bot if the process died&lt;/span&gt;
&lt;span class="c"&gt;# - appends a P&amp;amp;L snapshot to logs/daily_pnl.txt on every run&lt;/span&gt;
&lt;span class="c"&gt;# - SILENT on success; speaks only on restart or risk-limit breach&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/kali/freqtrade
&lt;span class="nv"&gt;VENV_PY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/.venv/bin/python"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"watchdog: cannot cd &lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;PY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;/.venv/bin/python
&lt;span class="nv"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;/logs/watchdog.log
ts&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s2"&gt;"%Y-%m-%dT%H:%M:%SZ"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Print PIDs of the REAL bot processes (empty if none).&lt;/span&gt;
find_bot&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;p &lt;span class="k"&gt;in&lt;/span&gt; /proc/[0-9]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;#/proc/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/cmdline"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    &lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/cmdline"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"freqtrade trade --config"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;exe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;/exe"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$exe&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VENV_PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;MSG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_bot&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; bot not running -&amp;gt; restarting"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;nohup&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/.venv/bin/freqtrade"&lt;/span&gt; trade &lt;span class="nt"&gt;--config&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/config.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--strategy&lt;/span&gt; SwingHighToSky &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/logs/trade_stdout.log"&lt;/span&gt; 2&amp;gt;&amp;amp;1 &amp;amp;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;30
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_bot&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;MSG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"crypto-bot: process was gone, restarted at &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; restart FAILED"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi
fi

&lt;/span&gt;&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/monitor_pnl.py"&lt;/span&gt; 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;RC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$RC&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 2 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;                      &lt;span class="c"&gt;# drawdown limit hit&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;pid &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;find_bot&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ts&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; DRAWDOWN LIMIT HIT -&amp;gt; stopping bot"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"crypto-bot: drawdown &amp;gt;= 10% of wallet. Bot STOPPED by risk rule."&lt;/span&gt; 
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MSG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MSG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The gotcha that cost me an hour
&lt;/h3&gt;

&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; detect the process with &lt;code&gt;pkill -f freqtrade&lt;/code&gt; or &lt;code&gt;pgrep -f freqtrade&lt;/code&gt;. That pattern also matches the shell that invoked your script — because the string &lt;code&gt;freqtrade&lt;/code&gt; appears in its command line — and the script kills itself. I hit exactly this. Scanning &lt;code&gt;/proc&lt;/code&gt; and requiring &lt;strong&gt;both&lt;/strong&gt; the &lt;code&gt;freqtrade trade --config&lt;/code&gt; cmdline &lt;strong&gt;and&lt;/strong&gt; an &lt;code&gt;exe&lt;/code&gt; pointing into the venv is unambiguous and safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it run without babysitting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# every 15 minutes; quiet unless it acts&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/15 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/kali/.hermes/scripts/crypto_watchdog.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it end-to-end the first time: kill the bot, run the watchdog, confirm the restart &lt;em&gt;and&lt;/em&gt; the P&amp;amp;L line. A watchdog you have never seen fire is a watchdog you do not have.&lt;/p&gt;

&lt;h2&gt;
  
  
  The kill switch is the whole point
&lt;/h2&gt;

&lt;p&gt;The drawdown rule (&lt;code&gt;&amp;gt;= 10% of the wallet → stop the bot&lt;/code&gt;) is enforced outside the strategy, in the watchdog. Strategy-level stoplosses protect a &lt;em&gt;trade&lt;/em&gt;; this protects the &lt;em&gt;account&lt;/em&gt;. Keeping it in one place — not scattered across strategy parameters — means you can reason about your worst case at a glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results after the first hours
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wallet=500.10 closed_pnl=+0.000USDT (+0.00%) all_pnl=+0.003USDT (+0.00%) trades=0W/0L open=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero closed trades, one open position, equity essentially flat. That is the correct &lt;em&gt;shape&lt;/em&gt; for hour one of a dry-run. The value is not the P&amp;amp;L yet — it is that the clock is now running on a validated, self-healing, risk-capped system, at zero cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do next
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Let it run for two weeks.&lt;/strong&gt; Judge the &lt;em&gt;behaviour&lt;/em&gt;: did the watchdog ever fire? did the kill switch ever trip? how did it behave around a volatility spike?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hyperopt deliberately&lt;/strong&gt; — a handful of epochs with a Sharpe-based loss, then re-backtest. Do not hyperopt your way into a curve fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only then&lt;/strong&gt; consider real capital — and only with an amount you are genuinely fine losing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole setup costs nothing and removes the two failure modes that kill most hobby bots: a dead process and an unenforced risk rule.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you build on this, the two files worth stealing are the &lt;code&gt;/proc&lt;/code&gt;-based process check and the drawdown kill switch. Everything else is configuration.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Want this pre-built?&lt;/strong&gt; I packaged the watchdog, the P&amp;amp;L logger, the backtest runner and the config&lt;br&gt;
into a drop-in kit — &lt;strong&gt;&lt;a href="https://tntofficial.gumroad.com/l/priklq" rel="noopener noreferrer"&gt;Freqtrade Self-Healing Starter Kit&lt;/a&gt;&lt;/strong&gt; ($9).&lt;br&gt;
It is the exact code from this article plus a setup guide with the &lt;code&gt;/proc&lt;/code&gt; gotcha already solved,&lt;br&gt;
so you can go from zero to a self-healing bot in about five minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related in this series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/keep-any-long-running-python-process-alive-on-linux-for-0-the-proc-watchdog-pattern-1c9"&gt;Keep any long-running Python process alive on Linux for $0 (the /proc watchdog pattern)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/i-backtested-14-trading-strategies-only-one-passed-my-filters-4e8e"&gt;I backtested 14 trading strategies. Only one passed my filters.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/matteo_adorni_948bc601a06/keep-freqtrade-running-247-systemd-vs-a-zero-dependency-watchdog-2d44"&gt;Keep Freqtrade running 24/7: systemd vs a zero-dependency watchdog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The watchdog from this post is open source: **&lt;a href="https://github.com/moon-hacks/proc-watchdog" rel="noopener noreferrer"&gt;https://github.com/moon-hacks/proc-watchdog&lt;/a&gt; (MIT).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prefer someone to just do it? I also offer a **done-for-you setup&lt;/em&gt;* (&lt;a href="https://tntofficial.gumroad.com/l/vweza" rel="noopener noreferrer"&gt;https://tntofficial.gumroad.com/l/vweza&lt;/a&gt;) — Freqtrade installed, backtested and left running in dry-run with the watchdog active.*&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>python</category>
      <category>trading</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
