<?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: Straud</title>
    <description>The latest articles on DEV Community by Straud (@straud_dev).</description>
    <link>https://dev.to/straud_dev</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%2F4128339%2F818c6f72-5835-44de-acee-f4245cc97e06.png</url>
      <title>DEV Community: Straud</title>
      <link>https://dev.to/straud_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/straud_dev"/>
    <language>en</language>
    <item>
      <title>68 public trading strategies: 12 declare a trial count, 0 report a Sharpe</title>
      <dc:creator>Straud</dc:creator>
      <pubDate>Wed, 16 Sep 2026 15:45:36 +0000</pubDate>
      <link>https://dev.to/straud_dev/68-public-trading-strategies-12-declare-a-trial-count-0-report-a-sharpe-3pi4</link>
      <guid>https://dev.to/straud_dev/68-public-trading-strategies-12-declare-a-trial-count-0-report-a-sharpe-3pi4</guid>
      <description>&lt;p&gt;I took a large public collection of ready-made trading strategies — 68 files, the canonical freqtrade-strategies repository — and audited what the artifacts declare, without running a single backtest.&lt;/p&gt;

&lt;p&gt;No data downloads. No re-implementation. No argument about whether I built someone's idea correctly. Just one question: if you download one of these and run it, what can you tell about the evidence behind it from what ships in the box?&lt;/p&gt;

&lt;p&gt;The answer is less than you would think, and the missing piece is the same one every time.&lt;/p&gt;

&lt;p&gt;What is in the box&lt;br&gt;
25 of 68 are optimiser output — they carry tunable parameters with live search ranges still in the file. Median 5 tunable parameters, maximum 24.&lt;/p&gt;

&lt;p&gt;12 of 68 declare a trial count somewhere, usually in a comment pasted straight from the optimiser. This is more honest than most of the field and worth saying plainly before anything else. The declared budgets:&lt;/p&gt;

&lt;p&gt;declared budget files&lt;br&gt;
  40,000    1&lt;br&gt;
  ~5,000    2&lt;br&gt;
   2,000    1&lt;br&gt;
   1,000    1&lt;br&gt;
   100 or fewer 7&lt;br&gt;
14 of the 25 optimiser-derived strategies declare no trial count at all.&lt;/p&gt;

&lt;p&gt;4 of 68 declare the sample window the result came from.&lt;/p&gt;

&lt;p&gt;Exactly 1 of 68 declares both a trial count and a window — the two fields you need together before either means anything.&lt;/p&gt;

&lt;p&gt;0 of 68 report a Sharpe ratio, or any other risk-adjusted number. Ten report a headline total profit percentage. A profit percentage cannot be deflated, cannot be compared against a null, and cannot be compared between two strategies run over different windows.&lt;/p&gt;

&lt;p&gt;That last one is the finding. It is not that the numbers are wrong. It is that the number you would need in order to check them is not a field anybody fills in.&lt;/p&gt;

&lt;p&gt;What a trial count costs&lt;br&gt;
If you search N configurations and keep the best, the best one has a good in-sample Sharpe whether or not anything real is there. That is not a flaw in anyone's method — it is what taking a maximum does.&lt;/p&gt;

&lt;p&gt;The expected best-of-N Sharpe under pure noise has a closed form (Bailey and López de Prado), so you can put a floor under what a result has to beat before it means anything other than "I looked at N things and kept the luckiest".&lt;/p&gt;

&lt;p&gt;Annualised Sharpe the winner must exceed:&lt;/p&gt;

&lt;p&gt;declared trials 1 yr    2 yr    3 yr    5 yr&lt;br&gt;
        10  1.57    1.11    0.91    0.70&lt;br&gt;
        100 2.53    1.79    1.46    1.13&lt;br&gt;
        1,000   3.26    2.30    1.88    1.46&lt;br&gt;
        5,000   3.69    2.61    2.13    1.65&lt;br&gt;
        40,000  4.19    2.96    2.42    1.87&lt;br&gt;
Read the bottom row. A strategy selected from 40,000 trials on one year of data needs an annualised Sharpe above 4.19 before its backtest says anything at all. That is not a high bar in the sense of ambitious. It is a bar almost nothing clears.&lt;/p&gt;

&lt;p&gt;The single file in the corpus that declares both fields declares exactly that: 40,000 trials, one year of data. Its reported result is a total profit figure. So the bar is computable, the result is not comparable to it, and the gap is invisible to anyone who downloads the file and runs it.&lt;/p&gt;

&lt;p&gt;The one that declares its own multiple-comparisons problem&lt;br&gt;
One file ships the results of eight optimiser runs across six different loss functions — Sharpe, Sharpe-daily, Sortino, Sortino-daily, profit-only, trade-duration — pasted in as a block of comments, one result line each, and then one set of parameters below.&lt;/p&gt;

&lt;p&gt;Publishing all eight is more transparent than publishing one, and it should be said. But the arithmetic does not care about intent: trying six objectives and keeping the configuration you liked best multiplies the search. The declared budget across those runs sums to a little over 5,000. Nothing downstream of that file knows it.&lt;/p&gt;

&lt;p&gt;The precision tell&lt;br&gt;
25 of 68 carry a take-profit or stoploss constant specified to three or more decimal places. Some go to five: 0.21275, 0.04093, 0.27058.&lt;/p&gt;

&lt;p&gt;Nobody chooses to take profit at 21.275%. That number is the output of a search, and its precision is the fingerprint. It is the cheapest thing to look for in any strategy artifact you are handed, and it takes about four seconds.&lt;/p&gt;

&lt;p&gt;What is not wrong here, and one thing that is&lt;br&gt;
It is worth being precise about what this collection does correctly, because the usual complaint does not land.&lt;/p&gt;

&lt;p&gt;Fees are applied. Freqtrade applies the exchange's fees automatically during backtesting, so a strategy file has no reason to mention them and their absence from the source is not evidence of anything. If you were about to write "they ignore costs" — I checked, and they do not.&lt;/p&gt;

&lt;p&gt;Slippage is not modelled. The documentation is explicit: orders fill at the requested price as long as it is within the candle's range. For a strategy trading a handful of times a month, fine. The file declaring 40,000 trials also declares 30,918 trades at an average of 0.39% each, which breaks even at 19.5 basis points of slippage per side, and loses a quarter of its edge at 5bp. At that trade count this is not a modelling nitpick; it is most of the result.&lt;/p&gt;

&lt;p&gt;Credit where it is due: the repository also ships a folder of four strategies deliberately flagged as containing lookahead bias, with the specific bug in each one written up as an exercise. That is a more honest artifact than most commercial products manage, and it is the reason this collection is worth auditing rather than dismissing.&lt;/p&gt;

&lt;p&gt;And mine&lt;br&gt;
I ran the same check over my own retired research artifacts before writing this. The trial-count field is missing there too — not because I did not search, but because I searched across sessions over two years and never wrote a count down anywhere. Reconstructing it afterwards is not possible.&lt;/p&gt;

&lt;p&gt;That is the same defect measured above, and it is the reason I think this is a convention problem rather than a carelessness problem.&lt;/p&gt;

&lt;p&gt;The check&lt;br&gt;
Whatever you are running, from wherever you got it, three fields:&lt;/p&gt;

&lt;p&gt;How many configurations were tried before this one was kept?&lt;br&gt;
How long was the sample?&lt;br&gt;
What was the risk-adjusted result — not the total profit?&lt;br&gt;
If you have all three, one function tells you whether the first number ate the third:&lt;/p&gt;

&lt;p&gt;import math&lt;br&gt;
from scipy.stats import norm&lt;br&gt;
GAMMA = 0.5772156649015329&lt;/p&gt;

&lt;p&gt;def sharpe_floor(n_trials, years):&lt;br&gt;
    """Annualised Sharpe a best-of-N winner must beat to mean anything."""&lt;br&gt;
    a = norm.ppf(1 - 1 / n_trials)&lt;br&gt;
    b = norm.ppf(1 - 1 / (n_trials * math.e))&lt;br&gt;
    return ((1 - GAMMA) * a + GAMMA * b) / math.sqrt(years)&lt;/p&gt;

&lt;p&gt;sharpe_floor(40000, 1)   # -&amp;gt; 4.19&lt;br&gt;
sharpe_floor(100,  3)    # -&amp;gt; 1.46&lt;br&gt;
sharpe_floor(10,   5)    # -&amp;gt; 0.70&lt;br&gt;
If you cannot answer the first question, the honest reading of the backtest is that it is uninterpretable rather than good or bad. That is an uncomfortable place to leave it, and it is where the evidence actually leaves it.&lt;/p&gt;

&lt;p&gt;The cheap fix is to write the count down while you search. Reconstructed afterwards it is always an underestimate, because the runs you do not remember are the ones that did not work.&lt;/p&gt;

&lt;p&gt;What this does not claim&lt;br&gt;
I did not run these strategies and I am not saying whether any of them makes or loses money — the audit is of the artifacts, not the returns, and every number above comes from what the files declare about themselves.&lt;/p&gt;

&lt;p&gt;The floor assumes independent trials. Guided search such as TPE is not independent, and the argument for which direction that pushes the floor is genuinely unsettled, so treat the table as an order of magnitude rather than a threshold.&lt;/p&gt;

&lt;p&gt;None of this is a recommendation to trade or not trade anything.&lt;/p&gt;

&lt;p&gt;Reproducing it&lt;br&gt;
The audit script reads only what each file declares and runs no backtest. It takes a few seconds against a fresh clone.&lt;/p&gt;

&lt;p&gt;git clone --depth 1 &lt;a href="https://github.com/freqtrade/freqtrade-strategies" rel="noopener noreferrer"&gt;https://github.com/freqtrade/freqtrade-strategies&lt;/a&gt;&lt;br&gt;
curl -O &lt;a href="https://straud.dev/audit.py" rel="noopener noreferrer"&gt;https://straud.dev/audit.py&lt;/a&gt;&lt;br&gt;
python audit.py freqtrade-strategies/user_data/strategies&lt;br&gt;
Every figure on this page comes out of it.&lt;/p&gt;

&lt;p&gt;Originally published at straud.dev.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>statistics</category>
      <category>finance</category>
    </item>
  </channel>
</rss>
